понедельник, 10 ноября 2014 г.

Устранение утечки памяти при работе с ImageView в Android

Привет всем) Сегодня я снова попытаюсь написать пост, который не просто выражает мои мысли, а и может быть кому-то полезен. Утечка памяти при работе с ImageView в Android - проблема с которой сам столкнулся и которую мне пришлось решать.

Кто не хочет читать весь пост может сразу листать страницу до подзаголовка "Освобождение памяти".



Предыстория

После того как в новом обновлении для моей любимой IDE появился Memory Monitor я впервые задумался о производительности кода моей игры

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

Обнаружение


На Memory Monitor можно было наблюдать, как график ползет вверх при любых манипуляциях с меню игры. После нескольких переходов по экранам (Главное меню\Меню выбора уровня\магазин) график выглядел примерно так:


Небольшой провал графика в центре, это результат работы сборщика мусора, который я принудительно вызвал из IDE.

Первым делом, я порядок действий во фрагментах для экрана магазина и экрана выбора уровня (картинки кликабельны):


Напомню, что onCreate() вызывается один раз при создании фрагмента, а метод onCreateView() вызывается один раз, когда фрагмент должен загрузить на экран свой интерфейс. Опытным путем, было установлено, что onCreateView() вызывается даже когда я возвращался на предыдущий фрагмент с помощью кнопки Back. Собственно по этому и перенес, зачем загружать данные игры из файла или создавать LevelsAdapter каждый раз!

Значительных результатов мои манипуляции не принести и я продолжил искать дальше! 

Обнаружение с помощью Memory Analyzer (MAT)

Гугл подсказал, что есть приложение которое помогает детально посмотреть содержимое памяти Android. И вот MAT скачан по первой же ссылке, пациент привязан к операционному столу телефон подключен  и скальп снят дамп памяти уже выгружен на PC.

Сразу при запуске MAT, он предложил найти утечки памяти и сразу же нашел несколько:


Ага! Вот и первая крупная зацепка! У меня ведь при каждой смене экрана меняется картинка на заднем плане, которая подгружается из ресурсов! Для стопроцентной верности я временно убрал это из игры и снова взглянул на Memory Monitor! Мои догадки были наглядно подтверждены!

Стоит заметить, что Bitmap на каждый пиксель тратит в общем случае 2 или 4 байта (зависит от битности изображения – 16 бит RGB_555 или 32 бита ARGB_888). Можно посчитать, сколько тратится ресурсов на Bitmap, содержащий изображение, снятое на 5-мегапиксельную камеру.

Освобождение памяти

После того как изображение уже не используется, его нужно переработать. Для этого стоит просто вызвать метод recycle().  Соответственно чтобы очистить ImageView я воспользовался методом из книги "Программирование под Android":


На входе ImageView, a на выходе чистая память! Для верности я еще вызываю сборщик мусора после очистки изображения:


Но знать как освободить память еще не достаточно, нужно делать это в правильный момент! И самый подходящий момент для этого, время когда ImageView уже не виден на экране:



Нюанс при работе с изображениями из ресурсов

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

Если я пытался второй раз использовать изображение(после recycle()) по ловил исключение:

AndroidRuntime(16736): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@428e5450
Чтобы такое больше не происходило, я подсмотрел интересное решение в гугле: в ImageView нужно использовать не оригинал из ресурсов, а копию изображения, и тогда очистка в onStop() будет происходить не оригинального изображение из ресурсов, а копии:



где getNextImage(res) возвращает Drawable из ресурсов.

Результаты оптимизации

Давайте теперь посмотрим на график использования памяти:


Вот эти маленькие провалы это моменты перехода между фрагментами. Как как видно из графика результат на лицо. 

И напоследок еще один график:


Два момента когда график держится на уровне ~4 Мб была запущена игра! Из этого следует, что моя игра во время "игры" занимает совсем мало памяти! Что не может меня не радовать) 
P.S. Я не утверждаю, что мое решение уникально! Я сам собрал его из нескольких источников. Но если оно кому-то еще поможет, это будет очень круто) 




Комментариев нет:

Отправить комментарий