Как автор библиотеки, вы должны обеспечить, чтобы разработчики приложений могли легко интегрировать вашу библиотеку в свои приложения, сохраняя при этом высокое качество работы конечных пользователей. Это означает, что ваша библиотека должна быть совместима с оптимизацией Android (R8) без необходимости дополнительной настройки со стороны разработчика. В противном случае в документации должно быть указано, что библиотека может быть непригодна для использования на Android. Библиотеки, предназначенные для использования в Android, не должны препятствовать важным оптимизациям приложений и должны соответствовать дополнительным требованиям к оптимизации.
Эта документация предназначена для разработчиков опубликованных библиотек, но может быть полезна и для разработчиков внутренних модулей библиотек в больших модульных приложениях.
Если вы разработчик и хотите узнать, как оптимизировать приложение для Android, ознакомьтесь со статьей Как включить оптимизацию приложения. Чтобы узнать, какие библиотеки подходят для использования, ознакомьтесь с разделом Выбирайте библиотеки с умом.
Типы правил хранения
В библиотеках можно использовать два типа правил хранения:
- Правила сохранения потребителей должны указывать правила, которые сохраняют все, что отражает библиотека. Если библиотека использует рефлексию или JNI для вызова своего кода или кода, определенного клиентским приложением, в этих правилах должно быть указано, какой код нужно сохранить. Библиотеки должны упаковывать правила сохранения для потребителей, которые используют тот же формат, что и правила сохранения приложений. Эти правила упаковываются в артефакты библиотеки (AAR или JAR) и автоматически используются во время оптимизации приложения Android, когда используется библиотека. Эти правила хранятся в файле, указанном в свойстве
consumerProguardFilesфайлаbuild.gradle.kts(илиbuild.gradle). Подробнее о том, как создавать правила хранения для потребителей… - Правила сохранения сборки библиотеки применяются при сборке библиотеки. Они нужны, только если вы решите частично оптимизировать библиотеку во время сборки. Они должны следить за тем, чтобы общедоступный API библиотеки не был удален, иначе он не будет включен в дистрибутив библиотеки, а значит, разработчики приложений не смогут использовать ее. Эти правила хранятся в файле, указанном в свойстве
proguardFilesв файлеbuild.gradle.kts(илиbuild.gradle). Подробнее о том, как оптимизировать сборку библиотеки AAR…
Требования и рекомендации по оптимизации
Конфигурация R8 в библиотеках влияет на размер и производительность конечного двоичного файла приложения. Помимо общих рекомендаций по правилам сохранения, авторы библиотек должны соблюдать определенные требования и учитывать дополнительные рекомендации.
Соблюдайте требования к оптимизации
Неэффективность библиотек – один из основных факторов раздувания приложений, нерационального использования памяти, медленного запуска и ошибок ANR ("Приложение не отвечает"). Чтобы не снижать качество приложений и удобство их использования, библиотеки должны соблюдать следующие требования.
Не используйте правила сохранения, которые применяются ко всей библиотеке или пакету. В вашей библиотеке не должно быть правил, которые сохраняют большую часть кода в ней или в другой библиотеке. Широкие правила сохранения могут временно устранить сбои, но при этом увеличивают размер всех приложений, использующих вашу библиотеку.
Не добавляйте правила сохранения для пакетов (например,
-keep class com.mylibrary.** {*; }) в пакеты в вашей библиотеке или других библиотеках, на которые вы ссылаетесь. Такие правила ограничивают оптимизацию этих пакетов во всех приложениях, использующих вашу библиотеку.Не используйте недопустимые глобальные правила. Никогда не используйте глобальные параметры, такие как
-dontobfuscateили-allowaccessmodification.По возможности используйте генерацию кода вместо рефлексии. Генерация кода (codegen) предпочтительнее рефлексии. Генерация кода и рефлексия – это распространенные подходы, позволяющие избежать шаблонного кода при программировании, но генерация кода лучше совместима с оптимизатором приложений, таким как R8.
При использовании генерации кода он анализируется и изменяется в процессе сборки. Поскольку после компиляции в код не вносятся значительные изменения, оптимизатор знает, какой код нужен, а какой можно удалить.
Рефлексия позволяет анализировать код и манипулировать им во время выполнения. Поскольку код не является окончательным до его выполнения, оптимизатор не знает, какой код можно безопасно удалить. Скорее всего, будет удален код, который используется динамически с помощью рефлексии во время выполнения. Это приведет к сбоям в работе приложения.
Многие современные библиотеки используют генерацию кода вместо рефлексии. KSP – распространенная точка входа, используемая Room, Hilt и многими другими инструментами.
Поддержка полного режима R8. При включении полного режима R8 библиотека не должна аварийно завершать работу. Полный режим R8 рекомендуется для использования и является режимом по умолчанию в AGP 8.0, который стал стабильным в 2023 году. Если библиотека аварийно завершает работу при использовании R8, нужно определить конкретную точку входа рефлексии или JNI и добавить целевое правило, а не сохранять весь пакет.
Рекомендации
Помимо требований к оптимизации, существуют дополнительные рекомендации.
- Не используйте
-repackageclassesв файле правил хранения для потребителей библиотеки. Однако для оптимизации сборки библиотеки вы можете использовать-repackageclassesс внутренним названием пакета, например<your.library.package>.internal, в файле правил сохранения сборки библиотеки. Это может повысить эффективность библиотеки в неоптимизированных приложениях. Однако обычно в этом нет необходимости, поскольку приложения также должны быть оптимизированы. - Объявите все атрибуты, необходимые для работы библиотеки, в файлах правил сохранения библиотеки, даже если они могут пересекаться с атрибутами, определенными в
proguard-android-optimize.txt. - Если вам нужны следующие атрибуты в дистрибутиве библиотеки, сохраните их в файле правил keep для сборки библиотеки, а не в файле правил keep для потребителя библиотеки:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Авторы библиотек должны сохранять атрибут
RuntimeVisibleAnnotationsв правилах сохранения для потребителей, если аннотации используются во время выполнения. - Авторам библиотек не следует использовать следующие глобальные параметры в правилах сохранения потребителя:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Когда отражение допустимо
Если вам необходимо использовать рефлексию, то только в следующих случаях:
- Определенные целевые типы (определенные исполнители интерфейса или подклассы)
- Код, использующий определенную аннотацию среды выполнения
Такое использование рефлексии ограничивает затраты на выполнение и позволяет писать целевые правила сохранения для потребителей.
Такая узконаправленная рефлексия используется как в Android Framework (например, при развертывании действий, представлений и графических объектов), так и в библиотеках AndroidX (например, при создании WorkManager
ListenableWorkers или RoomDatabases). В то же время Gson не подходит для приложений Android.
Распространенные недопонимания
Несколько распространенных заблуждений могут привести к неправильной настройке R8. К ним относятся:
Неправильное понимание оптимизаций R8. Вопреки распространенному мнению, оптимизации R8 не ограничиваются обфускацией, а также включают сжатие кода и логические оптимизации с использованием встраивания методов и объединения классов. Подробнее об оптимизации R8…
Пропуск оптимизации обфусцированных библиотек. Распространенная ошибка – исключение библиотеки из оптимизации, поскольку она была оптимизирована или обфусцирована при компиляции в AAR (Android ARchive) или JAR (Java ARchive). Оптимизация во время сборки библиотеки ограничена, и ваше приложение не должно отключать оптимизацию библиотеки, включая ее в правило сохранения. Подробнее о том, как оптимизировать сборку библиотеки AAR…
Неправильное понимание правила
-keep. Правило-keepзапрещает R8 выполнять оптимизацию. Подробнее о том, как выбрать подходящий вариант хранения…
Как проверить качество правил хранения для потребителей
Чтобы убедиться, что библиотека не препятствует оптимизации кода при встраивании в приложение, используйте образец библиотеки или приложение для интеграционного тестирования. Включите R8 и проверьте качество конфигурации R8 приложения с помощью анализатора конфигурации R8.
Если правила для потребителей ограничивают оптимизацию значительной части кода библиотеки, это означает, что их нужно доработать. Сузьте правила для потребителей и протестируйте все затронутые аспекты библиотеки в приложении с включенным R8.
Настройте упаковку правил
Чтобы правила хранения контента применялись корректно, их нужно правильно упаковать в зависимости от формата библиотеки.
Если вы используете Jetpack Keep, чтобы объявить об использовании отражения и JNI в библиотеке, плагин Gradle может упаковать сгенерированные правила как правила сохранения для потребителя.
Библиотеки AAR
Чтобы добавить правила для потребителей в библиотеку AAR, используйте параметр consumerProguardFiles в скрипте сборки модуля библиотеки Android. Подробнее о создании модулей библиотеки…
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Яркий
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
Библиотеки JAR
Чтобы добавить правила в библиотеку Kotlin или Java, которая поставляется в виде JAR-файла, поместите файл правил в каталог META-INF/proguard/ конечного JAR-файла. Название файла может быть любым.
Например, если ваш код находится в <libraryroot>/src/main/kotlin, поместите файл правил потребителя в <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro, и правила будут добавлены в нужное место в выходном JAR-файле.
Проверьте, правильно ли упакованы правила в конечные JAR-файлы. Они должны находиться в каталоге META-INF/proguard.
Как оптимизировать создание библиотеки AAR (для продвинутых пользователей)
Как правило, оптимизировать сборку библиотеки напрямую не нужно, поскольку возможные оптимизации во время сборки библиотеки очень ограничены. Как разработчику библиотеки, вам нужно учитывать несколько этапов оптимизации и сохранять поведение как во время сборки библиотеки, так и во время сборки приложения, прежде чем оптимизировать эту библиотеку.
Если вы хотите оптимизировать библиотеку во время сборки, это можно сделать с помощью плагина Android Gradle.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Яркий
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
Обратите внимание, что поведение proguardFiles сильно отличается от consumerProguardFiles:
proguardFilesиспользуются во время сборки, часто вместе сgetDefaultProguardFile("proguard-android-optimize.txt"), чтобы определить, какая часть библиотеки должна быть сохранена во время сборки библиотеки. Как минимум, это ваш общедоступный API.consumerProguardFiles, напротив, упаковываются в библиотеку, чтобы влиять на оптимизацию, которая происходит позже, во время сборки приложения, использующего вашу библиотеку.
Например, если ваша библиотека использует отражение для создания внутренних классов, вам может понадобиться определить правила сохранения как в proguardFiles, так и в consumerProguardFiles.
Если вы используете -repackageclasses в сборке библиотеки, переупакуйте классы в подпакет внутри пакета библиотеки. Пример: используйте -repackageclasses
'com.example.mylibrary.internal' вместо -repackageclasses 'internal'.
Поддержка разных версий R8 (для продвинутых пользователей)
Вы можете настроить правила для определенных версий R8. Это позволит вашей библиотеке оптимально работать в проектах, использующих более новые версии R8, и при этом использовать существующие правила в проектах с более старыми версиями R8.
Чтобы указать целевые правила R8, добавьте их в каталог META-INF/com.android.tools внутри classes.jar файла AAR или в каталог META-INF/com.android.tools файла JAR.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
В каталоге META-INF/com.android.tools может быть несколько подкаталогов с названиями в формате r8-from-<X>-upto-<Y>, указывающими, для каких версий R8 написаны правила. В каждом подкаталоге может быть один или несколько файлов с правилами R8. Названия и расширения файлов могут быть любыми.
Обратите внимание, что части -from-<X> и -upto-<Y> являются необязательными, версия <Y> является эксклюзивной, а диапазоны версий обычно непрерывны, но могут и пересекаться.
Например, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 и r8-from-8.2.0 – это названия каталогов, представляющие набор целевых правил R8. Правила в каталоге r8 могут использоваться в любых версиях R8. Правила из каталога r8-from-8.0.0-upto-8.2.0 можно использовать в R8 версии 8.0.0 и более поздних, но не 8.2.0.
Плагин Android Gradle использует эту информацию, чтобы выбрать все правила, которые могут использоваться текущей версией R8. Если в библиотеке не указаны целевые правила R8, плагин Android Gradle выберет правила из устаревших местоположений (proguard.txt для AAR или META-INF/proguard/<ProGuard-rule-files> для JAR).