面向库作者的优化

作为库作者,您必须确保应用开发者可以轻松地将您的库整合到其应用中,同时保持高质量的最终用户体验。这意味着,您的库必须与 Android 优化 (R8) 兼容,而无需开发者进行额外设置,或者您必须在文档中说明该库可能不适合在 Android 上使用。至关重要的是,旨在用于 Android 的库不得妨碍重要的应用优化,并且必须遵循其他优化要求。

本文档面向已发布库的开发者,但对于大型模块化应用中的内部库模块开发者也可能有用。

如果您是应用开发者,并且想要了解如何优化 Android 应用,请参阅启用应用优化。如需了解哪些库适合使用,请参阅明智地选择库。

了解保留规则类型

库中可以有两种不同类型的保留规则:

  • 使用方 keep 规则必须指定保留库所反映内容的规则。如果库使用反射或 JNI 来调用其代码或客户端应用定义的代码,则这些规则需要描述需要保留哪些代码。库应打包使用方 keep 规则,这些规则与应用 keep 规则采用相同的格式。这些规则会捆绑到库工件(AAR 或 JAR)中,并在使用库时在 Android 应用优化期间自动使用。这些规则在 build.gradle.kts(或 build.gradle)文件中使用 consumerProguardFiles 属性指定的文件中进行维护。如需了解详情,请参阅编写使用方保留规则。
  • 库构建保留规则会在构建库时应用。只有当您决定在构建时对库进行部分优化时,才需要这些文件。它们必须防止库的公共 API 被移除,否则公共 API 将不会出现在库分发中,这意味着应用开发者无法使用该库。这些规则在 build.gradle.kts(或 build.gradle)文件中使用 proguardFiles 属性指定的文件中进行维护。如需了解详情,请参阅优化 AAR 库 build。

优化要求和指南

库中的 R8 配置会对使用方应用的最终二进制文件大小和性能产生全局影响。除了常规的保留规则最佳实践之外,库作者还必须遵守特定要求,并考虑其他准则。

遵守优化要求

库效率低下是导致应用膨胀、内存浪费、启动缓慢和 ANR(“应用无响应”错误)的主要原因。库必须避免违反以下要求,以免严重降低应用质量和用户体验。

  • 不使用宽泛或针对整个包的保留规则:您的库不得包含会保留库中或另一个库中大部分代码的宽泛保留规则。宽泛的保留规则可能会在短期内解决崩溃问题,但会增加使用您库的所有应用的大小。

    请勿针对您的库或其他引用库中的软件包添加软件包级保留规则(例如 -keep class com.mylibrary.** {*; })。此类规则会限制在使用您的库的所有应用中对这些软件包进行优化。

    请勿为库使用的第三方依赖项(例如 -keep public class com.google.android.gms.** { *; })添加宽泛的保留规则。许多外部库已经自带了使用者保留规则;引入针对整个包的规则会取代这些规则,并强制 R8 在整个应用中保留未引用的依赖项代码。

  • 不使用不当的全局规则:切勿使用 -dontobfuscate 或 -allowaccessmodification 等全局选项。

  • 尽可能使用代码生成而非反射:尽可能使用代码生成 (codegen) 而非反射。Codegen 和反射都是编程时避免样板代码的常见方法,但 codegen 与 R8 等应用优化工具更兼容。

    使用 codegen 时,系统会在构建过程中分析和修改代码。 由于编译时间之后没有任何重大修改,因此优化器知道最终需要哪些代码以及可以安全移除哪些代码。

    通过反射,代码会在运行时进行分析和操作。由于代码在执行之前并未真正最终确定,因此优化器不知道可以安全移除哪些代码。它可能会移除在运行时通过反射动态使用的代码,从而导致应用崩溃。

    许多现代库都使用代码生成,而不是反射。如需了解 Room、Hilt 和许多其他工具使用的通用入口点,请参阅 KSP。

  • 支持 R8 完整模式:启用 R8 完整模式后,您的库不应崩溃。R8 的完整模式是推荐使用的 R8 模式,自 2023 年发布的稳定版 AGP 8.0 起,该模式已成为默认模式。如果您的库在 R8 下崩溃,解决方案是确定特定的反射或 JNI 入口点并添加有针对性的规则,而不是保留整个软件包。如需根据这些入口点上的注解生成有针对性的规则,请使用 Jetpack Keep。

其他建议

除了优化要求之外,以下是其他建议。

  • 请勿在库的使用方保留规则文件中使用 -repackageclasses。使用方保留规则会合并到使用您的库的每个应用的 R8 配置中,因此此全局选项会应用于整个应用,并会替换应用开发者自己的重新打包设置。默认情况下,从 AGP 9.0 开始,如果库的使用者保留规则包含全局选项,则发布库会失败。如需了解详情,请参阅 AGP 9.0 版本说明。

    为了优化库 build,您可以在库的 build 保留规则文件中改用 -repackageclasses 和内部软件包名称(例如 <your.library.package>.internal)。这可以提高库在未优化的应用中的效率,但通常没有必要,因为应用也应该进行优化。

  • 在库的保留规则文件中声明库正常运行所需的任何属性,即使这些属性可能与 proguard-android-optimize.txt 中定义的属性重叠。

  • 如果您在库分发中需要以下属性,请在库的 build 保留规则文件中维护这些属性,而不要在库的使用方保留规则文件中维护这些属性:

    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • 如果库作者在运行时使用注解,则应在其使用方 keep 规则中保留 RuntimeVisibleAnnotations 属性。

  • 库作者不应在其使用方保留规则中使用以下全局选项。与 -repackageclasses 一样,这些选项会配置应用的整个 R8 build,而不仅仅是库的代码,因此请将这些选项留给应用开发者:

    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

何时可以反思

如果您必须使用反射,则应仅反射到以下任一项:

  • 特定目标类型(特定接口实现器或子类)
  • 使用特定运行时注解的代码

以这种方式使用反射可限制运行时开销,并支持编写有针对性的使用方保留规则。

这种特定且有针对性的反射形式是您可以在 Android 框架(例如,在膨胀 activity、视图和可绘制对象时)和 AndroidX 库(例如,在构建 WorkManager ListenableWorkers 或 RoomDatabases 时)中看到的一种模式。相比之下,Gson 的开放式反射不适合在 Android 应用中使用。

常见误解

一些常见的误解可能会导致您错误地配置 R8。这些情况包括:

  • 对 R8 优化的误解:与普遍理解相反,R8 的优化不仅限于混淆处理,还包括代码缩减以及使用方法内联和类合并技术的逻辑优化。如需了解详情,请参阅 R8 优化概览。

  • 绕过对混淆库的优化:一个常见错误是省略对库的优化,因为该库在编译为 AAR(Android 归档)或 JAR(Java 归档)时已经过优化或混淆。库构建期间的优化是有限的,您的应用不应通过将库包含在保留规则中来停用库的优化。如需了解详情,请参阅优化 AAR 库 build。

  • 对 -keep 选项的理解不正确:-keep 规则会阻止 R8 运行其任何优化传递。如需了解详情,请参阅选择合适的保留选项。

验证使用方 keep 规则质量

如需验证您的库在嵌入到应用中时不会过度阻碍代码优化,请使用库示例或集成测试应用。启用 R8,并使用 R8 配置分析器检查应用的整体 R8 配置质量。

如果您的使用方规则显示为限制对库代码的大部分内容进行优化,则表明您的使用方规则需要优化。 缩小使用方规则的范围,并在启用了 R8 的应用中测试库中任何受影响的方面。

配置规则打包

为确保正确应用使用方保留规则,您必须根据库格式对其进行适当打包。

如果您使用 Jetpack Keep 声明库的反射和 JNI 用法,其 Gradle 插件可以将生成的规则打包为使用方 keep 规则。

AAR 库

如需为 AAR 库添加使用方规则,请在 Android 库模块的 build 脚本中使用 consumerProguardFiles 选项。如需了解详情,请参阅有关创建库模块的指南。

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

Groovy

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

JAR 库

如需将规则与以 JAR 形式提供的 Kotlin 或 Java 库捆绑在一起,请将规则文件放在最终 JAR 的 META-INF/proguard/ 目录中,并使用任意文件名。例如,如果您的代码位于 <libraryroot>/src/main/kotlin 中,请将使用方规则文件放在 <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro 中,这些规则将打包到输出 JAR 中的正确位置。

通过检查规则是否位于 META-INF/proguard 目录中,验证最终 JAR 是否正确捆绑了规则。

优化 AAR 库构建(高级)

通常,您无需直接优化库 build,因为在库 build 时可进行的优化非常有限。作为库开发者,您需要考虑多个优化阶段,并在优化库之前,在库和应用构建时保持行为。

如果您仍想在构建时优化库,Android Gradle 插件支持此操作。

Kotlin

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

Groovy

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 中定义保留规则。

如果您在库的 build 中使用 -repackageclasses,请将类重新打包到库软件包内的子软件包中。例如,使用 -repackageclasses 'com.example.mylibrary.internal' 而不是 -repackageclasses 'internal'。

支持不同的 R8 版本(高级)

您可以定制规则,以针对特定版本的 R8。这样,您的库就可以在使用较新 R8 版本的项目中以最佳方式运行,同时允许在具有较旧 R8 版本的项目中继续使用现有规则。

如需指定目标 R8 规则,您需要将其添加到 AAR 的 classes.jar 内的 META-INF/com.android.tools 目录中,或添加到 JAR 的 META-INF/com.android.tools 目录中。

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 版(不包括 8.2.0 版)使用。

Android Gradle 插件会使用这些信息来选择当前 R8 版本可以使用的所有规则。如果库未指定目标 R8 规则,Android Gradle 插件将从旧版位置(对于 AAR,为 proguard.txt;对于 JAR,为 META-INF/proguard/<ProGuard-rule-files>)选择规则。