• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Уменьшение потребления памяти для Android приложения

danyrusdem

RAID-массив
Пользователь
Регистрация
24.06.2019
Сообщения
56
Реакции
61
Как говорят в фильмах,основано на реальных событиях.Когда я работал над android проектом я столкнулся с ситуацией когда мне нужно было достичь 2-3 кратного уменьшения потребления оперативной памяти.Каждый мегабайт имел значение.
Мы начинали со 100-150Мб ОЗУ что вполне нормально для android приложения,но цель была сделать потребление меньше 70Мб дабы оно плавно запускалось на целевом девайсе.После просмотра кода,чистки переменных,оптимизации мы были в диапазоне от 110-130Мб.
Так что я решил поделиться 4 главными шагами которые помогут достичь потребления в 50-70Мб и сделать так что-бы прожористое приложение работало эффективно.
Дисклеймер - когда оптимизируешь производительность ты используешь лучшие методы и архитектурный подход как правило использование главных потоковых библиотек входит в твой "стандартный набор" :MVVM, Dagger, Retrofit, Glide, Kotlin, Jetpack libraries.
До запуска приложения :
1 yAIC3z9znwtTt7X1CC511Q.png


После запуска приложения:

1 5T3f2Vq6RAgRg-2sQN3ZYA.png

1-й шаг
Первый шаг это проверка на утечки памяти.Я верю что лучше всего начать оптимизацию с уверенности в отсутствии утечек памяти так как они могут уменьшить ваш объем работы по эффективному использованию памяти.
Вы можете использовать проект под названием "Leak Canary".
Посмотри эту статью
Ты можешь пометить "подозрительные" места своего кода запустив profiler в android studio.Выполняя определенные действия в приложении, наблюдайте за поведением памяти.
Что бы дать вам конкретный пример, у нас была утечка на задней кнопке. При нажатии вперед и назад использование памяти приложением резко увеличивалось, хотя предполагалось, что оно останется примерно таким же. Будьте бдительны, если ваша память продолжает увеличиваться и никогда не возвращается к более низким числам.
Далее изучите использование графики и растровых изображений. Наиболее требовательными к памяти в мобильных приложениях обычно являются графика и изображения. Поскольку наше приложение очень насыщено изображениями, мы оптимизировали его больше всего.
Если ты используешь BitMap напрямую то главное чего стоит опасаться ошибки OutOfMemory Exception.Что-бы её предотвратить ты должен кэшировать,изменить размер,переработать BitMap'ы когда закончил их использовать.
В нашем случаи мы использовали библиотеку Glide для загрузки изображений эта библиотека гарантирует работы "из коробки". Хоть и Glide помогает оптимизировать использование изображений, нам пришлось кое-что сделать, чтобы сделать его более эффективным.
Примечание. На мой взгляд, Glide - самая эффективная библиотека для загрузки изображений. Раньше мы использовали Picasso, но Glide намного лучше. В Интернете есть статьи, которые явно демонстрируют лучшую производительность Glide.
Оптимизации Glide
Использование RGB_565 вместо ARGB_8888 для маломощных устройств. В Glide v4 формат ARGB_8888 включен по умолчанию. Это обеспечивает более гладкое изображение, особенно в случаях градиента. Но на меньшем экране это не так заметно. Использование RGB_565 позволило сэкономить память до 2-х раз на изображениях.
Чтобы отключить его, вам нужно будет создать собственный класс GlideModule, который будет генерировать класс GlideApp, а затем его нужно использовать вместо ссылки Glide по умолчанию.
Java:
//custom GlideModule class
@GlideModule
class CustomGlideModuleV4 : AppGlideModule() {

    override fun applyOptions(context: Context, builder: GlideBuilder) {
        builder.setDefaultRequestOptions(
            RequestOptions().format(DecodeFormat.PREFER_RGB_565)
        )
    }

}//sample usage
GlideApp.with(view.context)
    .load("$imgUrl$IMAGE_URL_SIZE_SPEC")
    .into(view)

Удаляйте кеш при нехватке памяти. Создайте собственный класс приложения и переопределите метод onTrimMemory.
Java:
override fun onTrimMemory(level: Int) {
    GlideApp.with(applicationContext).onTrimMemory(TRIM_MEMORY_MODERATE)
    super.onTrimMemory(level)
}
Отмените загрузки в переработчике, если представление больше не отображаются.
Некоторые люди могут не знать, но переработчик предоставляет callback при повторном использовании представления. Если загрузка выполняется, когда просмотр был переработан, это может быть пустой тратой ваших ресурсов. Мы можем очистить это так:
Java:
override fun onViewRecycled(holder: ViewHolder) {
    holder.binding?.imageItem?.let {
        GlideApp.with(it.context).clear(it)
    }
    super.onViewRecycled(holder)
}
Уменьшите размер изображения. В качестве примера мы можем использовать что-то подобное для изменения размера изображения:
Java:
Glide
    .with(context)
    .load(url)
    .apply(new RequestOptions().override(600, 200))
    .into(imageView);
Про оптимизации Glide можете посмотреть тут:
Проверьте ваши Room операции.Использование базы данных Room предоставляет разработчикам полезную функцию возврата LiveData, которую вы можете наблюдать и получать уведомления об изменениях в вашей таблице. Однако, присмотревшись к нему, мы обнаружили, что экземпляр LiveData получает уведомления чаще, чем ожидалось. Были ложные срабатывания уведомлений для наблюдаемых запросов. Google предлагает решение в виде функции расширения (что кажется ошибкой в LiveData), в любом случае после использования расширения мы увидели заметное улучшение производительности, поскольку мы широко используем Room для локального варианта использования корзины покупок.
Вместо прямого использования LiveData вот так:
Java:
fun getCartSubscription(): LiveData<List<CartItem>> = db.loadCart()
Мы используем расширение для фильтрации ложных срабатываний уведомлений:
Java:
fun getCartSubscription(): LiveData<List<CartItem>> = db.loadCart().getDistinct()
getDistinct() выглядит так:
Java:
/**
* Extension from Google to allow notifying UI only for distinct operations in Room
*/
fun <T> LiveData<T>.getDistinct(): LiveData<T> {
    val distinctLiveData = MediatorLiveData<T>()
    distinctLiveData.addSource(this, object : Observer<T> {
        private var initialized = false
        private var lastObj: T? = null
        override fun onChanged(obj: T?) {
            if (!initialized) {
                initialized = true
                lastObj = obj
                distinctLiveData.postValue(lastObj)
            } else if ((obj == null && lastObj != null)
                || obj != lastObj
            ) {
                lastObj = obj
                distinctLiveData.postValue(lastObj)
            }
        }
    })
    return distinctLiveData
}

Шаг 4
Проверка жизненного цикла и навигации фрагмента. Этот шаг состоит из 2 частей.
Используя новую навигационную библиотеку и шаблон единственного действия, мы поняли, что фрагменты фактически не уничтожаются, пока они находятся на графике. Сначала это кажется ошибкой библиотеки навигации, но затем после проверки похоже, что это просто поведение по умолчанию для фрагмента. (проверьте эту ветку). Так что, если он не уничтожен, то все переменные тоже не уничтожаются и, самое главное, экземпляр привязки. Это наблюдение может быть связано с общей проблемой утечки памяти, но я считаю, что оно заслуживает отдельного шага. Таким образом, после явной установки привязки на null в onDestroyView использование памяти значительно улучшилось.
И вторая часть этого шага заключалась в проверке действий навигации и обнаружении случаев создания экземпляров фрагментов, когда один уже был на графике, но недоступен. Итак, решение заключалось в том, чтобы пересмотреть всю навигацию и убедиться, что на графике нет «мертвых» фрагментов.

Еще несколько советов:
По возможности используйте определенные методы уведомления при работе с RecyclerView. Вместо вызова notifyDataSetChanged () используйте notifyItemChanged (int position)
Уменьшите количество вспомогательных классов, таких как Mappers, Verifiers, Transformers и т. Д. Создание объектов не так дорого, как раньше, но это не значит, что вам не нужно обращать внимание. Избегайте ненужных абстракций.
Уменьшите размер apk. Уменьшение размера apk поможет уменьшить объем памяти, занимаемой приложением в целом.
Не храните кучу объектов в памяти «на всякий случай» или в пулах, чтобы избежать создания объекта. Чем больше объем памяти, тем дороже запускать сборщик мусора для обхода всех этих объектов «старого поколения».
Запустите тестирование производительности в неотлаживаемой сборке, поскольку это имеет значение, и вы можете не получить правильные результаты, поскольку отлаживаемые сборки еще не оптимизированы ... пока.
Анимация требует памяти. Отрежьте анимацию, если цель - работать в среде с ограничением памяти.
Изучите официальную документацию по оптимизации мощности и производительности приложения, особенно по части управления памятью.
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх