- PVSM.RU - https://www.pvsm.ru -
/ PxHere [1] / PD
Оптимизация веса APK — это нетривиальная, но очень актуальная во времена Instant App, задача. Включение proguard избавит вас от ненужного кода, если ваши зависимости можно определить на этапе компиляции, но в APK есть ещё несколько видов файлов, которые можно исключить из сборки.
Под катом о том, как сделать зависимости — определяемыми на этапе компиляции, какие файлы можно исключить из сборки и как это сделать, а так же, разберём, как исключить из сборки неиспользуемые компоненты, если у вас несколько приложений с общей кодовой базой.
Вес нашего APK, оптимизированного по гайду от Google [2] составлял 4.4 мб
.
Начнём с простого. Если вы не используете kotlin-reflect [3], то можете исключить из сборки мета-информацию о kotlin-классах. Сделать это можно следующим образом:
В build.gradle (Module: app)
android {
packagingOptions {
exclude("META-INF/*.kotlin_module")
exclude("**.kotlin_builtins")
exclude("**.kotlin_metadata")
}
}
Для Java-рефлексии не нужны файлы *.kotlin_module
, *.kotlin_builtins
и *.kotlin_metadata
. Определить, какую рефлексию вы используете, очень просто. Если вы пишите obj::class.<method>
, то вы используете kotlin-рефлекцию, если же obj::class.java.<method>
, то java-рефлексию.
Итог оптимизации для нас: -602.1 кб
Иногда библиотеки тянут за собой зависимости для случаев, которые никогда не произойдут в вашем приложении. Например, ktor-client [4] тянет вместе с собой kotlin-reflect [3] (0.5 мб!).
Я боролся с такими случаями следующим образом: собирал APK с minifyEnabled = true
, закидывал его в анализатор Android Studio, загружал mapping.txt
и искал пакеты, которые, по идее, не должны присутствовать в сборке. Например, kotlin.reflect
. После запускал ./gradlew app:dependencies
в папке проекта для поиска зависимостей (не забудьте увеличить длину истории в терминале. Дерево зависимостей может быть большим!). По этому дереву легко понять, что ссылается на лишние зависимости и исключить их. В build.gradle
вашего модуля:
dependencies {
implementation("io.ktor:ktor-client-core:$ktorVersion") {
exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect")
}
implementation("io.ktor:ktor-client-okhttp:$ktorVersion") {
exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect")
}
}
Этот код убирает зависимость библиотеки ktor-client [4] на kotlin-reflect [3]. Если хотите исключить что-то другое — подставьте свои значения.
!!! Очень осторожно пользуйтесь этим советом! Перед исключением зависимостей, убедитесь, что вам они не нужны. Если вы этого не сделаете, то приложение может начать падать в продакшене !!!
Итог оптимизации для нас: -500.3 кб
К сожалению, proguard не удаляет лишние файлы разметки на языке XML из папки layout. Неиспользуемые XML могут использовать "тяжёлые" виджеты и proguard не сможет исключить из сборки их тоже! Чтобы избежать такого, удалите неиспользуемые ресурсы с помощью Refactor -> Remove unused resources...
Если вы, как и мы, используете runtime DI, то проверьте, нет ли у вас provider'ов для тех зависимостей, которые вы не используете. Proguard не может исключить их из сборки потому что они не являются неиспользуемыми с точки зрения компилятора. Вы используете их при построении графа зависимостей.
Инструменты отладки могут занимать неожиданно много места. Например, stetho
весит около 0.2 мб
после сжатия! В любом случае, лучше исключить из релизной сборки всю отладочную инфраструктуру, чтобы никто не смог узнать о вашем приложении слишком много, просто скачав его из Google Play.
Вы можете сделать так, чтобы для дебага и для релиза использовались разные версии одних и тех же файлов. Для этого в папке src
, рядом с main
, создайте папки debug
и release
. Теперь вы можете написать функцию initStetho
, которая инициализирует Stetho в файле src/debug/java/your/pkg/Stetho.kt
и функцию initStetho
, которая не делает ничего, в файле src/release/java/your/pkg/Stetho.kt
.
На всякий случай, сделайте так, чтобы эта зависимость включалась только в дебаговые сборки. Сделать это можно, заменив implementation
на debugImplementation
в build.gradle
. Чаще всего, proguard исключает ненужные файлы даже без этого шага, но не всегда. Ответ на вопрос "почему?" ниже в тексте статьи [5].
Иногда на одной кодовой базе выпускаются несколько разных версий приложения. Это могут быть разные версии для разных стран или регионов, или, как в нашем, случае, для разных клиентов. Ниже советы о том, как разгрузить платформу.
/ PxHere [6] / PD
Мы разрабатываем конструктор мобильных приложений E-SHOP [7]. У нас несколько десятков клиентов и у каждого свой индивидуальный набор компонентов. Некоторые компоненты используются всеми клиентами, некоторые — только частью. Наша задача — включить в сборку клиента только те компоненты, которые ему нужны.
Для каждого клиента мы создаём отдельный productFlavor [8]. Это удобно, потому что легко сделать разные ресурсы для разных клиентов, IDE предоставляет графический интерфейс для переключения между flavor'ами, и хорошо работают кэши. А ещё можно генерировать для каждого клиента свой BuildConfig.java
. Значения полей этого класса известны на этапе компиляции. Это то, что нам нужно! Создаём поле типа boolean
для каждого компонента.
android {
productFlavors {
client1 {
buildConfigField("boolean", "IS_CATALOG_ENABLED", "true")
}
client2 {
buildConfigField("boolean", "IS_CATALOG_ENABLED", "false")
}
}
}
Это — упрощённая версия конфигурации. Настоящая сложна из-за интеграции с нашим CI.
Теперь известно, активен ли компонент, на этапе компиляции, и proguard может исключить его из сборки!
Теперь проблема с неиспользуемыми XML-layouts приобретает новый масштаб! Нельзя просто взять и удалить разметку какого-нибудь компонента просто потому, что некоторым клиентам он не нужен.
В нашем приложении в XML одного из редкоиспользуемых компонентов, использовался виджет, который ссылался на библиотеку распознавания изображений firebase.ml.vision
. Она весит около 0.2 мб, что немало. Было принято решение добавлять этот виджет кодом вместо того, чтобы объявлять его в разметке. После этого proguard смог исключить vision
из сборки для клиентов, которым он не нужен.
Итог оптимизации для нас: -222.3 кб для среднего APK
@Keep
Есть 2 способа сказать proguard, что ваш класс нельзя минифицировать: написать правило в файле proguard-rules.pro
или поставить аннотацию @Keep
. В библиотеке play-services-vision
на корневом классе стоит именно эта аннотация. Поэтому 0.2 мб висело мёртвым грузом даже в тех приложениях клиентов, которым не нужно распознавание изображений.
Я не нашёл простого и безопасного способа убрать эту аннотацию. Если вы знаете, как — напишите, пожалуйста, в комментариях.
К счастью, библиотека firebase.ml.vision
, которая является более новой версией play-services-vision
, не использует эту аннотацию и мы решили проблему, перейдя на неё.
Последний, но не по значимости пункт. DI при отключаемых компонентах. Тут всё просто: для каждого компонента мы используем свой контейнер, а общие зависимости подключаем через отдельный модуль.
Итог оптимизации для нас: -20.1 кб для среднего APK
4.4 мб
до 3.1 мб
, а минимального — до 2.5 мб
!Все оптимизации, представленные в статье — это "низковисящие фрукты". Их довольно легко внедрить и быстро получить результат. До -43% для уже оптимизированного APK в нашем случае. Надеюсь, я сэкономил ваше время тем, что перечислил всё в одном месте.
Автор: Napoleon IT
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android/318248
Ссылки в тексте:
[1] PxHere: https://pxhere.com/ru/photo/1087326
[2] гайду от Google: https://developer.android.com/topic/performance/reduce-apk-size
[3] kotlin-reflect: https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
[4] ktor-client: https://ktor.io/clients/index.html
[5] Ответ на вопрос "почему?" ниже в тексте статьи: #annotaciya-keep
[6] PxHere: https://pxhere.com/en/photo/349190
[7] E-SHOP: https://shop.napoleonit.ru/
[8] productFlavor: https://developer.android.com/studio/build/build-variants#product-flavors
[9] Источник: https://habr.com/ru/post/452524/?utm_source=habrahabr&utm_medium=rss&utm_campaign=452524
Нажмите здесь для печати.