Приложение Android В Памяти. Отчет По Оптимизации Для Яндекс.лаунчера

Облегченная система Android Go предъявляет более высокие требования к предустановленным приложениям, таким как размер и использование памяти.

Перед нами стояла задача удовлетворить эти требования.

Мы провели ряд оптимизаций и решили серьёзно изменить архитектуру нашей графической оболочки — Яндекс.

Лаунчера.

Таким опытом поделился руководитель группы разработки приложений для мобильных решений Александр Старченко.

— Меня зовут Александр, я из Санкт-Петербурга, из команды, занимающейся разработкой Яндекс.

Лаунчера и Яндекс.

Телефона.

Сегодня я расскажу вам, как мы оптимизировали память в Лаунчере.

Сначала я кратко объясню, что такое Launcher. Далее мы обсудим причины, по которым нам необходимо оптимизировать память.

После этого мы рассмотрим, как правильно измерять память и из чего она состоит. Тогда перейдем к практике.

Расскажу, как мы оптимизировали память в Лаунчере и как пришли к радикальному решению вопроса.

И напоследок я расскажу о том, как мы следим за использованием памяти, как держим ее под контролем.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

«Лаунчер» или «Лаунчер» не столь важно.

Мы в Яндексе привыкли называть его Лаунчером, а в отчете я буду использовать слово «Лаунчер».



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Еще один важный момент: Лаунчер достаточно широко распространяется через предустановки, то есть, когда вы покупаете новый телефон, Яндекс.

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

Теперь о причинах, по которым нам необходимо оптимизировать память.

Начнем с нашей причины.

В двух словах, это Android Go. А теперь это длиннее.

В конце 2017 года Google представила Android Oreo и его специальную версию — Android Oreo Go edition. Почему это особенное? Эта версия предназначена для бюджетных, недорогих телефонов с оперативной памятью до одного гигабайта.

Что еще делает ее особенной? К приложениям, предустановленным в этой версии Android, Google предъявляет дополнительные требования.

В частности, требования к потреблению оперативной памяти.

Грубо говоря, память приложения удаляется на некоторое время после запуска, а размер для Лаунчера не должен превышать 30–50 мегабайт в зависимости от размера экрана телефона.

30 на самых маленьких, 50 на больших экранах.

Также следует отметить, что Google продолжает развивать это направление, и уже существует версия Android Pie Go. Какие еще могут быть причины для оптимизации использования памяти? Во-первых, ваше приложение будет реже выгружаться.

Во-вторых, это будет работать быстрее, так как сборщик мусора будет работать реже и память будет выделяться реже.

Не будут создаваться лишние объекты, не будут раздуваться лишние представления и т. д. Косвенно, судя по нашему опыту, это приведет к уменьшению размера apk вашего приложения.

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

Давайте разберемся, как его измерить и из чего он состоит.

Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера



Ссылка со слайда
Наверное, многие из вас видели эту картинку.

Это снимок экрана из профиля Android Studio из просмотра памяти.

Этот инструмент подробно описан на сайте Developer.android.com. Вероятно, многие из вас ими пользовались.

Кто еще не пользовался, попробуйте.

Что здесь хорошего? Оно всегда под рукой.

Удобно использовать в процессе разработки.

Однако у него есть некоторые недостатки.

Здесь видны не все выделения вашего приложения.

Например, здесь не видны загруженные шрифты.

Также неудобно использовать этот инструмент для просмотра того, какие классы загружены в память, и вы не сможете использовать этот инструмент в автоматическом режиме, то есть вы не сможете настроить какой-то автоматический тест на основе профиля Android Studio.

Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера



Ссылки со слайда: первый , второй
Следующий инструмент существует еще со времен разработки Android в Eclipse, это Memory Analyzer, короче MAT. Оно предоставляется как отдельное приложение и совместимо с дампами памяти, которые можно сохранить из Android Studio. Для этого вам понадобится небольшая утилита, профессиональный конвертер.

Он поставляется с версией Android Go и имеет ряд преимуществ.

Например, он может строить пути к корням gs. Нам очень помогло увидеть, какие именно классы загружаются программой запуска и когда они загружаются.

Мы не смогли сделать это с помощью Android Studio Profiler.

Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Следующий инструмент — утилита dumpsys, а именно dumpsys meminfo. Здесь вы можете увидеть часть вывода этой команды.

Он обеспечивает довольно высокий уровень знаний о потреблении памяти.

Однако у него есть и определенные преимущества.

Удобно использовать его в автоматическом режиме.

Вы можете легко настроить тесты, которые просто вызывают эту команду.

Он также показывает память для всех процессов одновременно.

И показывает все локации.

Насколько нам известно, Google во время тестирования использует значение памяти из этого инструмента.

Позвольте мне использовать пример вывода, чтобы кратко объяснить, из чего состоит память приложения.

Первый — это Java Heap, все места вашего кода Java и Kotlin. Обычно этот раздел довольно большой.

Далее идет Native Heap. Вот выделения из собственного кода.

Даже если вы не используете явно нативный код в своем приложении, выделения здесь будут присутствовать, поскольку многие объекты Android — одни и те же представления — выделяют нативную память.

Следующий раздел — «Код».

Сюда входит все, что связано с кодом: байт-код, шрифты.

Код также может быть довольно большим, если вы используете много сторонних неоптимизированных библиотек.

Ниже приведен программный стек Java и собственного кода, обычно небольшого размера.

Далее идет графическая память.

Сюда входят текстуры поверхности, то есть для рендеринга используется память, находящаяся между процессором и графическим процессором.

Далее идет раздел «Частное другое».

Сюда входит все, что не попало в перечисленные разделы, все, что система не смогла разбросать по ним.

Обычно это какие-то нативные аллокации.

Далее идет раздел Система, это часть системной памяти, которая отведена вашему приложению.

И в конце у нас ИТОГО, это сумма всех перечисленных разделов.

Мы хотим уменьшить его.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Что еще важно знать об измерении памяти? Прежде всего, наше приложение не полностью контролирует все распределения.

То есть мы как разработчики не имеем полного контроля над тем, какой код будет загружен.

Следующий.

Память приложений может сильно подскочить.

В процессе измерения вы можете наблюдать большие различия в показаниях.

Это может быть связано как со сроками проведения чтений, так и с различными сценариями развития событий.

В связи с этим, когда мы оптимизируем память и анализируем ее, очень важно делать это в одинаковых условиях.

В идеале на одном устройстве.

Еще лучше, если у вас есть возможность вызвать Сборщика мусора.

Большой.

Мы знаем, зачем нужно оптимизировать память, как ее правильно измерять и из чего она состоит. Приступим к практике и я расскажу вам, как мы оптимизировали память в Лаунчере.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Так было сначала.

У нас было три процесса, которые в сумме выделили около 120 мегабайт. Это почти в четыре раза больше, чем нам хотелось бы получить.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Что касается выделения основного процесса, то там был большой раздел Java Heap, много графики, много кода и довольно большой Native Heap.

Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Поначалу мы подошли к решению проблемы довольно наивно и решили последовать рекомендациям Google с некоторых ресурсов и постараться решить проблему быстро.

Мы уделили внимание синтетическим методам, которые генерируются в процессе компиляции.

У нас их было более 2 тысяч.

За пару часов мы удалили их все и удалили память.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

И мы получили прирост где-то один-два мегабайта на участке кода.

Большой.

Далее мы обратили внимание на enum. Как вы знаете, enum — это класс.

И, как в конце концов признал Google, перечисления не очень эффективно используют память.

Мы преобразовали все перечисления в InDef и StringDef. Здесь вы можете мне возразить, что ProgArt здесь поможет. Но на самом деле ProgArt не заменит все перечисления примитивными типами.

Лучше сделать это самостоятельно.

Кстати, нашего перечисления было больше 90, довольно много.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Эта оптимизация уже заняла дни, так как большую часть ее приходилось делать вручную, и мы выиграли примерно три-шесть мегабайт на участке кучи Java. Далее мы обратили внимание на коллекции.

Мы использовали довольно стандартные коллекции Java, такие как HashMap. Их у нас было более 150, и все они были созданы при запуске Лаунчера.

Мы заменили их на SparseArray, SimpleArrayMap и ArrayMap и начали создавать коллекции с заранее известным размером, чтобы не выделялись пустые слоты.

То есть мы передаем размер коллекции конструктору.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Это тоже давало определенную выгоду, да и на эту оптимизацию у нас уходили дни, большую часть которых мы делали вручную.

Тогда мы предприняли более конкретный шаг.

Мы увидели, что у нас есть три процесса.

Как мы знаем, даже пустой процесс в Android занимает около 8–10 мегабайт памяти, довольно много.

Подробно о процессах рассказал мой коллега Артур Василов.

Не так давно на конференции Мосдроид был его отчет , а также об Android Go.

Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Что мы получили после этих оптимизаций? На основном тестовом устройстве мы наблюдали потребление памяти в районе 80–100 мегабайт, что неплохо, но все же недостаточно.

Мы начали измерять память на других устройствах.

Мы обнаружили, что потребление памяти было намного выше на более быстрых устройствах.

Оказалось, что у нас было много разных отложенных инициализаций.

Через некоторое время Лаунчер надул некоторые представления, инициировал некоторые библиотеки и т.д.

Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Что мы наделали? В первую очередь мы прошли вид, все планировки.

Удалены все виды, которые были раздуты, а видимость исчезла.

Мы добавили их в отдельные макеты и начали программно надувать.

Мы перестали надувать те, которые нам не нужны, до того момента, пока они не понадобятся пользователю.

Мы уделили внимание оптимизации изображений.

Мы прекратили загрузку изображений, которые пользователь в настоящее время не может видеть.

В случае с Лаунчером это были картинки иконок приложений в полном списке приложений.

Не загружаем их, пока не откроется.

Это дало нам очень хороший прирост в графическом разделе.

Мы также проверили кэши изображений в памяти.

Оказалось, что не все из них оптимальны; они не сохранили в памяти все картинки, соответствующие экрану телефона, на котором был запущен Лаунчер.

После этого мы начали анализировать участок кода и заметили, что откуда-то у нас появилось много довольно тяжелых классов.

Оказалось, что это в основном библиотечные занятия.

В некоторых библиотеках мы нашли довольно странные вещи.

Одна из библиотек создала HashMap и заполнила его довольно большим количеством объектов в статическом инициализаторе.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Другая библиотека также загружала аудиофайлы статическим блоком, занимавшим около 700 килобайт памяти.



Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

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

Все вышеперечисленные оптимизации заняли несколько недель.

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

Но и прирост мы получили довольно неплохой, около 25 мегабайт из 40 в разделах Native, Heap, Code и Java Heap. Но этого было недостаточно.

Потребление памяти пока не упало до 30 мегабайт. Казалось, что мы исчерпали все возможности каких-то простых автоматических и безопасных оптимизаций.

Мы решили поискать радикальные решения.

Здесь мы увидели два варианта — создание отдельного облегченного приложения или переработка архитектуры Лаунчера и переход на модульную архитектуру с возможностью делать сборки Лаунчера без дополнительных модулей.

Первый вариант довольно долгий и дорогой.

Скорее всего, создание такого приложения выльется для вас в полноценное отдельное приложение, которое нужно будет полностью поддерживать и развивать.

С другой стороны, вариант с модульной архитектурой тоже довольно дорогой, достаточно рискованный, но все же он быстрее, поскольку вы уже работаете с известной кодовой базой, у вас уже есть набор автоматических модульных тестов, интеграционных тестов и ручные тесты-кейсы.

Следует отметить, что какой бы вариант вы ни выбрали, вам так или иначе придется отказаться от некоторых возможностей вашего приложения в версии Android Go. Это отлично.

Google делает то же самое со своими приложениями Go. В результате, реализовав модульную архитектуру, мы достаточно надёжно решили наши проблемы с памятью и начали проходить тесты даже на устройствах с маленьким экраном, то есть снизили потребление памяти до 30 мегабайт.

Приложение Android в памяти.
</p><p>
 Отчет по оптимизации для Яндекс.
</p><p>
Лаунчера

Немного о мониторинге памяти, как мы держим использование памяти под контролем.

Прежде всего мы настроили статические анализаторы, тот же Lint, на выявление ошибок в случаях, когда мы используем перечисления, создаём синтетические методы или используем неоптимизированные коллекции.

Дальше сложнее.

Мы настроили автоматические интеграционные тесты, которые запускают Лаунчер на эмуляторах и через некоторое время уменьшают потребление памяти.

Если она сильно отличается от предыдущей сборки, срабатывают предупреждения и оповещения.

Затем мы начинаем исследовать проблему и не публикуем изменения, которые увеличивают использование памяти Launcher. Подведем итоги.

Существуют различные инструменты мониторинга памяти, измерения памяти для быстрой и эффективной работы.

Лучше использовать их все, так как у них есть свои плюсы и минусы.

Радикальные решения с модульной архитектурой оказались для нас более надежными и эффективными.

Сожалеем, что не сделали их сразу.

Но те шаги, о которых я говорил в самом начале доклада, не прошли даром.

Мы заметили, что основная версия приложения стала оптимально использовать память и работать быстрее.

Спасибо.

Теги: #Разработка Android #Разработка мобильных приложений #Оптимизация клиента #Android Studio #оптимизация памяти #лаунчер #управление памятью #использование памяти #android go #dumpsys

Вместе с данным постом часто просматривают: