Привет всем) Сегодня я снова попытаюсь написать пост, который не просто выражает мои мысли, а и может быть кому-то полезен. Утечка памяти при работе с 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. Я не утверждаю, что мое решение уникально! Я сам собрал его из нескольких источников. Но если оно кому-то еще поможет, это будет очень круто)
Комментариев нет:
Отправить комментарий