Оптимизация для авторов библиотеки

Как автор библиотеки, вы должны убедиться, что разработчики приложений смогут легко интегрировать вашу библиотеку в свои приложения, сохраняя при этом высокое качество пользовательского опыта. Убедитесь, что ваша библиотека совместима с оптимизацией для Android без дополнительной настройки, или задокументируйте, что библиотека может быть неподходящей для использования на Android.

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

Если вы разработчик приложений и хотите узнать об оптимизации своего приложения для Android, см. статью «Включение оптимизации приложения» . Чтобы узнать, какие библиотеки подходят для использования, см. статью «Разумный выбор библиотек» .

Используйте кодогенерацию вместо рефлексии

По возможности используйте генерацию кода ( codegen ), а не рефлексию. И кодогенерация, и рефлексия — распространённые подходы, позволяющие избежать шаблонного кода при программировании, но codegen более совместим с оптимизатором приложений, таким как R8:

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

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

Когда рефлексия — это нормально

Если вам необходимо использовать отражение, вы должны отражать только одно из следующих:

  • Конкретные целевые типы (конкретные реализаторы интерфейсов или подклассы)
  • Код, использующий определенную аннотацию времени выполнения

Использование отражения таким образом ограничивает затраты времени выполнения и позволяет писать целевые потребительские правила хранения .

Эта специфическая и целенаправленная форма рефлексии — это шаблон, который можно наблюдать как во фреймворке Android (например, при расширении действий, представлений и рисуемых объектов), так и в библиотеках AndroidX (например, при создании WorkManager ListenableWorkers или RoomDatabases ). В отличие от этого, открытая рефлексия Gson не подходит для использования в приложениях Android .

Типы правил хранения в библиотеках

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

  • Правила сохранения данных потребителя должны определять правила, сохраняющие всё, что отражает библиотека. Если библиотека использует рефлексию или JNI для вызова своего кода или кода, определённого клиентским приложением, эти правила должны описывать, какой код необходимо сохранить. Библиотеки должны упаковывать правила сохранения данных потребителя, которые используют тот же формат, что и правила сохранения данных приложения. Эти правила объединяются в артефакты библиотеки (AAR- или JAR-файлы) и автоматически применяются во время оптимизации приложения Android при использовании библиотеки. Эти правила хранятся в файле, указанном свойством consumerProguardFiles в файле build.gradle.kts (или build.gradle ). Подробнее см. в разделе Запись правил сохранения данных потребителя .
  • Правила сохранения сборки библиотеки применяются при её сборке. Они необходимы только в том случае, если вы решили частично оптимизировать библиотеку во время сборки. Они должны предотвращать удаление открытого API библиотеки, в противном случае открытый API не будет присутствовать в дистрибутиве библиотеки, что означает, что разработчики приложений не смогут использовать библиотеку. Эти правила хранятся в файле, указанном свойством proguardFiles в файле build.gradle.kts (или build.gradle ). Подробнее см. в разделе Оптимизация сборки библиотеки AAR .

Напишите правила хранения для потребителей

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

  • Не используйте неподходящие глобальные правила — избегайте помещения глобальных настроек, таких как -dontobfuscate или -allowaccessmodification в файл правил хранения данных потребителя вашей библиотеки, поскольку они влияют на все приложения, использующие вашу библиотеку.
  • Не используйте -repackageclasses в файле правил хранения для потребителей вашей библиотеки. Однако для оптимизации сборки библиотеки вы можете использовать -repackageclasses с внутренним именем пакета, например, <your.library.package>.internal , в файле правил хранения для пользователей вашей библиотеки. Это может повысить эффективность вашей библиотеки, даже если приложения, использующие её, не оптимизированы, но, как правило, это не обязательно, поскольку приложения также должны оптимизироваться. Подробнее об оптимизации библиотек см. в разделе Оптимизация для авторов библиотек .
  • Объявите все атрибуты, необходимые для функционирования вашей библиотеки, в файлах правил хранения вашей библиотеки, даже если они могут пересекаться с атрибутами, определенными в proguard-android-optimize.txt .
  • Если вам требуются следующие атрибуты в дистрибутиве библиотеки, сохраните их в файле правил хранения сборки вашей библиотеки, а не в файле правил хранения потребителя вашей библиотеки:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • Авторам библиотек следует сохранять атрибут RuntimeVisibleAnnotations в правилах хранения потребителей, если аннотации используются во время выполнения.
  • Авторам библиотек не следует использовать следующие глобальные параметры в правилах хранения данных потребителей:
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -overloadaggressively
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

AAR-библиотеки

Чтобы добавить потребительские правила для библиотеки AAR, используйте параметр consumerProguardFiles в скрипте сборки модуля библиотеки Android. Подробнее см. в нашем руководстве по созданию модулей библиотеки .

Котлин

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 (расширенная)

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

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

Котлин

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-файла).