Как говорят в фильмах,основано на реальных событиях.Когда я работал над android проектом я столкнулся с ситуацией когда мне нужно было достичь 2-3 кратного уменьшения потребления оперативной памяти.Каждый мегабайт имел значение.
Мы начинали со 100-150Мб ОЗУ что вполне нормально для android приложения,но цель была сделать потребление меньше 70Мб дабы оно плавно запускалось на целевом девайсе.После просмотра кода,чистки переменных,оптимизации мы были в диапазоне от 110-130Мб.
Так что я решил поделиться 4 главными шагами которые помогут достичь потребления в 50-70Мб и сделать так что-бы прожористое приложение работало эффективно.
Дисклеймер - когда оптимизируешь производительность ты используешь лучшие методы и архитектурный подход как правило использование главных потоковых библиотек входит в твой "стандартный набор" :MVVM, Dagger, Retrofit, Glide, Kotlin, Jetpack libraries.
До запуска приложения :
После запуска приложения:
1-й шаг
Первый шаг это проверка на утечки памяти.Я верю что лучше всего начать оптимизацию с уверенности в отсутствии утечек памяти так как они могут уменьшить ваш объем работы по эффективному использованию памяти.
Вы можете использовать проект под названием "Leak Canary".
Посмотри эту статью
proandroiddev.com
Ты можешь пометить "подозрительные" места своего кода запустив profiler в android studio.Выполняя определенные действия в приложении, наблюдайте за поведением памяти.
Что бы дать вам конкретный пример, у нас была утечка на задней кнопке. При нажатии вперед и назад использование памяти приложением резко увеличивалось, хотя предполагалось, что оно останется примерно таким же. Будьте бдительны, если ваша память продолжает увеличиваться и никогда не возвращается к более низким числам.
Далее изучите использование графики и растровых изображений. Наиболее требовательными к памяти в мобильных приложениях обычно являются графика и изображения. Поскольку наше приложение очень насыщено изображениями, мы оптимизировали его больше всего.
Если ты используешь BitMap напрямую то главное чего стоит опасаться ошибки OutOfMemory Exception.Что-бы её предотвратить ты должен кэшировать,изменить размер,переработать BitMap'ы когда закончил их использовать.
В нашем случаи мы использовали библиотеку Glide для загрузки изображений эта библиотека гарантирует работы "из коробки". Хоть и Glide помогает оптимизировать использование изображений, нам пришлось кое-что сделать, чтобы сделать его более эффективным.
github.com
Примечание. На мой взгляд, Glide - самая эффективная библиотека для загрузки изображений. Раньше мы использовали Picasso, но Glide намного лучше. В Интернете есть статьи, которые явно демонстрируют лучшую производительность Glide.
Оптимизации Glide
Использование RGB_565 вместо ARGB_8888 для маломощных устройств. В Glide v4 формат ARGB_8888 включен по умолчанию. Это обеспечивает более гладкое изображение, особенно в случаях градиента. Но на меньшем экране это не так заметно. Использование RGB_565 позволило сэкономить память до 2-х раз на изображениях.
Чтобы отключить его, вам нужно будет создать собственный класс GlideModule, который будет генерировать класс GlideApp, а затем его нужно использовать вместо ссылки Glide по умолчанию.
Удаляйте кеш при нехватке памяти. Создайте собственный класс приложения и переопределите метод onTrimMemory.
Отмените загрузки в переработчике, если представление больше не отображаются.
Некоторые люди могут не знать, но переработчик предоставляет callback при повторном использовании представления. Если загрузка выполняется, когда просмотр был переработан, это может быть пустой тратой ваших ресурсов. Мы можем очистить это так:
Уменьшите размер изображения. В качестве примера мы можем использовать что-то подобное для изменения размера изображения:
Про оптимизации Glide можете посмотреть тут:
proandroiddev.com
Проверьте ваши Room операции.Использование базы данных Room предоставляет разработчикам полезную функцию возврата LiveData, которую вы можете наблюдать и получать уведомления об изменениях в вашей таблице. Однако, присмотревшись к нему, мы обнаружили, что экземпляр LiveData получает уведомления чаще, чем ожидалось. Были ложные срабатывания уведомлений для наблюдаемых запросов. Google предлагает решение в виде функции расширения (что кажется ошибкой в LiveData), в любом случае после использования расширения мы увидели заметное улучшение производительности, поскольку мы широко используем Room для локального варианта использования корзины покупок.
developer.android.com
Вместо прямого использования LiveData вот так:
Мы используем расширение для фильтрации ложных срабатываний уведомлений:
getDistinct() выглядит так:
Шаг 4
Проверка жизненного цикла и навигации фрагмента. Этот шаг состоит из 2 частей.
Используя новую навигационную библиотеку и шаблон единственного действия, мы поняли, что фрагменты фактически не уничтожаются, пока они находятся на графике. Сначала это кажется ошибкой библиотеки навигации, но затем после проверки похоже, что это просто поведение по умолчанию для фрагмента. (проверьте эту ветку). Так что, если он не уничтожен, то все переменные тоже не уничтожаются и, самое главное, экземпляр привязки. Это наблюдение может быть связано с общей проблемой утечки памяти, но я считаю, что оно заслуживает отдельного шага. Таким образом, после явной установки привязки на null в onDestroyView использование памяти значительно улучшилось.
И вторая часть этого шага заключалась в проверке действий навигации и обнаружении случаев создания экземпляров фрагментов, когда один уже был на графике, но недоступен. Итак, решение заключалось в том, чтобы пересмотреть всю навигацию и убедиться, что на графике нет «мертвых» фрагментов.
Еще несколько советов:
По возможности используйте определенные методы уведомления при работе с RecyclerView. Вместо вызова notifyDataSetChanged () используйте notifyItemChanged (int position)
Уменьшите количество вспомогательных классов, таких как Mappers, Verifiers, Transformers и т. Д. Создание объектов не так дорого, как раньше, но это не значит, что вам не нужно обращать внимание. Избегайте ненужных абстракций.
Уменьшите размер apk. Уменьшение размера apk поможет уменьшить объем памяти, занимаемой приложением в целом.
Не храните кучу объектов в памяти «на всякий случай» или в пулах, чтобы избежать создания объекта. Чем больше объем памяти, тем дороже запускать сборщик мусора для обхода всех этих объектов «старого поколения».
Запустите тестирование производительности в неотлаживаемой сборке, поскольку это имеет значение, и вы можете не получить правильные результаты, поскольку отлаживаемые сборки еще не оптимизированы ... пока.
Анимация требует памяти. Отрежьте анимацию, если цель - работать в среде с ограничением памяти.
Изучите официальную документацию по оптимизации мощности и производительности приложения, особенно по части управления памятью.
proandroiddev.com
Мы начинали со 100-150Мб ОЗУ что вполне нормально для android приложения,но цель была сделать потребление меньше 70Мб дабы оно плавно запускалось на целевом девайсе.После просмотра кода,чистки переменных,оптимизации мы были в диапазоне от 110-130Мб.
Так что я решил поделиться 4 главными шагами которые помогут достичь потребления в 50-70Мб и сделать так что-бы прожористое приложение работало эффективно.
Дисклеймер - когда оптимизируешь производительность ты используешь лучшие методы и архитектурный подход как правило использование главных потоковых библиотек входит в твой "стандартный набор" :MVVM, Dagger, Retrofit, Glide, Kotlin, Jetpack libraries.
До запуска приложения :
После запуска приложения:
1-й шаг
Первый шаг это проверка на утечки памяти.Я верю что лучше всего начать оптимизацию с уверенности в отсутствии утечек памяти так как они могут уменьшить ваш объем работы по эффективному использованию памяти.
Вы можете использовать проект под названием "Leak Canary".
Everything you need to know about Memory Leaks in android.
One of the core benefits of Java that they are garbage collected language. Essentially, we can create objects and the garbage collector…
proandroiddev.com
Что бы дать вам конкретный пример, у нас была утечка на задней кнопке. При нажатии вперед и назад использование памяти приложением резко увеличивалось, хотя предполагалось, что оно останется примерно таким же. Будьте бдительны, если ваша память продолжает увеличиваться и никогда не возвращается к более низким числам.
Далее изучите использование графики и растровых изображений. Наиболее требовательными к памяти в мобильных приложениях обычно являются графика и изображения. Поскольку наше приложение очень насыщено изображениями, мы оптимизировали его больше всего.
Если ты используешь BitMap напрямую то главное чего стоит опасаться ошибки OutOfMemory Exception.Что-бы её предотвратить ты должен кэшировать,изменить размер,переработать BitMap'ы когда закончил их использовать.
В нашем случаи мы использовали библиотеку Glide для загрузки изображений эта библиотека гарантирует работы "из коробки". Хоть и Glide помогает оптимизировать использование изображений, нам пришлось кое-что сделать, чтобы сделать его более эффективным.
bumptech/glide
An image loading and caching library for Android focused on smooth scrolling - bumptech/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);
How to optimize memory consumption when using Glide
For developers of the apps which a lot of images, the one thing is certain besides death and taxes: OutOfMemory errors (OOM). Facing these…
proandroiddev.com
Save data in a local database using Room | Android Developers
Learn to persist data more easily using the Room Library
Java:
fun getCartSubscription(): LiveData<List<CartItem>> = db.loadCart()
Java:
fun getCartSubscription(): LiveData<List<CartItem>> = db.loadCart().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 поможет уменьшить объем памяти, занимаемой приложением в целом.
Не храните кучу объектов в памяти «на всякий случай» или в пулах, чтобы избежать создания объекта. Чем больше объем памяти, тем дороже запускать сборщик мусора для обхода всех этих объектов «старого поколения».
Запустите тестирование производительности в неотлаживаемой сборке, поскольку это имеет значение, и вы можете не получить правильные результаты, поскольку отлаживаемые сборки еще не оптимизированы ... пока.
Анимация требует памяти. Отрежьте анимацию, если цель - работать в среде с ограничением памяти.
Изучите официальную документацию по оптимизации мощности и производительности приложения, особенно по части управления памятью.
Decrease memory usage of your Android app in half
I would like to share 4 main steps that ultimately helped get our app memory usage cut in half and some useful tips
proandroiddev.com
Последнее редактирование: