Android 平台构建系统 (Soong) 会在编译 DEX 字节码的目标上运行 R8 编译器,这些目标包括 Android 应用 (android_app)、测试 (android_test) 以及具有 compile_dex: true 的可安装 Java 库(例如 services.jar),以缩减、优化和剥离未使用的代码和资源。在静态库(android_library 和静态 java_library)上,Soong 不会直接运行 R8,而是使用 optimize 属性块将使用方保留规则 (export_proguard_flags_files: true) 附加并传播到静态关联它们的下游 DEX 目标。
对于构建系统映像或供应商软件包的平台工程师来说,消除过于宽泛的保留规则可直接提升系统健康状况:
- 更小的系统分区占用空间:R8 会先移除死类、方法和资源,然后再将 APK 和 JAR 打包到
/system、/system_ext、/product和/vendor。 - 编译工件更小:DEX 方法越少,意味着
dex2oat在构建时或设备端编译期间生成的.odex和.vdex文件越小。 - 降低运行时内存压力:由于 Android 使用按需分页的
mmap调用将.odex、.vdex和 APK 资源表映射到进程内存中,因此较小的二进制文件可减少应用启动期间的主要页面错误,并降低跨进程的常驻代码和资源内存。如需详细了解编译后的应用代码如何影响设备内存,请参阅应用代码即内存。 - 更有效的全程序优化:缩小保留限制范围可让 R8 内嵌方法、对接口和虚方法调用进行去虚化、删减未使用的字段,以及跨类传播常量。
在 Soong 中配置 R8 优化
在 Android.bp 文件中,使用 android_app 和可安装 java_library 目标(或 android_library 和静态 java_library 模块,以导出使用方保留规则)上的 optimize 属性块配置 R8 优化:
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
optimize: {
obfuscate: true,
shrink_resources: true,
},
}
下表总结了 Soong 中最常见的 optimize 属性(在 build/soong/java/dex.go 中定义):
| 属性 | 说明 |
|---|---|
enabled |
控制 R8 是否在目标上运行。对于所有 android_app 目标,默认值为 true。
|
shrink |
控制摇树优化,以移除无法访问的类、字段和方法。对于 android_app 目标,默认值为 true(对于独立 java_library 和测试模块,默认值为 false,除非明确设置,否则会传递 -dontshrink)。
|
optimize |
控制字节码优化,例如方法内联、类合并、常量传播和死分支移除。对于 android_app 目标,默认为 true(由 RELEASE_R8_OPTIMIZE_BY_DEFAULT 发布 build 标志控制)。
|
obfuscate |
控制标识符缩减大小和重命名。默认为 false,以实现历史兼容性,但请尽可能将其设置为 true,以缩减 DEX 大小并实现更深层次的优化(请参阅尽可能启用混淆)。
|
shrink_resources |
在代码缩减后,从打包的 APK 中移除未使用的资源(res/ 条目)。默认值为 false。
|
optimized_shrink_resources |
运行集成的 R8 代码和资源缩减器流水线,以便 R8 在单次传递中联合跟踪代码和资源引用。设置 shrink_resources: true 后,默认值为 RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT(在标准平台 build 中为 true)。
|
proguard_flags_files |
列出包含自定义保留规则的模块专用 .flags 或 .pro 文件。如果标准注解或默认值足够,请避免添加自定义文件。
|
export_proguard_flags_files |
将此库的 proguard_flags_files 传播到静态依赖于它的下游模块。
|
trace_references_from |
列出直接 DEX 编译依赖目标(通过 instrumentation_for 或 libs 连接),其字节码引用到此目标 R8 会自动跟踪并保留(也会在 override_android_app 和 override_android_test 变体中传播,并在与 .impl 拆分搭配使用时支持非依赖配套 Java 库)。
|
allow_traced_references |
允许直接 DEX 编译的依赖目标(通过 instrumentation_for 或 libs 连接,例如无法在 trace_references_from 中列出的跨代码库或供应商测试)通过设置 trace_references: true 选择启用将其字节码引用跟踪到此目标。 |
trace_references |
如果对具有 allow_traced_references: true 的目标的直接 DEX 编译依赖项设置为 true,则会在此模块上运行 R8 tracereferences,并将生成的保留规则传递给目标模块。
|
protect_api_surface |
将 --protect-api-surface 传递给 R8。在 java_sdk_library 上设置时,还会自动生成 Metalava API keep 规则,并将其附加到所有已启用的 API 范围(公共、系统、module-lib、system-server 和测试)的实现库。
|
静态库(android_library、java_library 和 AAR 导入)也可以在 Android.bp 中设置 required_downstream_optimization: { enabled: true, optimize: true },以要求任何静态链接它们(直接或间接)的下游 DEX 编译目标启用 R8 缩减和字节码优化。Soong 会在 androidx.compose.runtime_runtime 上自动启用此功能,以便使用 R8 优化链接 Jetpack Compose 的目标。
尽可能启用混淆功能
在 Soong 中,为了实现历史兼容性,obfuscate 默认为 false,但您应尽可能在 android_app 和独立 DEX 目标上明确设置 obfuscate: true:
- 更小的 DEX 和
.vdex占用空间:将软件包、类、字段和方法重命名为简短标识符(a、b)可缩减 DEX 字符串池(string_ids和string_data_item)和类型描述符。由于 Android 会将.vdex和.odex文件映射到进程内存中,因此较小的符号表会直接缩减系统分区大小和运行时内存占用。 - 更深入的全程序优化:让 R8 重命名标识符可解锁类合并、软件包扁平化和跨软件包的合成类去重,否则 R8 必须跳过这些操作以避免名称冲突。
- 完整的堆栈轨迹符号化解析:在 Soong 中启用混淆处理不会降低可调试性。对于每个 R8 编译的目标,Soong 都会在模块的中间目录中发出一个
proguard_dictionary映射文件,将所有模块字典捆绑到 build 的proguard-dict.zip工件中,将映射哈希 (--map-id-template) 嵌入到 DEX 标头中,并重写类SourceFile属性 (--source-file-template),以便retrace可以自动对堆栈轨迹进行符号化。
仅针对以下情况保留 obfuscate: false(或明确保留公共 API 名称):
- bootclasspath 或
system_server类路径上的共享库: 如果模块(java_library或java_sdk_library)公开了其他模块在运行时动态链接的外部 API 接口(例如framework.jar、services.jar或<uses-library>目标),则必须保留obfuscate: false,或使用protect_api_surface: true、@KeepForApi或-keep规则明确保留其 API 接口,以便根据其桩编译的调用方可以在运行时解析类和成员名称。在java_sdk_library模块上,设置protect_api_surface: true会自动生成 Metalava ProGuard 保留规则,并将其附加到所有已启用的 API 范围(公共、系统、module-lib、system-server 和测试)的实现库。在不可更新的 bootclasspath 目标(framework-minus-apex、ext和framework-location)上启用通用 R8 优化后,这些目标还会包含framework-hiddenapi-proguard-rules(由hiddenapi_to_proguard生成),以保留活跃boot/hiddenapi/*.txt文件中列出的成员(省略hiddenapi-max-target-o.txt)。
在应用上启用 obfuscate: true 之前,请验证以下迁移前提条件:
- 外部测试 APK 依赖项:如果外部
android_testAPK 设置了instrumentation_for: "MyApp"并直接调用MyApp的内部类或方法,则重命名这些符号会导致在测试运行时出现NoSuchMethodError或NoClassDefFoundError。配置trace_references_from(或allow_traced_references与trace_references: true),将测试构建为静态链接MyApp.impl的自插桩 APK,或使用@VisibleForTesting为测试钩子添加注解(请参阅第 2 步:迁移测试驱动的 keep 规则),以便在混淆应用的其余部分时保留测试所需的内部符号。 - 基于字符串的反射和 JNI:如果模块通过字面量字符串名称(
Class.forName、getDeclaredMethod或 JNIFindClass和GetMethodID)查找类、方法或字段,而没有保留规则或注解,混淆处理会重命名这些目标并破坏运行时查找。(请注意,未加注解的反射在shrink: true和optimize: true下也会失败。)使用保留注解指南(@UsesReflection、@UsedByReflection或@UsedByNative)为这些入口点添加注解,以便 R8 保留其名称并对模块的其余部分进行混淆处理。
遵循零自定义标志原则
理想的 Android 平台模块没有自定义 proguard.flags 或 keep.xml 文件。在 Android 平台 build 中,绝大多数自定义保留规则都是多余的:
- 平台基准和 AAPT2:大多数入口点已由全局平台基准(例如
@Keep、JNInative方法和@VisibleForTesting)自动保留,或由 AAPT2 从AndroidManifest.xml和布局资源生成(请参阅了解默认平台保留规则)。 - 有针对性的替代方案:如果必须保留入口点,请优先使用声明网站注解(
keepanno、@VisibleForTesting)、导出的库规则 (export_proguard_flags_files: true) 或静态测试链接,而不是分离的.flags文件(请参阅审核和迁移现有保留规则)。
在添加或保留自定义 proguard.flags 或 keep.xml 文件之前,请验证该规则是否已由默认基准处理,或者是否可以迁移到代码注解。
了解默认平台保留规则
Soong 会自动将以下全局基准规则传递给 DEX 编译目标(android_app、android_test 和可安装的 java_library 模块)上的每个 R8 调用,这些规则在 build/soong/java/dex.go 中配置:
build/make/core/proguard.flags:在所有软件包中保留使用@com.android.internal.annotations.VisibleForTesting注解的类和成员,并在android.**、com.android.**和com.google.android.**软件包中保留使用@VisibleForTesting(androidx.annotation.VisibleForTesting或com.google.common.annotations.VisibleForTesting)注解的类和成员。此外,还会保留@TestApi、@Keep(androidx.annotation、android.support.annotation、com.android.internal.annotations)、@KeepForWeakReference、@WeaklyReferencedCallback和@dalvik.annotation.optimization.**。build/make/core/proguard_basic_keeps.flags:保留堆栈轨迹、运行时可见性注解 (RuntimeVisible*Annotations)、Exceptions和AnnotationDefault属性、native方法、Serializable成员、@JavascriptInterface方法、Throwable(String)构造函数、Parcelable$CREATOR字段和 protobufMessageLite字段的SourceFile属性。build/make/core/proguard/kotlin.flags:针对特定 Kotlin 元注解(kotlin.Metadata和kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target})忽略无害警告,并在发布 build 中剥离 KotlinDebugMetadata注解。build/make/core/proguard/checknotnull.flags:将常见的 null 检查辅助调用(com.google.common.base.Preconditions.checkNotNull和dagger.internal.Preconditions.checkNotNull*)替换为简洁的字节码 null 检查。此文件有意省略了Objects.requireNonNull,以便在框架 API 边界之间保留显式异常消息。build/make/core/proguard/enumvalues.flags:保留enum类型上的values和valueOf方法,除非 build 配置停用了这些方法。- AAPT2 自动生成的规则:AAPT2 会检查合并后的
AndroidManifest.xml、布局 XML 和偏好设置 XML 文件,以便为每个已注册的Activity、Service、BroadcastReceiver、ContentProvider、BackupAgent、Application、自定义View子类、Preference子类和 XMLandroid:onClick方法生成确切的保留规则。
审核并迁移现有保留规则
在审核平台代码库中的现有 proguard.flags 或 keep.xml 文件时,请按照以下四步层次结构依次评估每条规则:
第 1 步:删除多余或过时的规则
删除已由全局基准、AAPT2 清单和布局规则或 R8 默认规则涵盖的规则,以及引用不再存在的类或软件包的规则。
如果 .flags 文件中的所有规则都是多余的,请删除该文件并从 Android.bp 中移除 proguard_flags_files。如果剩余的 optimize 块仅重述默认设置,请移除多余的块,并使用 bpfmt -w Android.bp 设置 build 文件的格式。清理软件包时,请检查子目录中的配套模块(例如 Kotlin 目标变体)是否引用了相同的标志文件,并一并更新它们。
第 2 步:迁移测试驱动的 keep 规则
使用以下模式之一将测试驱动的保留规则移出自定义生产 .flags 文件:
跟踪来自测试或配套目标(
trace_references_from或allow_traced_references)的引用:当目标和直接依赖模块都编译自己的 DEX(android_app、android_test或 dexedjava_library)并通过instrumentation_for或libs连接时,Soong 可以在依赖模块上运行 R8tracereferences,并将生成的保留规则馈送到目标的 R8 编译中,而无需创建依赖项循环或手动拆分.impl库:同一代码库测试 (
trace_references_from):列出目标模块的直接instrumentation_for或libs依赖项(这也会在override_android_app和override_android_test变体之间自动传播,并且在与.impl拆分搭配使用时继续支持非依赖项配套 Java 库):android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { obfuscate: true, shrink_resources: true, trace_references_from: ["MyAppTests"], }, } android_test { name: "MyAppTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], }跨代码库或供应商测试(
allow_traced_references和trace_references):如果目标模块无法直接在trace_references_from中列出依赖项(例如其他代码库或供应商树中的测试),请在目标模块上设置allow_traced_references: true,并使用trace_references: true从每个依赖项中选择启用:android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { allow_traced_references: true, }, } android_test { name: "MyAppVendorTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], optimize: { trace_references: true, }, }测试中基于字符串的反射:
tracereferences会检查字节码常量池引用,无法检测测试中基于字符串的反射(Class.forName、getDeclaredField、getDeclaredMethod或反射字段注入辅助程序)。使用@VisibleForTesting(或@UsedByReflection)为通过反射访问的生产字段和方法添加注解。
将实现库关联到测试 (
static_libs):如果您希望在正式版 APK 中保留零个仅限测试的入口点,并且不希望测试更改在正式版目标上重新触发 R8,请将应用源文件放在android_library(MyApp.impl) 中,并将MyApp.impl静态关联到MyApp和MyAppTests:android_library { name: "MyApp.impl", srcs: ["src/**/*.java"], manifest: "AndroidManifest.xml", } android_app { name: "MyApp", static_libs: ["MyApp.impl"], optimize: { obfuscate: true, shrink_resources: true, }, } android_test { name: "MyAppTests", srcs: ["tests/**/*.java"], static_libs: ["MyApp.impl"], }这种 3 模块模式可让
MyAppTests作为具有内部类完整访问权限的自插桩测试运行,同时允许MyApp纯粹针对生产入口点进行缩减和混淆处理。使用
@VisibleForTesting注释测试钩子:如果外部测试 APK 仅对android_app目标调用一些内部方法或构造函数,您可以使用@VisibleForTesting注释这些声明。全局build/make/core/proguard.flags基准会自动保留android.**、com.android.**和com.google.android.**软件包中的@VisibleForTesting项,而无需自定义.flags文件。(对于这些命名空间之外的供应商模块,请使用trace_references_from、@UsedByReflection或导出的库规则。) 手动使用@VisibleForTesting(而非trace_references_from或trace_references)添加注解时,还应为传递给 Mockitomock(...)或spy(...)的类(而不仅仅是桩方法)添加注解,因为设备端mockito-target(dexmaker-mockito) 会生成运行时子类,而 R8 可以将未子类化的类标记为final(MockitoException: Can't mock/spy class ... - final class)。避免或重新打包重叠的静态库 (
jarjar_rules):当android_app及其instrumentation_for测试 APK 静态链接同一库(直接链接,例如guava,或间接链接)时,tracereferences会看到测试中定义的这些类,并且不会为应用发出保留规则,而在运行时,应用的父类加载器会使用其 R8 缩减的类来遮盖测试的副本,从而导致IllegalAccessError或NoSuchMethodError。如果测试不需要自己的捆绑副本,请从测试的static_libs中移除重复项(或通过libs链接),以避免冲突;否则,请使用jarjar_rules在模块专用前缀下重新打包应用的重叠静态库软件包。
第 3 步:从所属库中导出规则
如果共享 java_library 或 android_library 需要为其自己的内部反射或 JNI 回调保留规则,请在库目标上定义规则并设置 export_proguard_flags_files: true:
java_library {
name: "my-shared-library",
srcs: ["src/**/*.java"],
optimize: {
proguard_flags_files: ["proguard.flags"],
export_proguard_flags_files: true,
},
}
静态关联 my-shared-library 的所有下游 android_app 和库目标都会自动继承这些规则,因此下游应用无需复制这些规则。如果不同目录中的多个模块必须共享不与单个代码库关联的规则集,请通过 Android.bp 中的显式 filegroup 发布 .flags 文件,以便模块可以跨软件包边界干净地引用 :my-shared-flags。如需了解有关在不限制下游应用优化的情况下编写库使用方保留规则的一般指南,请参阅针对库作者的优化。
第 4 步:在源代码中为声明添加注解
当通过反射或 JNI 访问类、方法、构造函数或字段,且 AAPT2 或全局基准未涵盖这些类、方法、构造函数或字段时,请将 .flags 文件中分离的 -keep 规则替换为保留注解指南中的源代码注解(请参阅 keepanno Javadoc 参考文档):
@UsesReflection:如果您控制执行反射的代码,请首选此注解。将其放置在反射调用位置,以声明动态访问的目标类、方法或字段。由于@UsesReflection会自动对带注解的调用网站本身可访问的前提条件进行编码,因此如果调用网站未使用,R8 会同时删减调用方和反射目标。@UsedByReflection:放置在由外部代码或库反射式实例化或调用的类、方法、字段或构造函数上,例如使用Class.forName从Bundle或Settings键加载的类,或跨动态类加载器加载的插件类。指定kind(例如KeepItemKind.CLASS_AND_METHODS)、preconditions(@KeepCondition) 和参数限制,以便 R8 仅保留确切的反射协定,并且仍能优化或剪除类中未使用的成员。请勿将@UsedByReflection添加到清单注册的组件(例如JobService或BroadcastReceiver),因为 AAPT2 已经保留了这些组件。@UsedByNative:放置在使用GetMethodID、GetStaticMethodID或GetFieldID从 C 或 C++ JNI 代码访问的方法或字段上。@KeepForApi:放置在库 API 类或成员上,这些类或成员在库本身在分发前被缩减时必须保持不变。@Keep(androidx.annotation.Keep):在keepanno不适用时用作后备。将@Keep应用于某个类会无条件保留该类及其所有成员。将@Keep应用于方法或字段会充当无条件入口点,即使类从未实例化,也会保留该成员及其包含类,而keepanno则表示条件可达性。
如需在 Soong 模块中使用 R8 keepanno 注解,请执行以下操作:
在
Android.bp中,将"keepanno-annotations"添加到libs:libs: [ "keepanno-annotations", ],导入
com.android.tools.r8.keepanno.annotations.*,并在 Java 或 Kotlin 源代码中为声明或调用网站添加注解:import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.UsedByReflection; public final class CustomPluginController { @UsedByReflection( description = "Instantiated via Class.forName from plugin config", kind = KeepItemKind.CLASS_AND_METHODS) public CustomPluginController(Context context) { // ... } }R8 会将
keepanno注解直接转换为其内部保留规则模型,并从最终 DEX 输出中剥离这些注解,从而实现零运行时字节码开销。
避免常见陷阱
以下部分介绍了平台代码中常见的 proguard.flags 和 keep.xml 陷阱以及如何修复它们。
宽泛的组件保留规则
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- 为什么会影响性能:此规则与 AAPT2 完全重复。
由于它使用通配符,而不检查组件是否已在最终合并的
AndroidManifest.xml中注册,因此它会强制 R8 保留在类路径中找到的每个Activity、Service或BroadcastReceiver子类,包括未使用的库组件和已停用的调试 activity。 - 建议的解决方法:删除相应规则。AAPT2 会检查合并后的清单,并为已注册的组件生成确切的
-keep规则。
宽泛的 XML 点击处理程序保留规则
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- 为何会影响性能:保留模块中每个类的每个
public void *(View)方法会阻止 R8 剥离或内联任何恰好接受单个View参数的方法。 - 建议的解决方法:删除相应规则。AAPT2 会扫描布局 XML 文件,并为
android:onClick属性引用的方法生成有针对性的保留规则。
宽泛的 View getter 和 setter 保留规则
# Don't do this:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
public *** get*();
}
- 为什么会影响性能:此规则会强制 R8 保留应用和所有关联库(包括 AndroidX 和 Material 库)中每个
View子类的每个 getter 和 setter,从而阻止移除死方法和内联界面代码。 - 建议的解决方法:删除相应规则。AAPT2 已经为从布局 XML 文件中膨胀的自定义
View类保留了构造函数。如果代码使用反射字符串名称(例如ObjectAnimator.ofFloat(view, "translationZ", ...))为视图属性添加动画效果,请将字符串名称替换为类型化属性引用(View.TRANSLATION_Z或自定义FloatProperty或IntProperty实现),以完全避免反射。如果无法避免反射属性访问,请使用@UsedByReflection为特定 getter 或 setter 添加注解。
全面优化或混淆停用
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- 为什么会影响性能:将
-dontoptimize或-dontshrink放在.flags文件中会静默替换模块的Android.bp设置,并停用整个目标的优化传递。更糟糕的是,如果某个库导出了包含-dontoptimize的.flags文件,它会为链接该库的每个下游android_app停用 R8 优化。 - 建议的解决方法:从
.flags文件中移除-dontoptimize、-dontshrink和-dontobfuscate。使用叶目标上的optimize块(shrink、optimize、obfuscate)在Android.bp中明确控制优化行为。
已提交规则中的诊断标志
# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
- 为何会影响 build:诊断标志会使 build 日志泛滥,或在沙盒化 Soong build 期间尝试将输出文件写入本地路径。
- 建议的解决方法:从已提交的
.flags文件中删除这些标志。Soong 会自动将 R8 映射和使用情况输出(例如proguard_dictionary和proguard_usage.zip)写入out/soong/.intermediates/下的模块中间目录。
测试代码的生产环境保留规则
- 为何会影响性能:仅为测试访问权限添加自定义
-keep规则会强制该代码在正式版 build 中保持未混淆状态并保留。虽然配置trace_references_from(或将allow_traced_references与trace_references: true搭配使用)或使用@VisibleForTesting注释方法可避免维护手动-keep规则,并仅保留测试引用的内容,但这些符号仍会在正式版二进制文件中提供。 - 建议的解决方法:通过启用
trace_references_from(或allow_traced_references和trace_references: true)来消除自定义测试-keep规则,或者将应用构建为使用static_libs关联到自插桩测试 APK 的.impl库,以使仅限测试的入口点完全不出现在正式版 APK 中,如第 2 步:迁移测试驱动的 keep 规则中所述。
keep.xml 中的全资源通配符
<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/*,@string/*" />
- 为何会影响性能:通配符会保留模块及其依赖项中匹配类型的所有资源,从而导致资源缩减 (
shrink_resources: true) 失效,并使 APK 和映射的resources.arsc表膨胀。 - 建议的修复方法:
- 优先使用静态资源引用,而不是
keep.xml:避免使用Resources.getIdentifier方法动态查找一组有限的资源,例如带编号的实验字符串或主题可绘制对象。请改用编译时switch语句,或映射静态R.id、R.string或R.drawable常量。静态引用可让 R8 和 AAPT2 跟踪确切的实时资源,消除运行时字符串查找开销,并完全消除对keep.xml的需求。 - 列出特定资源名称:如果需要动态查找(例如通过第三方许可读取器),请在
tools:keep中列出确切的资源标识符(例如tools:keep="@raw/third_party_licenses")。 - 删除多余的
keep.xml文件:如果列出的资源已在代码 (R.raw.foo) 或 XML (@raw/foo) 中静态引用,缩减器会自动保留这些资源,因此您可以删除res/raw/keep.xml。
- 优先使用静态资源引用,而不是
验证和审核规则更改
每当您移除或缩小 proguard.flags 或 keep.xml 中的保留规则时,请验证模块是否能顺利构建、通过单元和插桩测试,并保留所有必需的入口点。
构建和测试模块
干净地构建模块,以验证 R8 和 AAPT2 是否在没有缺少引用警告的情况下完成:
m <MODULE_NAME>使用
atest运行模块的单元测试和插桩测试:atest <TEST_MODULE_NAME>对于系统应用、特权服务或依赖于硬件的模块,请在目标实体设备上或在设备测试实验室中运行插桩测试和界面测试,以执行运行时反射路径、IPC 绑定和资源膨胀。
检查 DEX 和资源差异
比较规则更改前后的已编译 APK 或 JAR,以确认 R8 移除了死代码和资源,而没有剥离预期的入口点:
使用
apkanalyzer或dexdump检查输出 APK 或 DEX 文件中保留的类、方法和字段:apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk修改
keep.xml或启用shrink_resources时,请使用aapt2 dump resources验证 build 是否会剥离未使用的资源并保留所需的资源:aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk直接在模块的 R8 前
classes.jar上运行prebuilts/r8/r8.jar以在不进行完整平台 build 的情况下离线验证标志更改时,请同时传递模块的--lib类路径 JAR(android.jar、framework.jar和共享libs)以及 AAPT2 从AndroidManifest.xml和布局 XML 生成的proguard.options规则。如果没有这些输入,R8 将无法解析框架超类型(Activity、Service、Phone、Handler),并且会保留过少的层次结构方法。
使用 R8 分析保留半径和规则包含关系
开源 R8 编译器包含一个保留半径分析器(在 Android Studio 和 Gradle 中也称为 R8 配置分析器),用于在编译期间精确衡量每条保留规则的影响。Soong 会将此分析器直接集成到 Android 平台 build 中(在 build/soong/java/dex.go 中配置)。
如需分析平台模块或整个 build 的保留规则,请执行以下操作:
使用
R8_DUMP_KEEP_RADIUS=true环境变量运行构建:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>设置
R8_DUMP_KEEP_RADIUS=true后,Soong 会指示 R8 将保留规则指标记录在out/soong/.intermediates/下每个已编译模块的中间r8keepradius.pb文件中。对于每个保留规则和keepanno注解,R8 会记录:- 直接保留半径:规则保留的确切类、字段和方法,以及规则针对缩减、优化或混淆处理强制执行的具体限制。
- 规则包容:哪些其他保留规则或注解已保留相同的项。如果自定义规则完全被 AAPT2 规则或全局基准规则涵盖,您可以放心地将其删除。
- 包级和全局规则:使用宽泛的包级通配符或应用全局配置指令的规则。
使用
KeepRadiusHtmlReportGenerator(捆绑在prebuilts/r8/r8.jar中)将r8keepradius.pb输出转换为交互式 HTML 报告:# Generate an HTML report for a single module INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME> java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ $INTERMEDIATES/android_common/r8keepradius.pb \ keep_radius_report.html # Scan all built modules under out/soong/.intermediates and generate # per-module HTML reports plus an aggregate keepradius.html summary java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ out/soong/.intermediates \ out/keep_radius_reports当给定一个目录时,
KeepRadiusHtmlReportGenerator会遍历所有*keepradius*.pb文件,为每个模块生成一个 HTML 报告,并创建out/keep_radius_reports/keepradius.html,用于汇总整个 build 中的活跃项计数、保留项计数和最高半径保留规则。如需详细了解如何解读生成的报告中的缩减、优化和混淆处理得分,请参阅使用 R8 配置分析器。
Soong 还支持 build/soong/java/dex.go 中的其他 R8 诊断和内循环环境变量:
R8_DUMP_INPUT=true:将r8inputs.zip写入模块的中间目录,其中包含所有输入 JAR、库 JAR 以及用于独立 R8 重现的合并 ProGuard 配置。R8_DUMP_PERFETTO_TRACE=true:将r8trace.ptrace写入模块的中间目录,以便在 Perfetto 中检查 R8 编译过程。SOONG_SKIP_TRACE_REFERENCES=true:跳过运行由trace_references_from或trace_references配置的tracereferences规则,以便在迭代测试代码时加快本地内部循环构建。
验证库使用方规则
当 java_library 或 android_library 将保留规则导出到下游消费者应用 (export_proguard_flags_files: true) 时,这些规则不得包含会更改或停用消费者应用优化的全局标志。
R8 提供了一个开源保留规则分析器 (ProcessKeepRules),该分析器在 Android 平台 build 中作为 process-keep-rules 主机工具(在 prebuilts/r8/Android.bp 中定义)公开:
# Build the R8 keep rules validator host binary
m process-keep-rules
# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>
process-keep-rules 工具会解析每个配置文件,如果遇到库使用方规则中不允许的指令,则会失败并显示文件和行诊断信息。不允许的指令包括全局优化、缩减或混淆停用(例如 -dontoptimize 或 -dontshrink)、软件包重新打包和访问权限修改标志、诊断或映射标志,以及应用级 -keepattributes。
使用 pgaudit.py 审核源代码树
为了在 R8 的构建时分析器之外审核未构建的源目录,Android 平台树在 build/make/core/proguard/tools/ 下添加了 pgaudit.py 脚本。此静态分析工具会扫描整个代码库中的 Android.bp、proguard.flags 和 keep.xml 文件,以标记已由 AAPT2 或全局平台基准涵盖的规则、宽泛的通配符、-dontoptimize 替换项和仅限测试的保留规则:
# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/
# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json
平台和 OEM 团队可以结合使用 pgaudit.py 进行快速源代码树分类,使用 process-keep-rules 验证导出的库规则,以及将 R8_DUMP_KEEP_RADIUS=true 与 KeepRadiusHtmlReportGenerator 结合使用来衡量 build 中每条规则的确切类、字段和方法保留半径。