[Программирование, Разработка мобильных приложений, Разработка под Android] Большие картинки? Deal with it
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, Хабр! Приложение iFunny создано, чтобы показывать контент, который генерируют пользователи. Это могут быть видео, гифки и картинки. Очень большие картинки. Представьте себе, сколько памяти займёт комикс, высотой в 10К пикселей. Представили? А теперь представьте, что вы не можете его сжимать, потому что в таком случае он потеряет в качестве настолько, что станет абсолютно нечитаемым. Под катом я расскажу, как iFunny работает с подобным контентом.
Из коробки Android довольно плохо справляется с большими картинками. Первая проблема, с которой мы столкнулись, — ограничение на размер Bitmap, которую может отобразить Canvas. На старых устройствах ошибки начинают возникать при стороне картинки больше 2К пикселей.
В рантайме можно получить ограничение Canvas#getMaximumBitmapHeight и Canvas#getMaximumBitmapWidth
Оно нас совершенно не устраивало, поэтому мы обошли его тем, что каждую картинку стали нарезать в памяти на куски и рисовать по отдельности в нужных местах. Таким образом, итоговое изображение собирается как лоскутное одеяло из фрагментов допустимых размеров. Этот приём решает проблему отрисовки больших картинок, но не избавляет от переполнения памяти.
На этот случай мы придумали подгружать картинки подобно тому, как картографические приложения подгружают местность. Суть алгоритма заключается в том, что большая картинка разбивается на меньшие куски, видимые из которых затем грузятся в оперативную память и показываются пользователю. Первая идея была в том, чтобы после загрузки каждой картинки нарезать её и разложить по файлам. Хотя при таком подходе и нужно разок положить в оперативку всё изображение целиком, позднее будут загружаться только видимые тайлы. Эта реализация несёт за собой сложности в менеджменте файлов каждого из тайлов, поскольку кэш ограничен, и мы периодически подчищаем его. После непродолжительного исследования мы обнаружили, что в Android предусмотрен специальный механизм, позволяющий грузить определённый кусок картинки в Bitmap — BitmapRegionDecoder.
Первые версии алгоритма работали не слишком гладко, потому что им не хватало скорости загрузки отдельных кусков. После пары часов, проведённых с профилировщиком, нашлись два места, которые стоит оптимизировать.
Во первых, декодирование отдельного фрагмента занимает много времени. Эту проблему легко решить, распараллелив загрузку каждого тайла. Для этого мы использовали RxJava, который и так широко используется в нашем приложении. Это помогло нам легко манипулировать степенью многопоточности, передавая разные типы Scheduler.
Во вторых, долгая инициализация самого декодера — BitmapRegionDecoder#newInstance().
С этим методом связан ещё один прикол — параметр isShareable ни на что не влияет, начиная с Android L.
В переиспользовании BitmapRegionDecoder есть нюанс, который заключается в том, что каждый инстанс сам по себе не предоставляет возможности параллельно выполнять decodeRegion. Поэтому просто держать один инстанс декодера нельзя. Так что наш выход — организовать пул BitmapRegionDecoder, чтобы не нужно было тратить дополнительное время на инициализацию при загрузке каждого тайла.
Ещё такой подход отлично сочетается с использованием BitmapPool. Он позволяет нам не аллоцировать картинки каждый раз, а брать изменяемые инстансы из общего пула. В своё время, эта фича существенно улучшила производительность нашего приложения. Когда пользователь прокручивает нарезанное изображение, мы должны перезагрузить довольно много картинок одинакового размера. Использование пула позволило нам не только существенно сэкономить на оперативной памяти, но и улучшить плавность работы приложения.
Так как в приложении iFunny комиксы — это длинные вертикальные картинки, то нам выгодно нарезать их узкими горизонтальными кусками. Это позволяет выгружать из оперативной памяти максимум ненужных тайлов.
К сожалению, работоспособность BitmapRegionDecoder может разниться на разных версиях Android. На 5-6 версиях ОС мы наблюдали артефакты декодирования. Виноват оказался наш способ кодирования картинок mozjpeg’ом на бэкенде. Дальше решили не исследовать, потому что таких устройств у наших пользователей очень мало.
Под спойлером пример того, как картинка выглядит в приложении
SPL
Так картинка выглядит в iFunny. Красные линии — границы тайлов, синие — показывают видимую область, а в углу выводится количество аллоцированных Bitmap. Для сравнения, контент целиком.
В итоге, разница в потреблении памяти заметна даже в профилировщике AndroidStudio. Она составляет порядка 10-15% для каждой картинки. Сейчас мы проводим эксперимент в продакшене и уже наблюдаем некоторое снижение OutOfMemory и ANR.
===========
Источник:
habr.com
===========
Похожие новости:
- [C, C++, Программирование, Функциональное программирование] Сравнение встраиваемых ЯП по размеру в исполняемом файле
- [Разработка мобильных приложений, Дизайн мобильных приложений, Разработка под Android] How to Develop Dating Mobile App like Tinder?
- [Разработка мобильных приложений, Управление разработкой, Управление проектами, Agile, Управление продуктом] Сколько стоит разработать мобильное приложение
- [JavaScript, Программирование, Разработка веб-сайтов] Использование «глобального» await в JavaScript (перевод)
- [Карьера в IT-индустрии, Программирование, Управление разработкой] Junior — приговор или возможность? Что надо знать новичкам о своей первой работе
- [IT-компании, Машинное обучение, Программирование] Microsoft рассказала, как ИИ поможет инвалидам
- [Программирование, Учебный процесс в IT, Карьера в IT-индустрии, Конференции] Бесплатные онлайн-мероприятия по разработке (20 октября — 29 октября)
- [Разработка под Android] Создание SDK под Android в стиле Single-Activity
- [Разработка мобильных приложений, Здоровье] Модель адаптивного усвоения углеводов искусственной поджелудочной железы AIAPS
- [Разработка мобильных приложений, Разработка под Android] Делаем код в адаптере чище с помощью MergeAdapter
Теги для поиска: #_programmirovanie (Программирование), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_android (Разработка под Android), #_android, #_android_development, #_blog_kompanii_funcorp (
Блог компании FunCorp
), #_programmirovanie (
Программирование
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_razrabotka_pod_android (
Разработка под Android
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 01:00
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, Хабр! Приложение iFunny создано, чтобы показывать контент, который генерируют пользователи. Это могут быть видео, гифки и картинки. Очень большие картинки. Представьте себе, сколько памяти займёт комикс, высотой в 10К пикселей. Представили? А теперь представьте, что вы не можете его сжимать, потому что в таком случае он потеряет в качестве настолько, что станет абсолютно нечитаемым. Под катом я расскажу, как iFunny работает с подобным контентом. Из коробки Android довольно плохо справляется с большими картинками. Первая проблема, с которой мы столкнулись, — ограничение на размер Bitmap, которую может отобразить Canvas. На старых устройствах ошибки начинают возникать при стороне картинки больше 2К пикселей. В рантайме можно получить ограничение Canvas#getMaximumBitmapHeight и Canvas#getMaximumBitmapWidth
Оно нас совершенно не устраивало, поэтому мы обошли его тем, что каждую картинку стали нарезать в памяти на куски и рисовать по отдельности в нужных местах. Таким образом, итоговое изображение собирается как лоскутное одеяло из фрагментов допустимых размеров. Этот приём решает проблему отрисовки больших картинок, но не избавляет от переполнения памяти. На этот случай мы придумали подгружать картинки подобно тому, как картографические приложения подгружают местность. Суть алгоритма заключается в том, что большая картинка разбивается на меньшие куски, видимые из которых затем грузятся в оперативную память и показываются пользователю. Первая идея была в том, чтобы после загрузки каждой картинки нарезать её и разложить по файлам. Хотя при таком подходе и нужно разок положить в оперативку всё изображение целиком, позднее будут загружаться только видимые тайлы. Эта реализация несёт за собой сложности в менеджменте файлов каждого из тайлов, поскольку кэш ограничен, и мы периодически подчищаем его. После непродолжительного исследования мы обнаружили, что в Android предусмотрен специальный механизм, позволяющий грузить определённый кусок картинки в Bitmap — BitmapRegionDecoder. Первые версии алгоритма работали не слишком гладко, потому что им не хватало скорости загрузки отдельных кусков. После пары часов, проведённых с профилировщиком, нашлись два места, которые стоит оптимизировать. Во первых, декодирование отдельного фрагмента занимает много времени. Эту проблему легко решить, распараллелив загрузку каждого тайла. Для этого мы использовали RxJava, который и так широко используется в нашем приложении. Это помогло нам легко манипулировать степенью многопоточности, передавая разные типы Scheduler. Во вторых, долгая инициализация самого декодера — BitmapRegionDecoder#newInstance(). С этим методом связан ещё один прикол — параметр isShareable ни на что не влияет, начиная с Android L.
В переиспользовании BitmapRegionDecoder есть нюанс, который заключается в том, что каждый инстанс сам по себе не предоставляет возможности параллельно выполнять decodeRegion. Поэтому просто держать один инстанс декодера нельзя. Так что наш выход — организовать пул BitmapRegionDecoder, чтобы не нужно было тратить дополнительное время на инициализацию при загрузке каждого тайла. Ещё такой подход отлично сочетается с использованием BitmapPool. Он позволяет нам не аллоцировать картинки каждый раз, а брать изменяемые инстансы из общего пула. В своё время, эта фича существенно улучшила производительность нашего приложения. Когда пользователь прокручивает нарезанное изображение, мы должны перезагрузить довольно много картинок одинакового размера. Использование пула позволило нам не только существенно сэкономить на оперативной памяти, но и улучшить плавность работы приложения. Так как в приложении iFunny комиксы — это длинные вертикальные картинки, то нам выгодно нарезать их узкими горизонтальными кусками. Это позволяет выгружать из оперативной памяти максимум ненужных тайлов.
К сожалению, работоспособность BitmapRegionDecoder может разниться на разных версиях Android. На 5-6 версиях ОС мы наблюдали артефакты декодирования. Виноват оказался наш способ кодирования картинок mozjpeg’ом на бэкенде. Дальше решили не исследовать, потому что таких устройств у наших пользователей очень мало. Под спойлером пример того, как картинка выглядит в приложенииSPLТак картинка выглядит в iFunny. Красные линии — границы тайлов, синие — показывают видимую область, а в углу выводится количество аллоцированных Bitmap. Для сравнения, контент целиком. В итоге, разница в потреблении памяти заметна даже в профилировщике AndroidStudio. Она составляет порядка 10-15% для каждой картинки. Сейчас мы проводим эксперимент в продакшене и уже наблюдаем некоторое снижение OutOfMemory и ANR. =========== Источник: habr.com =========== Похожие новости:
Блог компании FunCorp ), #_programmirovanie ( Программирование ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ), #_razrabotka_pod_android ( Разработка под Android ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 01:00
Часовой пояс: UTC + 5