Обновить

Как я ускорил бэкапы в 20 раз и обошёл ловушку Jsoup: развитие самописной Android-читалки MRead (v1.3.0)

Всем привет! Не так давно я рассказывал, как боль от перегруженных интерфейсов заставила меня открыть Android Studio и написать собственную читалку с кастомным движком рендеринга и точным выделением текста.

Статья получила теплый отклик и в комментариях набежало много отличных предложений. В этом посте я хочу поделиться техническими решениями, которые вошли в крупное обновление 1.3.0.

1. Бэкапы и боль от Storage Access Framework (SAF)
В приложении есть функция бэкапа: упаковка базы данных Room, настроек и распакованных HTML-глав с картинками в один ZIP-архив. Изначально я писал файлы напрямую в OutputStream, полученный через ContentResolver (SAF). Итог: библиотека на 500 МБ архивировалась около 5 минут. SAF проводит проверки безопасности для каждого записываемого чанка, что убивает I/O операции.
Решение: сборка архива переехала во внутренний кэш приложения. Туда пишем без ограничений SAF - буфером по 64 КБ и уровнем сжатия BEST_SPEED (картинки уже сжаты, гнать их через BEST_COMPRESSION бессмысленно). Когда ZIP готов целиком, он одним куском копируется в пользовательскую папку через SAF - вместо тысяч мелких защищённых записей получается одна

2. Material You: как получить правильные цвета обоев
При внедрении динамических тем (Android 12+) я столкнулся с тем, что стандартный вызов dynamicLightColorScheme().background на многих устройствах выдает просто унылый белый или бледно-серый цвет, игнорируя сочные оттенки обоев.
Решение: Самые насыщенные цвета из системной палитры Monet хранятся в secondaryContainer и surface. Решение нашлось в самой палитре Monet: наиболее насыщенные цвета живут в secondaryContainer и surface, а не в background. Переориентировал маппинг цветов приложения на эти слоты и интерфейс действительно ожил. Теперь интерфейс действительно реагирует на смену обоев. Плюс привязал OnSharedPreferenceChangeListener, чтобы тема менялась мгновенно на всех экранах без перезапуска.

3. Странности парсинга FB2 и баги Jsoup
Иногда вместо обложки FB2 парсер ставил черно-белую картинку из середины книги. FB2 хранит все изображения в тегах <binary> в конце файла в хаотичном порядке. Если тег <coverpage> отсутствует, старый алгоритм просто брал первую попавшуюся картинку из бинарной кучи.
Я переписал фоллбэк: теперь, если явной обложки нет, Jsoup ищет первый тег <image> прямо внутри <body> книги.
Попутно всплыло неочевидное поведение Jsoup: если атрибут отсутствует, attr() возвращает пустую строку, а не null - это задокументировано, но интуитивно ожидаешь null. Из-за этого Элвис-операторы (?:) молча проглатывали пустую строку вместо ухода в fallback. Написал строгую обертку takeIf { it.isNotEmpty() }, и теперь обложки извлекаются безошибочно.

4. Изолированный свайп яркости в Compose
Нужно было добавить регулировку яркости свайпом по левому краю экрана. Проблема: в режиме вертикального скролла (VerticalPager) свайпер страниц перехватывает вертикальные жесты на себя.
Решение: перехватывать жест на фазе Initial - до того, как пейджер успевает его обработать. Если касание началось в левых 15% ширины экрана, событие забирается себе и до пейджера не доходит.

Помимо этого в релизе 1.3.0:
• Добавлен полноэкранный просмотрщик иллюстраций с pinch-to-zoom (на основе detectTransformGestures).
• Написан собственный File Picker со сканированием вложенных папок и извлечением книг прямо из ZIP-архивов на лету.
• Добавлен поворот страниц для PDF с сохранением состояния в SharedPreferences.
• Разделен UI верхнего меню: закладки теперь можно переименовывать, а тап по номеру страницы открывает быстрый переход.
• Добавлен множественный выбор в библиотеке (массовое добавление на полки/удаление/скрытие).

Ссылки:
GitHub
RuStore

Теги:
+3
Комментарии0

Публикации