Android 平台构建系统 (Soong) 会在编译 DEX 字节码的目标(包括 Android 应用 [android_app]、测试 [android_test] 和具有 compile_dex: true 的可安装 Java 库 [例如 services.jar])上运行 R8 编译器,以缩减、优化和剥离未使用的代码和资源。对于静态库(android_library 和静态 java_library),Soong 不会直接运行 R8,而是使用 optimize 属性块将消费者保留规则 (export_proguard_flags_files: true) 附加并传播到静态关联它们的下游 DEX 目标。
对于构建系统映像或供应商软件包的平台工程师,消除过于宽泛的保留规则可直接提升系统健康度:
- 更小的系统分区占用空间:R8 会在将 APK 和 JAR 封装到
/system、/system_ext、/product和/vendor上之前,移除无用的类、方法和资源。 - 更小的编译后制品:DEX 方法越少,
dex2oat在 build 时或设备端编译期间生成的.odex和.vdex文件就越小。 - 降低运行时内存压力:由于 Android 使用按需分页的
mmap调用将.odex、.vdex和 APK 资源表映射到进程内存中,因此较小的二进制文件可减少应用启动期间出现的主要页面错误,并降低进程间的常驻代码和资源内存。如需详细了解已编译的应用代码如何影响设备内存,请参阅应用代码即内存。 - 更高效的程序级优化:通过缩小 keep 限制,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(对于传递 -dontshrink 的独立 java_library 和测试模块,默认值为 false,除非明确设置)。
|
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 |
列出 Java 库配套目标,R8 会自动跟踪并保留其字节码对相应目标的引用。 |
尽可能启用混淆
在 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_serverclasspath 中的共享库:如果模块(java_library或java_sdk_library)公开了在运行时由其他模块(例如framework.jar、services.jar或<uses-library>目标)动态链接的外部 API 表面,则必须保留obfuscate: false或使用@KeepForApi或-keep规则(以及针对 bootclasspath 目标的protect_api_surface: true)显式保留其 API 表面,以便针对其桩编译的调用方可以在运行时解析类和成员名称。
在应用上启用 obfuscate: true 之前,请验证以下迁移前提条件:
- 外部测试 APK 依赖项:如果外部
android_testAPK 设置了instrumentation_for: "MyApp"并直接调用MyApp的内部类或方法,则重命名这些符号会导致测试运行时出现NoSuchMethodError或NoClassDefFoundError。最好将测试构建为静态链接MyApp.impl的自检测 APK,使用@VisibleForTesting注释测试钩子,或配置trace_references_from(请参阅第 2 步:迁移测试驱动的保留规则),以便在混淆应用其余部分的同时保留测试所需的内部符号。 - 基于字符串的反射和 JNI:如果模块通过字面字符串名称(
Class.forName、getDeclaredMethod或 JNIFindClass和GetMethodID)查找类、方法或字段,但没有保留规则或注释,混淆会重命名这些目标,并中断运行时查找。(请注意,在shrink: true和optimize: true下,未添加注释的反射也会失败。)请使用 Keep 注释指南(@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文件(请参阅审核和迁移现有的 keep 规则)。
在添加或保留自定义 proguard.flags 或 keep.xml 文件之前,请验证相应规则是否已由默认基准处理,或者是否可以迁移到代码注释。
了解默认平台保留规则
Soong 会自动将以下全局基准规则传递给 build/soong/java/dex.go 中配置的每个 DEX 编译目标(android_app、android_test 和可安装的 java_library 模块)上的 R8 调用:
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:除非 build 配置停用,否则保留enum类型上的values和valueOf方法。- 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 步:迁移测试驱动型保留规则
使用以下模式之一,将测试驱动的保留规则移出自定义 build .flags 文件:
将实现库链接到测试中 (
static_libs):当android_test练习应用的软件包私有或内部实现细节时,建议的平台架构是将应用源文件放在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能够自由启用obfuscate: true而不会中断测试;避免在生产 APK 中提供仅用于测试的入口点;并在测试代码更改时无需重新构建MyApp。使用
@VisibleForTesting注释测试钩子:如果外部测试 APK 在android_app目标上调用少量内部方法或构造函数,并且重构为.impl库不切实际,请使用@VisibleForTesting注释这些声明。全局build/make/core/proguard.flags基准会自动保留android.**、com.android.**和com.google.android.**软件包中的@VisibleForTesting项,而无需自定义.flags文件。(对于这些命名空间之外的供应商模块,请使用@UsedByReflection或导出的库规则。)跨平台库边界使用
trace_references_from:如果测试无法静态链接目标实现(例如,测试系统服务器服务或framework-connectivity等平台 JAR),请在指向包含测试源的 Java 库配套模块的目标模块上配置trace_references_from。R8 会跟踪并保留辅助字节码引用的所有类和成员。
第 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 规则替换为“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属性引用的方法生成有针对性的保留规则。
广角视图 getter 和 setter keep 规则
# 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 在应用中的每个
View子类和所有关联的库(包括 AndroidX 和 Material 库)中保留每个 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。在Android.bp中使用optimize块(shrink、optimize、obfuscate)在叶目标上显式控制优化行为。
已提交规则中的诊断标志
# 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 中。虽然使用@VisibleForTesting注释方法或配置trace_references_from可避免维护手动-keep规则,但这些符号仍会随生产二进制文件一起提供。 - 建议的修复方法:为了使仅限测试的入口点完全不出现在生产 APK 中,请将应用结构化为一个
.impl库,并使用static_libs将其链接到自插桩测试 APK 中,如第 2 步:迁移测试驱动的保留规则中所述。
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 分析保留半径和规则包含关系
开源 R8 编译器包含一个保留半径分析器(也在 Android Studio 和 Gradle 中作为 R8 配置分析器公开),该分析器可在编译期间测量每条保留规则的确切影响。Soong 将此分析器直接集成到 Android 平台 build 中(在 build/soong/java/dex.go 中配置)。
如需分析平台模块或整个 build 的保留规则,请执行以下操作:
使用
R8_DUMP_KEEP_RADIUS=true环境变量运行 build:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>如果设置了
R8_DUMP_KEEP_RADIUS=true,Soong 会指示 R8 在中间r8keepradius.pb文件中记录out/soong/.intermediates/下每个已编译模块的保留规则指标。对于每个保留规则和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 和合并的 ProGuard 配置,以便进行独立的 R8 重现。R8_DUMP_PERFETTO_TRACE=true:将r8trace.ptrace写入模块的中间目录,以便在 Perfetto 中检查 R8 编译传递。
验证库使用方规则
当 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 的 build-time 分析器一起审核未构建的源目录,Android 平台树在 build/make/core/proguard/tools/ 下包含 pgaudit.py 脚本。此静态分析工具会扫描整个代码库中的 Android.bp、proguard.flags 和 keep.xml 文件,以标记已由 AAPT2 或全局平台基准、宽泛的通配符、-dontoptimize 替换项和仅限测试的 keep 规则涵盖的规则:
# 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 中每条规则的精确类、字段和方法保留半径。