
Как и любой другой стартап, одержимый импульсом, мы не тратили много времени на создание эффективного продукта с первого раза. Мы отправили наше приложение для Android, и оно работало «отлично».
Когда мы начали увеличивать масштаб предложения для клиентов, наше приложение стало громоздким - с тоннами изображений - и мы начали замечать проблемы с производительностью. Наше приложение работало медленно и зависало на бюджетных устройствах. Расход батареи тоже увеличился.
Для отладки этой проблемы мы использовали инструмент мониторинга памяти, предоставляемый Android Studio. Просматривая очень длинный список изображений продуктов, мы наблюдали следующее:

Чтобы немного лучше объяснить эти графики:
- Внезапное падение выделенной памяти связано с событиями сборки мусора (GC).
- Размер свободной памяти увеличивается, когда Android останавливает процессы других приложений (которые находятся в фоновом режиме), чтобы выделить больше памяти для приложения переднего плана.
- Использование ЦП увеличилось, когда мы пролистали список продуктов.
Просто открыв страницу со списком продуктов, приложение заняло 15 мегабайт памяти. Если мы прокручиваем страницу со списком продуктов до конца, приложение потребляет 50 мегабайт памяти с большим количеством событий сборки мусора.
При просмотре других списков продуктов мы наблюдали аналогичные закономерности. Вот графики:


Мы снова наблюдали эту закономерность. К этому времени Android выделил максимальный объем памяти (для растровых изображений), который он мог бы выделить нашему приложению, убивая процессы других приложений в фоновом режиме, и объем чистой памяти достиг 57 мегабайт вместе с несколькими событиями сборки мусора.
Эти графики взяты из среды выполнения Android. Dalvik ведет себя еще хуже с точки зрения управления памятью и сборки мусора.
В Android растровые изображения представляют собой самые большие непрерывные блоки памяти. Они занимают кучи, что приводит к многочисленным спорам за свободное место для выделения новых растровых изображений при прокрутке. Затем это приводит к большему количеству событий GC, поэтому он может освободить память, чтобы предоставить необходимое пространство. Поскольку в список загружалось так много изображений, эти события сборки мусора снижали производительность нашего приложения.

События GC заставляют приложение зависать до завершения. Некоторые из них не имеют значения, но слишком большое количество этих событий приведет к снижению частоты кадров. Запуск сборщика мусора также увеличивает нагрузку на ЦП, что приводит к расходу заряда батареи.
Кроме того, чем выше использование памяти приложением, тем больше вероятность, что система решит убить его, когда оно работает в фоновом режиме.
Нам нужно было решить эту проблему, прежде чем мы могли продолжить разработку продукта. И для этого мы перешли к концепции пула объектов для растровых изображений, как советует Кольт в этом видео:
Таким образом, идея состоит в том, что вместо создания нового растрового изображения вы используете существующий фрагмент памяти для загрузки растрового изображения в:
mBitmapOptions.inBitmap = mCurrentBitmap; //using the mCurrentBitmap to load the new bitmap mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);
Затем, когда вы просматриваете длинный список изображений, нет необходимости загружать все изображения в отдельные участки памяти. Вы можете просто выделить максимальное количество растровых изображений, которые будут видны, а затем повторно использовать их память, тем самым избегая этих ужасных событий сборки мусора.
Вот результаты, показывающие улучшения:

После внесения изменений мы снова пролистали тот же список продуктов. Мы заметили, что с уже выделенными начальными 15 мегабайтами памяти было выделено всего 27 мегабайт памяти к моменту прокрутки страницы вниз (и с очень небольшим количеством событий сборки мусора).

Мы прокрутили еще несколько списков продуктов и обнаружили отсутствие выделения дополнительной памяти (магия Bitmap Pool) и, следовательно, никаких серьезных событий сборки мусора.
В конце концов, мы успешно сократили объем памяти, занимаемой растровыми изображениями, почти на 50%.
Мы должны помнить о том, что Android имеет некоторые ограничения для повторного использования растровых изображений в отношении физического размера существующих растровых изображений:
- В версиях SDK с 11 по 18 загружаемые растровые изображения и повторно используемые растровые изображения должны иметь одинаковый размер. Мы решили эту проблему, указав точный размер ImageView в нашем списке для всех версий SDK до версии 18.
- В версиях SDK новее 19 существующие растровые изображения, которые мы хотим использовать, могут быть больше или равны размерам нового входящего растрового изображения.
Мы также стараемся использовать тот же формат пикселей для повторного использования растровых изображений. Итак, чтобы загрузить изображение как RGB_565 растровое изображение, мы используем выделение RGB_565 растрового изображения.
Хорошая новость в том, что вам не нужно делать все это в одиночку. Уже существует несколько замечательных библиотек, таких как Glide и Fresco, которые имеют встроенные возможности для повторного использования растровой памяти. Все, что вам нужно сделать, это убедиться, что ваши растровые изображения можно использовать повторно. (Помните, что существуют ограничения относительно размера растрового изображения и Bitmap.Config.) Если вы не хотите их использовать, вы можете просто подключить Bitmap Pool к существующему загрузчику изображений. Использование этих библиотек также поможет вам сэкономить драгоценную память за счет предварительного масштабирования растровых изображений и многого другого.
Наряду с этим мы также начали использовать формат RGB_565, который занимает всего 16 бит на пиксель - по сравнению с ARGB_8888, который занимает 32 бита на пиксель - на устройствах с низким объемом памяти. Это еще больше сокращает объем нашей памяти.
Есть много замечательных вещей, которые вы можете сделать, чтобы повысить производительность вашего приложения. Буду продолжать размещать их. Давайте создавать лучшие приложения.
Если вам понравилось читать эту статью, это будет много значить, если вы порекомендуете ее, используя значок ❤, и поделитесь ею со своими коллегами и друзьями. Спасибо!
Также давайте подключимся к Facebook, Twitter, Linkedin и Github.