Jacky's blog
首页
  • Android
  • Web
  • Server
  • Python
  • iOS
  • Java
  • Vue
  • 计算机基础
  • AI
  • 全部工具
  • CLI 工具
  • 编辑器
  • 开发工具
  • 构建工具
  • 版本控制
  • 调试工具
  • 网络工具
  • 个人成长
  • 博客
  • 分类
  • 标签
  • 归档
收藏
GitHub (opens new window)

Jack Yang

编程; 随笔
首页
  • Android
  • Web
  • Server
  • Python
  • iOS
  • Java
  • Vue
  • 计算机基础
  • AI
  • 全部工具
  • CLI 工具
  • 编辑器
  • 开发工具
  • 构建工具
  • 版本控制
  • 调试工具
  • 网络工具
  • 个人成长
  • 博客
  • 分类
  • 标签
  • 归档
收藏
GitHub (opens new window)
  • tutorial
  • Jetpack

  • 组件

  • AndroidX

  • 动态化
  • APM

    • apm相关概念
    • Android稳定性治理
    • Android低端机性能优化
    • monkey测试
    • bitmap
    • 大对象监控
    • android内存
    • 移动端的网络优化
    • 记一次anr问题查询ThreadedRenderer
    • 记一次shrink代码减包调研方案
    • proguard
    • R8
      • Shrink code / code optimize
      • R8 优化详解
        • Shrink(代码缩减)
        • Optimize(代码优化)
        • 性能提升
        • 注意事项
        • 最佳实践
      • 构建产物与字节码校验
      • link
    • perfetto
    • mat
  • 组件化

  • 鸿蒙

  • 工具

  • 其他

  • Kotlin

  • android_snip
  • ANDROID 深度技术面试问答
  • android tool
  • android图形系统
  • android
  • apm
Jacky
2024-09-10
目录

R8

# Shrink code / code optimize

本页讨论 Android 应用构建中的 R8。配置示例采用 AGP 8.x 的 Groovy DSL;不同 AGP 版本的默认行为与 DSL 可能变化,使用时对照项目版本。

处理流程

构建流程与作用

Android 构建中的 R8 读取应用及依赖的 class 字节码、入口与 keep 规则,进行分析和优化并生成 DEX。不能把 ProGuard 的“input jar → preverify → output jar”示意流程直接当作 Android R8 的实际构建输出。

操作 作用
Shrink 根据引用关系、入口及规则移除不可达代码
Optimize 重写代码,例如内联、简化表达式、类合并;具体结果取决于完整程序和配置
Obfuscate 缩短类、字段和方法名称,生成映射以便还原堆栈

代码入口并非只有 MainActivity,还包括 Manifest 声明的组件和 keep 规则指定的代码。反射等动态访问不能仅依靠普通静态调用图发现,需要适当的规则。

# R8 优化详解

# Shrink(代码缩减)

AGP 8.x 中,release 的 minifyEnabled true 启用 R8 代码处理。资源缩减由 shrinkResources 等配置控制,不能把资源缩减与代码缩减视为同一个开关。

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                          'proguard-rules.pro'
        }
    }
}
1
2
3
4
5
6
7
8
9
10

示例假设模块中存在 proguard-rules.pro,用于维护自定义规则。默认优化配置已经提供优化规则,不需要额外创建名为 proguard-optimize.pro 的文件作为“开启优化”的必要条件。

Keep 规则示例

# 例:保留确实被反射访问的类及成员
-keep class com.example.MyClass { *; }

# 当所属类被保留时,保留这个成员
-keepclassmembers class com.example.MyClass {
    public void myMethod();
}
1
2
3
4
5
6
7

-keepclassmembers 不会单独阻止整个所属类被删除。规则必须结合真实访问方式验证;不应为省事而保留整个依赖包,过宽的规则会限制缩减和优化。

# Optimize(代码优化)

AGP 8.x 示例中的 proguard-android-optimize.txt 用于启用优化配置;含有 -dontoptimize 的配置会禁止代码优化,需要检查最终合并的规则。不能笼统地说 optimize 必须通过额外配置文件单独开启。

AGP 8.0 起,R8 full mode 默认启用;它与 minifyEnabled、具体优化规则是不同维度的设置。升级 AGP 或调整规则后应重新测试,而不是沿用旧版本观察结果。

方法是否内联、字段或类是否移除,取决于可达性、调用关系、keep 规则及优化器版本。public 修饰符本身不意味着代码必然保留;包级私有也不意味着代码必然删除。应用对外暴露的反射入口、JNI、序列化等契约需要有针对性的规则。

# 性能提升

不使用未经测量的固定百分比概括代码体积、启动时间或内存收益。比较时记录 AGP/R8 版本、构建变体和规则,在相同设备与测试条件下检查:

指标 验证方式
体积 比较同一分发形式的 APK/AAB 或 DEX;记录代码与资源配置
启动与运行时表现 使用一致的启动场景、采样方法和设备
功能正确性 对优化后的 release 产物验证反射、JNI、序列化及动态加载路径
故障定位 保存与发布版本对应的 mapping.txt,用于还原混淆堆栈

# 注意事项

  • 调试产物与经过 R8 优化的 release 产物可能表现不同,测试发布前实际使用的优化产物。
  • 给动态访问补规则前,先确认真实入口;通过构建配置、产物分析和复现结果判断,避免无依据地扩大 keep 范围。
  • configuration.txt、usage.txt 等文件是否生成及其路径取决于配置和版本。规则、产物说明见 ProGuard 记录。

# 最佳实践

调整一次配置后比较结果,保存最终规则与对应构建信息。暂时限制优化用于定位问题时,应记录具体禁用项及原因;解决问题后重新验证目标配置。

具体调研与 smali 观察见 shrink 代码减包记录,其中历史现象不能直接推广到其他 AGP/R8 版本。

# 构建产物与字节码校验

先区分:中间 JAR 不等于 R8 最终输出

build 中确实通常有 .class 和 .jar,但对于 Android 应用模块的 APK 构建,R8 通常直接生成 DEX,不保证存在一份“经过全部 R8 优化的最终 class JAR”。要证明发布代码被如何优化,主要检查 R8 输出的 DEX,再与同一次构建打包进 APK 的 DEX 对照。

简化的代码构建链路如下;实际任务会因 AGP、Kotlin 插件、变体及其他插件而变化:

Java / Kotlin / 生成的源码
  → javac / Kotlin 编译器
  → JVM .class(以目录或 JAR 承载)
  → 汇集应用、依赖及插件处理后的 class,加入规则与运行库信息
      ├─ minifyEnabled=true:R8 分析、缩减、优化、混淆及 DEX 生成
      └─ minifyEnabled=false:通常经 D8 转换及 DEX 合并,不做 R8 全程序缩减
  → classes.dex、classes2.dex……
  → 与编译后的资源、Manifest、assets、native 库等打包、对齐、签名
  → APK
1
2
3
4
5
6
7
8
9

资源编译由 AAPT2 等任务处理,与代码链路有交汇,但不是“资源先转换成 class 再交给 R8”。Java 字节码的 desugaring 也属于构建过程;启用 R8 时,不应固定理解为独立的“R8 输出 JAR → D8 输出 DEX”两步。R8 官方说明 (opens new window)明确区分了 DEX 与 classfile 输出模式。

在 build 中找什么

以下为常见位置或搜索范围,不是跨版本稳定的路径契约。release 是示例变体;有 product flavor 时可能是 demoRelease 等。

产物 常见位置或查找范围 用途与限制
Java 编译结果 app/build/intermediates/javac/<variant>/.../classes/ 检查 javac 输出,通常在 R8 之前
Kotlin 编译结果 app/build/tmp/kotlin-classes/<variant>/ 检查 Kotlin 编译结果,可能有额外生成的类
中间 JAR app/build/intermediates/ 下的 classes.jar 等 必须确认生成任务;可能是依赖、打包的 class,也可能只包含 Java 资源
R8 的 DEX 输出 app/build/intermediates/ 下,对应 minify<Variant>WithR8 输出的 .dex 直接观察优化结果;检查全部 classes*.dex,不能只看主 DEX
R8 报告 通常在 app/build/outputs/mapping/<variant>/ 用于辅助确认名称与配置,不替代字节码检查
发布 APK 通常在 app/build/outputs/apk/ 最终交付代码的核对对象

在 Android 项目根目录执行;模块名不是 app 时替换对应路径:

# 先构建目标变体;检查日志中实际的编译与 R8 任务
./gradlew :app:assembleRelease --info

# build 常被 Git 忽略,搜索时需要 --no-ignore
rg --files --hidden --no-ignore app/build \
  -g '*.class' -g '*.jar' -g '*.dex' \
  -g 'mapping.txt' -g 'configuration.txt' -g 'usage.txt' -g 'seeds.txt'
1
2
3
4
5
6
7

不要把其他变体或旧构建遗留的产物混进比较;搜索结果本身不能证明文件由当前 R8 任务生成。结合任务日志及其输出确认来源。若要做长期自动化收集,优先使用对应 AGP 版本的 Variant/Artifacts API,避免依赖内部目录;其中 ScopedArtifact.CLASSES (opens new window) 表示 class 产物,不是“最终优化 DEX”的替代物。

检查优化前的 JVM 字节码:javap

以下路径、类名均为示例,替换为搜索到的实际产物:

# 先看 JAR 内容:有 .class 才能按 JVM class 检查
jar tf path/to/classes.jar

# JAR 中的类
javap -classpath path/to/classes.jar -c -p -s com.example.OptimizeProbe

# 或直接检查 class 目录中的类
javap -classpath app/build/tmp/kotlin-classes/release \
  -c -p -s com.example.OptimizeProbe
1
2
3
4
5
6
7
8
9

-c 显示指令,-p 包含私有成员,-s 显示类型描述符;需要常量池、注解等信息时再加 -v。javap 检查的是 JVM .class,不能拿它直接读取 DEX。JDK javap 文档 (opens new window)

这一阶段适合建立对照:方法、字段是否原本就存在,调用是否已被 javac/Kotlin 编译器改写。例如常量已在编译阶段内联,就不能把最终看不到字段访问完全归功于 R8。JAR 只是容器;其中若装的是 classes.dex,仍应使用 DEX 工具。

不反编译整个 APK,直接检查 R8 的 DEX

Android SDK Build Tools 中的 dexdump 可以反汇编 DEX 的代码段。下例的 SDK 版本、DEX 路径需要替换;-d 是反汇编,-c 只校验校验和,后者不能验证优化是否符合预期。AOSP dexdump 参数实现 (opens new window)

# 替换成实际 SDK 工具和当前 R8 任务输出的 DEX
/path/to/android-sdk/build-tools/34.0.0/dexdump \
  -d path/to/r8-output/classes.dex
1
2
3

如果要核对打包后的发布产物,SDK Command-Line Tools 的 apkanalyzer 可以直接输出指定类的 smali,无需把整个 APK 反编译成 Java:

# 假设 apkanalyzer 已加入 PATH,APK 路径替换为实际输出
apkanalyzer dex list app/build/outputs/apk/release/app-release.apk
apkanalyzer dex packages --defined-only app/build/outputs/apk/release/app-release.apk
apkanalyzer dex code --class com.example.OptimizeProbe \
  app/build/outputs/apk/release/app-release.apk
1
2
3
4
5

已混淆时,先查同一构建的 mapping.txt,把 --class 换成 DEX 中的实际类名。只看某个方法可再加 --method 'methodName()V',参数采用 DEX 方法描述符,不能写 Java 风格签名。apkanalyzer 官方命令说明 (opens new window)

怎样判断优化真的发生了

要验证的现象 应查看的证据 容易误判的情况
未使用的类或成员被删除 编译后的 class 中存在,最终全部 DEX 中无对应定义;结合 usage.txt 类可能改名或合并;原名搜不到不等于删除
方法被内联 查看调用点及调用指令,确认逻辑是否转移进调用者 方法定义消失不代表功能消失,也不一定就是内联
表达式或分支被简化 对比编译后 JVM 指令与最终 DEX 的运算、条件跳转 两者指令集不同,不能逐条按文本直接比较
配置确实生效 检查合并的 configuration.txt、构建日志及 DEX 只看手写规则会漏掉默认规则和依赖的 consumer rules

可临时用 -dontobfuscate 降低名称追踪难度,但最终仍需验证真实发布配置。不要为观察一个类随手加 -keep class ... { *; }:它会限制缩减和优化,使实验条件发生变化。mapping.txt 是名称/位置追踪信息,usage.txt 是缩减报告,seeds.txt 是保留入口报告,都不是完整的优化执行日志。

固定 AGP/R8 版本、变体、输入与规则,一次改变一个条件;先检查字节码,再运行优化后的产物验证行为。反编译得到的 Java 是工具重建的表示,不能单凭其排版或语法变化证明某项 R8 优化。

确实需要优化后的 class JAR 时

R8 本身支持 --classfile,可以用于单独的 JVM 字节码实验;这不同于 Android 应用默认的 DEX 输出模式。下面假设已有指定版本的 r8.jar、完整输入、运行库和入口规则,路径均须替换:

java -cp path/to/r8.jar com.android.tools.r8.R8 \
  --release --classfile \
  --output output.jar \
  --pg-conf proguard.cfg \
  --lib path/to/runtime-library.jar \
  input.jar
1
2
3
4
5
6

再用 jar tf output.jar 和 javap 检查输出。运行库、依赖和入口规则必须匹配目标程序;缺失依赖或没有正确入口会让实验无效。classfile 模式的结果不能等同于 Android DEX 后端的最终结果。

另一个例外是启用缩减的 Android library/AAR 构建,它可以输出处理后的 class JAR。AGP 8.4 的库类缩减说明 (opens new window)记录了库子项目发布缩减后类的行为变化。因此,判断一个 JAR 是否经过 R8,应确认它所属模块、变体与生成任务,不能只凭文件名叫 classes.jar。

# link

  • Android 官方:启用 R8 优化与版本差异 (opens new window)
  • Android 官方:full mode、默认优化配置与 dontoptimize (opens new window)
  • Android 官方:keep 规则 (opens new window)
  • 其他
    • Jake Wharton 的个人博客 (opens new window) 深入字节码理解每项优化的实际原理
#Android#APM#performance#guide
上次更新: 2026/10/18, 22:14:48
proguard
perfetto

← proguard perfetto→

最近更新
01
Codex 使用与会话管理指南
10-17
02
Tailscale 入门:让自己的设备跨网络互联
10-15
03
GitHub CLI(gh)工作流与 CI 排查
10-08
更多文章>
Theme by Vdoing | Copyright © 2019-2026 Jacky | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式