Android 包体积优化(四):深入 ReDex——StripDebugInfo、InterDex 与 DEX 重排

序、众所周知,每一次开始写这种框架系类的文章的时候,就是我在找工作了,有闲暇时间可以写点东西。哈哈哈哈,26年找工作,简直就是。。。。。

重要的事情说三遍

有岗位的请 CALL 我,谢谢大家了。。。。

有岗位的请 CALL 我,谢谢大家了。。。。

有岗位的请 CALL 我,谢谢大家了。。。。

前面三篇,我们已经分别讲了:

  • APK 结构与体积分析

  • DEX、R8 与依赖治理

  • Resources、图片、SO、Assets 优化

这些基本覆盖了 Android 包体积优化中最常见的手段。

但如果:

R8 已经开启,依赖也清理过了,DEX 仍然占整个 APK 很高比例,还能不能继续优化?

这时候就可以继续往更深一层走:

DEX Bytecode Optimization。

这里我实际使用的是:

ReDex。


一、ReDex 是什么?

ReDex 是 Meta 开源的一套 Android DEX 优化工具。

它和普通 APK 分析工具不一样。

APK Analyzer 更多是:

看 APK 里面有什么。

ReDex 则会真正:

读取 DEX → 分析 DEX → 修改 DEX → 重新生成 APK。

可以简单理解成:

APK

解析 DEX

建立内部代码结构

执行多个 Optimization Pass

重新布局 / 修改 DEX

重新输出 APK

所以 ReDex 属于:

DEX 后处理优化工具。


二、已经有 R8 了,为什么还需要 ReDex?

这是最容易被问的问题。

R8 本身已经能够完成:

  • Shrinking

  • Optimization

  • Obfuscation

  • DEX 生成

所以:

ReDex 并不是用来替代 R8。

两者更适合理解成:

R8

位于 Android 正常 Build Pipeline 中。

源码 / Bytecode

R8

DEX

APK

而 ReDex:

是在 APK / DEX 已经产生以后继续处理。

流程变成:

R8

APK

ReDex

Optimized APK

所以更准确地说:

R8 是 Android 构建链中的主优化器,ReDex 可以作为后处理层继续寻找 DEX 优化空间。


三、ReDex 的核心:Pass

ReDex 并不是:

一个算法把 APK 自动变小。

它更像一个:

Optimization Pass Framework。

不同 Pass:

负责不同事情。

例如:

某个 Pass:

处理 Debug 信息。

某个 Pass:

调整 DEX 中 Class 的布局。

某个 Pass:

做代码分析。

所以使用 ReDex 时,真正需要关注的是:

到底开启了哪些 Pass。

Pass 开得越多:

不一定越好。

因为 Bytecode 修改越激进:

潜在风险也越高。

所以我在实际测试时,没有一开始就堆很多 Pass。

目前只选择了:

StripDebugInfoPass

InterDexPass

先建立一个相对容易验证的优化组合。


四、StripDebugInfoPass

DEX 中除了真正执行的 Bytecode,还存在一些:

Debug Metadata。

例如:

  • Source File 信息

  • Line Number

  • Local Variable 信息

  • Debug Position 信息

这些数据:

对于开发和 Crash 定位非常有价值。

但从 App 正常运行角度来看:

很多 Debug Metadata 并不是业务逻辑本身。

所以:

StripDebugInfoPass

可以根据配置:

删除或精简部分 DEX Debug 信息。

最终效果就是:

DEX Size ↓

APK Size ↓


五、StripDebugInfo 的代价

这里一定要注意:

Debug 信息不是越少越好。

因为如果过度删除:

线上 Crash Stack Trace:

可能变得更难定位。

所以 Release 包优化时:

应该在:

包体积

和:

可诊断性

之间取得平衡。

也就是说:

StripDebugInfo 不是简单:

把所有 Debug 信息全删掉。

而应该根据项目线上 Crash 分析需求选择配置。


六、InterDexPass 是什么?

如果说:

StripDebugInfo

主要解决:

DEX 里面哪些信息可以减少。

那么:

InterDex

更关注:

Class 应该怎么放进不同的 DEX。

对于 MultiDex 应用:

可能存在:

classes.dex

classes2.dex

classes3.dex

......

Class 怎么分布:

并不是完全无所谓。

例如某些启动阶段经常使用的 Class:

如果分散在很多 DEX 中:

可能导致更差的访问局部性。

所以 InterDex 的核心可以理解成:

重新调整 Class Ordering 和 Cross-DEX Distribution。


七、InterDex 不是"减少 DEX 数量工具"

这个概念要特别说一下。

很多人看到我的实际结果:

DEX:

7 → 5

很容易理解成:

InterDex 的作用就是减少 DEX 数量。

其实不准确。

InterDex 真正解决的是:

DEX Layout。

也就是:

哪些 Class 放在哪个 DEX。

最终 DEX 数量:

可能减少

也可能不变

甚至在某些策略下出现不同变化。

所以:

7 → 5 是这次实际测试结果,不是 InterDex 的固定效果。


八、为什么 DEX Layout 值得优化?

假设 App 冷启动经常访问:

Class A

Class B

Class C

Class D

原来的布局:

DEX1:

A

DEX4:

B

DEX6:

C

DEX2:

D

那么启动阶段可能需要访问多个 DEX。

如果经过合理布局:

DEX1:

A

B

C

D

启动所需 Class:

更加集中。

这种:

Locality

就是 DEX Layout 优化的重要方向之一。

所以 InterDex 的意义并不只是:

包体积。

它还可能和:

启动性能

I/O

运行时访问模式

产生关系。


九、我的实际测试结果

为了测试 ReDex,我准备了一个故意引入大量依赖的 Android 项目。

其中包括:

  • FastUtil

  • Guava

  • BouncyCastle

  • Protobuf

  • Tink

原始 APK:

12,557,258 Bytes

大约:

12.56 MB

DEX:

7 个

Class Defs:

20,357

Method ID Entries:

273,702

然后执行:

StripDebugInfoPass

InterDexPass

ReDex 整个优化过程:

大约耗时:

53.7 秒

最终:

Optimized APK:

10,350,190 Bytes

DEX:

5 个

Class Defs:

20,362

Method ID Entries:

275,958

最终减少:

2,207,068 Bytes

体积下降:

17.58%

DEX:

7 → 5

对于这次实验来说:

效果还是比较明显的。


十、一个很有意思的问题:Method ID Entries 为什么反而增加了?

这个结果特别值得单独拿出来讲。

优化前:

273,702

优化后:

275,958

也就是说:

APK:

小了。

DEX:

少了。

但是:

Method ID Entries:

反而增加了。

是不是优化失败了?

不是。


十一、原因在于 Method ID Table 是 per-dex 的

前面第二篇讲过:

每一个 DEX:

都有自己的:

Method ID Table。

所以:

classes.dex

有自己的 Method References。

classes2.dex

也有自己的。

当 InterDex 重新调整:

Class Distribution

以后:

不同 DEX 中需要引用的方法集合:

也会变化。

例如:

以前 Class A 和 Class B:

在同一个 DEX。

后来:

被重新放到两个 DEX。

那么:

不同 DEX:

可能分别需要保存相关 Method Reference。

于是:

所有 DEX 的:

method_ids_size

加起来:

完全可能出现增加。


十二、这说明包体积优化不能只盯一个指标

如果只看:

Method ID Entries:

那么会得出:

优化以后更差了。

但实际上:

APK:

减少:

17.58%。

所以真正判断优化效果:

应该综合看:

  • APK Size

  • DEX Total Size

  • DEX Count

  • Class Defs

  • Method ID Entries

  • Runtime Correctness

而不是:

只看某一个数字。

这也是做 APK 优化比较容易踩的一个坑。


十三、ReDex 优化完成就算成功了吗?

还不能。

ReDex:

Successfully Finished

只能说明:

DEX 重写完成了。

它不能保证:

修改后的 APK 一定能正常运行。

Bytecode 被修改以后:

理论上可能出现:

  • VerifyError

  • ClassNotFoundException

  • NoClassDefFoundError

  • Startup Crash

  • Runtime Crash

所以:

APK 变小只是第一步。


十四、必须进行 APK Validation

我最终没有只看:

APK Size。

还继续进行了:

ZIP Alignment 检查

APK Signature 检查

真机安装

App 启动

PID 检查

Logcat 检查

最终优化后的 App:

成功安装并启动。

执行:

pidof

可以正常获得 App Process。

同时没有发现 App 自身的:

  • FATAL EXCEPTION

  • VerifyError

  • NoClassDefFoundError

  • Startup Crash

到这个阶段:

我才认为这组优化:

基本验证通过。


十五、真机验证还踩了一个坑

Logcat 中:

出现了:

ClassNotFoundException

第一眼看:

很容易怀疑:

ReDex 把 Class 搞丢了。

结果继续分析发现:

这个异常来自:

MIUI 系统进程。

而不是:

目标 App。

这说明:

自动验证不能简单写成:

发现:

ClassNotFoundException

直接失败。

必须结合:

  • package

  • PID

  • process

  • AndroidRuntime

  • stack trace

  • startup time window

判断:

异常到底是不是目标 App 的。

这个问题也直接引出了我后续准备做的:

ADB 自动 Validation。


十六、什么时候应该考虑 ReDex?

我不建议一上来:

就使用 ReDex。

比较合理的顺序是:

APK Analyze

R8

Keep Rule 优化

Dependency 治理

Resources / SO / Assets 优化

重新 Analyze

如果 DEX 仍然存在较明显的优化空间

再考虑 ReDex。

因为:

最好的优化往往不是:

用复杂算法处理无用代码。

而是:

直接删除无用代码和依赖。


十七、总结

ReDex 可以理解成:

APK / DEX 构建完成之后的一层 Bytecode Optimization。

它不是:

R8 的替代品。

而是:

更深一步的 DEX 后处理工具。

这次实际测试主要使用:

StripDebugInfoPass

负责:

精简 DEX Debug Metadata。

InterDexPass

负责:

优化 Class Ordering 和 DEX Distribution。

最终:

12.56 MB → 10.35 MB

体积下降:

17.58%

DEX:

7 → 5

但更重要的不是:

"减少了 17.58%。"

而是让我进一步认识到:

包体积优化不能只关注 APK 是否变小,还必须关注优化后的 APK 是否仍然正确。

所以真正完整的优化流程应该是:

Analyze

Optimize

Re-Analyze

Compare

Validate

而不是:

APK 变小

结束。


下一篇

《Android 包体积优化(五):从人工优化到自动化------Gradle Plugin、真机验证与 CI Gate》

最后一篇会把整个系列串起来:

从人工使用 APK Analyzer,

发展到:

Gradle Plugin

自动 APK Analysis

Diagnosis

ReDex

Compare

ADB Validation

CI Gate

真正把包体积优化从:

一次性专项

变成:

长期工程化治理。

相关推荐
安卓与AI研习社1 小时前
主线程明明是空闲的,为什么仍然 ANR?3 个真实 Trace 的证据链复盘
android
恋猫de小郭3 小时前
Dart 3.13 大更新,感觉比 Flutter 更带劲
android·前端·flutter
大锅盖13 小时前
HarmonyOS 6.1.1 ArkWeb:交付门户下载资料前-为什么必须先登记下载代理与来源字段
android·华为·harmonyos
峥嵘life11 小时前
Android16 311Y3 EAP-TLS 网络连接失败分析与修复总结
android·开发语言·人工智能·php
Kapaseker12 小时前
破坏性更新 - 解读 Jetpack Compose 1.12
android·kotlin
雨白13 小时前
我的 UML 学习笔记:结合 Android 实例看懂 4 种常用图表
android·架构
怣疯knight13 小时前
快速判断apk有没有支持16kb页面标准
android
风流 少年14 小时前
Spring AI 2.0:Advisor
android·人工智能·spring
YM52e16 小时前
分页查询的基石:ArkTS 为鸿蒙商品列表设计 LIMIT/OFFSET 的表
android·学习·华为·harmonyos