文件页-编译产物(dex/odex/vdex)解析
来源:飞书 wiki(obj_token: OG4ddQRBQoet06xJgSqcYY79nzc)
注:本文档所有图片均因飞书侧权限限制(403)无法下载,已用文字描述占位保留;4 个非图片附件(APK/Python/txt)MinIO 不可用,已记录于文末。
产物概览
从手机中拉出:
adb shell pm path com.xiaomi.market
# package:/data/app/.../base.apk📷 图片描述:Windows 系统下从手机中拉出的 com.xiaomi.market 应用文件目录截图(token: VVvrbecYao0cE5xPDw1cd7kFnSg)
📷 图片描述:com.xiaomi.market 应用的 oat/arm64 目录,含 base.art (3,017 KB)、base.odex (16,334 KB)、base.vdex (48,572 KB)(token: ErbpbE749o6UyIxkjuGcOuNGnTd)
📷 图片描述:com.xiaomi.market 的 lib/arm64 目录下多个 SO 文件列表,如 libglide-webp.so、libpdetector.so 等,大小 43KB~6007KB(token: Qexxb2IGfobPK5xhklEcPJNLnAh)
一、解析base.apk/class.dex
APK / DEX:影响体积的配置与可推断点
是否开启 R8(minify + shrink)
R8 是 Android 项目默认的代码优化工具,集成了 minify(代码压缩 / 混淆)和 shrink(资源收缩)两大核心功能,用于减小 APK 体积并保护代码。
- minify 针对字节码:删除无用代码、混淆类/方法名,并可能做一定优化,依赖
minifyEnabled true。- shrinkResources 针对资源文件:基于 minify 的可达性结果删除无用资源,需
shrinkResources true且minifyEnabled true才可能生效。- 核心配置通常在
app/build.gradle,自定义 keep 规则在proguard-rules.pro。启用后建议保留mapping.txt并做完整回归。
- 配置方式(gradle/bp文件):
📷 图片描述:Android 构建配置 release 包代码优化与混淆设置,启用 minifyEnabled、shrinkResources,混淆规则 proguard-android-optimize.txt 和 proguard-rules.pro(token: OHY0bgLewopj2WxJqPUcwSIin6e)
-
产物特征:
-
类/方法/字段总量显著减少;类名大量变成短名(a/b/c):强信号说明开启了混淆/压缩(但仍建议以构建配置与 mapping 为准)。
📎 附件:base.apk(application/vnd.android.package-archive,token: KQn3bHFW3oPp6XxlssYczEekn8f)— MinIO 不可用,未下载
📷 图片描述:Android Studio 中 Dex 文件类结构界面,含 “Show Deobfuscated Names” 按钮,Class 分类显示 e6/dalvik/oa 等类文件夹(token: KJGKbnQMtohnInxtdXBc5mZlno6)
-
关于 shrinkResources:仅凭
resources.arsc/资源目录变小无法归因。
是否启用/是否生效建议以:
build.gradle配置为准。 -
-
局限:仅靠 dex 很多时候只能”强推断”,无法证明用了哪些 R8 规则。
-
无干扰apk验证:上图关闭isMinifyEnabled、isShrinkResources;下图开启
📷 图片描述:com.example.myapplication 进程内存信息统计,Native Heap/Dalvik Heap/Dalvik Other 等 Pss、Private Dirty、Private Clean 数据(token: N9vybEZh7o6sL3x0405cMDY1nlc)
📷 图片描述:com.example.myapplication(pid 26759)内存信息,Native Heap Total 15895/Private Dirty 15084 等(token: Qeg2blN6fo5McFxerQXc4PVnnAN)
优化明显,收益约10M,release APK一般默认都会带上
调整保留规则(业务自行调整)
- 配置方式
📷 图片描述:Android 项目 proguard 相关配置文件和代码,左侧 proguard-rules.pro,右侧 build.gradle.kts 中 identifiable=true(token: Som4bXUsWol5m6xAvj2cnvMAnVd)
参考说明配置链接:https://developer.android.com/topic/performance/app-optimization/keep-rules-overview?hl=zh-cn
- 局限:从产物中无法可靠推断其配置。不了解 R8 规则时不建议随意调整,以免引起运行时异常;如需优化体积建议先从”减少不必要 keep”入手,并确保有可回滚与充分测试。
- 无干扰apk验证:上图未添加keep规则;下图添加AI给出的规则
📷 图片描述:MEMINFO 输出,pid 27994 进程 com.example.myapplication,Native Heap Pss Total 15883、Dalvik Heap Pss Total 7543、Dalvik Other Pss Total 1804 等(token: R2Z7b80v8o6d8vxwbvXcZembnQb)
📷 图片描述:MEMINFO 输出,com.example.myapplication(pid 28359),Native Heap Total 14795、Private Dirty 14784 等(token: TLfBb1L9LofQIlxopbQcyFt4nvh)
有一定优化收益,约1M,不如预期明显,建议灵活调整keep规则
multidex / 分包策略(classesN.dex 数量)
多 dex 支持,用于解决单个 dex 文件方法数不能超过 65536 的限制
- 产物特征:classes.dex..classesN.dex
📷 图片描述:base.apk 编译产物结构,assets 7.3MB,classes.dex 4.1MB、classes2.dex 3.5MB,resources.arsc 7.3MB(token: X5pdbu77coBnQlxxn6RcMC7znhb)
- 影响:
- 多 dex 主要改变 dex 的组织方式,进而影响安装后 dexopt/验证/类加载过程中的 IO 分布。
- 对 APK 体积的影响主要体现在 压缩局部性(不保证更大或更小);未压缩总大小通常不因是否多 dex 而有数量级变化。
- 是否影响启动/内存需要结合运行时证据(
cmd package dump的 dexopt state、/proc/<pid>/smaps映射与 PSS/Private Dirty)验证。
Dex 版本(035/039/040…)
- 产物特征:dex header magic:dex magic: dex\n035\0 → dex version = 035
📷 图片描述:不同 DEX 版本对应 Android API 级别及核心特性,035 版本对应 API 14+(token: LB4ybaf9MoORrmx925acgIlmn1b)
- 影响:不同版本对应不同格式能力(例如 cdex/compact-dex 在某些链路上影响体积/加载)。
📷 图片描述:高 DEX 版本(039+)与低 DEX 版本(035)在兼容性、特性支持、性能、APK 体积、构建速度等差异对比表(token: OljebE5sDowcFMxjWiWcgKE8n0c)
- 配置方式:建议自动选择,或者手动尝试:
📷 图片描述:app/build.gradle 中通过 compileOptions 和 kotlinOptions 控制 Dex 版本,Java 11、coreLibraryDesugaringEnabled=true(token: NDSfbxP5uoaxAkxHmFtcBOEKnbe)
- 局限:版本不直接告诉你”用了哪个 D8/R8 flag”。需要使用dexdump解析,或者自行用脚本解析:
dexdump -f classes.dex > dex_header_info.txt
Processing ‘C:\Users\bobo.wang\Desktop\dex\com.xiaomi.market-xy5vGUO4lRVZfVyVS6i_bw==\base\classes.dex’…
Opened ‘C:\Users\bobo.wang\Desktop\dex\com.xiaomi.market-xy5vGUO4lRVZfVyVS6i_bw==\base\classes.dex’, DEX version ‘035’
DEX file header: magic : ‘dex\n035\0’ checksum : 356a3959 signature : 7364…060e file_size : 11194280 header_size : 112 link_size : 0 link_off : 0 (0x000000) string_ids_size : 89095 string_ids_off : 112 (0x000070) type_ids_size : 11471 type_ids_off : 356492 (0x05708c) proto_ids_size : 15048 proto_ids_off : 402376 (0x0623c8) field_ids_size : 43629 field_ids_off : 582952 (0x08e528) method_ids_size : 65455 method_ids_off : 931984 (0x0e3890) class_defs_size : 5680 class_defs_off : 1455624 (0x163608) data_size : 9556896 data_off : 1637384 (0x18fc08)
class/method数量对比
如上图解析,class_defs_size : 5680/method_ids_size : 65455
可从 dexdump 的 class_defs_size、method_ids_size 宏观对比对照机,判断”dex 大”的主要原因是”方法/类型数量”还是”字符串/调试信息”等。
是否 uncompressed dex / alignment(zip 条目压缩方式)
zipAlignEnabled过时的原因 随着Android开发工具链的进化,zipAlign 这一过程已经被自动化
📷 图片描述:Android Studio 代码片段,isZipAlignEnabled 标记为已废弃,包含 getDefaultProfile、baselineProfile、compileOptions sourceCompatibility(token: XcWFbuIYRogRwUxxxUIcOu8znLg)
产物特征:zip entry method=stored/deflated、对齐:dex 字节码文件默认开启 ZIP 压缩,能显著减小体积
📷 图片描述:base.apk 中 assets 目录文件信息压缩情况,classes.dex 压缩比 8.6%(token: FSNBbOn1foWZgtxGm0lcvSc9nCh)
影响:不压缩 dex 会增大 APK 体积但可能改善安装/加载/映射;压缩则相反。配置方式:读 zip central directory(压缩方法/压缩前后大小)。
📷 图片描述:Android 项目构建配置代码,release 构建类型中 ZipAlignEnabled true 被红框标注(token: XHesbBg8VoiLlHxHuSecBNwPnj1)
是否开启dex布局优化
- 基本概念
Baseline Profile:本质是一种预生成的、标准化的
*.prof配置文件(和你之前了解的 Profile 文件同源),由开发者在测试环境生成,包含 App 核心流程(启动、首页渲染等)的热点方法、类加载顺序等信息,用于指导 ART 编译器进行更精准的优化。
dexLayoutOptimization:Baseline Profile 中的关键开关,用于开启 Dex 文件的布局优化—— 简单说,就是根据 Baseline Profile 记录的类 / 方法调用顺序,重新排列 Dex 文件中的字节码布局,让核心类 / 方法在 Dex 文件中物理上连续存储,减少 ART 加载 Dex 时的磁盘 I/O 和内存映射开销。
- 配置方式:
📷 图片描述:Android 配置截图(token: OtgIbdvhioxgg0x3EKYcwvWgnfb)
- 影响:开启之后,让核心类 / 方法在 Dex 文件中物理上连续存储,减少 ART 加载 Dex 时的磁盘 I/O 和内存映射开销,基本等价于热点重排
- 无干扰apk验证:上图开启布局优化;下图未开启布局优化
📷 图片描述:Android 系统 meminfo 输出,Native Heap、Dalvik Heap、Dalvik Other 等 Pss/Private Dirty/Private Clean 数据,Native Heap Pss 16508(token: Kf3Db4GfzozZhkxi9PCcxIhonog)
📷 图片描述:adb 命令行输出的 MEMINFO,Native Heap、Dalvik Heap、Dalvik Other 等详细数据,Dalvik Heap Pss Total 约 7MB(token: Nb7SbacWAoczwIx8lk2cJEFJnUc)
收益不明显,可能更倾向于IO的优化
是否开启 debuggable / 资源调试信息
- 产物特征:
- Manifest 的 android:debuggable=true
- dex 里存在更多调试相关信息(但 dex 本身不等于完整 debug 符号)
二、解析 odex/vdex/oat:
- .odex:通常是 ART AOT 编译产物,常见为 ELF 容器,里面承载 OAT 代码段与元数据。
- .vdex:验证/快化/以及 dex 内容的承载体。很多”dex 是否在编译产物里”要看 vdex。
- .art:app image(类/对象图像),可影响冷启动性能与部分内存映射。
framework一般是使用boot_xxx.oat来命名,应用基本上都是.odex/.dex,对于market应用来说,oat=odex。
影响体积与内存的 ART 编译配置推断:
这部分更多是”运行时编译产物”,不是 APK 构建参数,但确实影响 odex/vdex 大小 和运行时映射/内存。
compiler-filter(verify / speed-profile / speed / everything)
-
获取途径:
-
adb shell cmd package dump
的 Dexopt state(最推荐)
📷 图片描述:com.xiaomi.market 的 Dexopt state,arm64 状态为 speed-profile,原因 bg-dexopt、primary-abi,base.odex 16333Kb,base.vdex 48571Kb(token: F8G8btWEJoapfqx863Kc80f7nAd)
-
通过自写脚本解析:python inspect_art_outputs.py” —dir “C:\Users\bobo.wang\Desktop\dex\com.xiaomi.market-xy5vGUO4lRVZfVyVS6i_bw==\oat\arm64”
📎 附件:inspect_art_outputs.py(text/x-python,token: SKBJbveSNoouKqx0IEqcNSs1nvc)— MinIO 不可用,未下载
-
生成json文件:
📷 图片描述:dex 解析文件夹文件列表,inspect_art_outputs.result.json 高亮显示,修改日期 2026/2/5 11:30,3KB(token: HSQbbZGnGo878nx1rdbcHqVwnwd)
📷 图片描述:json 文件示例,compiler_filter 和 compiler_filter_arg 字段均为 speed-profile(token: IVK6byPc0oghbpxSSnrcX2O7nAG)
-
多种途径均可该进程编译方式为:speed-profile
- 一般需要兼顾运行效率和空间体积大小,speed的odex(oat)文件会大于speed-profile,但是执行效率更高
speed往往产生更高 AOT 覆盖率,因此产物可能更大;speed-profile依赖 profile,产物通常更小。- 产物更大不等价于运行时内存更大:是否增加 PSS 取决于启动路径与 fault-in 情况(建议以
smaps的 Pss/Private Dirty 佐证)
-
无干扰apk验证:verify/speed-profile/speed
📷 图片描述:com.example.myapplication(pid 12472)MEMINFO,Native Heap Pss Total 13332 等(token: RdCGbrFkUoDhsAx1U50cFGHlneh)
📷 图片描述:MEMINFO 信息截图(token: OPZMb4XP3ogUANxZSXicVQninSh)
📷 图片描述:com.example.myapplication(pid 12703)MEMINFO,Native Heap Pss 10696、Private Dirty 10656 等(token: DjBPbhu6ZolCE0x0q2scF0PGn5b)
编译程度基本可以理解为空间换时间,编译等级越高,dex文件越大,内存占用越大
odex/vdex可用信息提取
📎 附件:inspect_art_outputs.py(text/x-python,token: TtMwbvJumoNbbXxSSWqc5EjKnwe)— MinIO 不可用,未下载
运行inspect_art_outputs.py脚本得到:
📷 图片描述:inspect_art_outputs.py 脚本运行输出,含 base_odex、compiler_filter、compilation_reason、target_sdk_version 等字段(token: IWKIbEkMPoNjA3xJV4fccc1enDf)
- oat_kv.compiler_filter:本次编译过滤器(常见:verify/quicken/speed-profile/speed/everything 等)
- dex2oat.cmdline:可查看ART学习-编译模式与编译产物中”dex2oat 详解”此节
- dex2oat.compilation_reason:编译触发原因(如 bg-dexopt / install 等)
- dex2oat.image_format:app image 压缩格式(lz4 等)
- vdex.version:vdex 格式版本(027)
- embedded_dex.likely_format:脚本用魔数扫描推断 dex 格式(dex035/cdex 等)
当前样例(com.xiaomi.market)(Android 16,arm64)解析得到:
- base.odex:ELF64 little-endian(ODEX 属于 OAT/ART 产物体系的 ELF 容器)
- compiler-filter = speed-profile
- compilation-reason = bg-dexopt
- target-sdk-version = 35(Android 16)
- app image:—image-format=lz4,且 base.art 存在
- base.vdex:vdex 版本 027
- dex 嵌入位置:在 base.vdex 中发现 dex\n035\0(offset=80),未发现 cdex001\0
其他解析odex/vdex/oat方式
设备侧(最权威):用 oatdump 解析 ODEX/OAT
- 工具获取
Android 16 设备通常在:
/apex/com.android.art/bin/oatdump
/apex/com.android.art/bin/vdexdump(不一定存在,本设备缺失)
- 解析命令(header-only,建议先用这个)
/apex/com.android.art/bin/oatdump \
--oat-file=/data/app/.../com.xiaomi.market-.../oat/arm64/base.odex \
--header-only- 从 oatdump 里看什么
重点字段:
- INSTRUCTION SET / FEATURES
- DEX FILE COUNT
- KEY VALUE STORE:
- compiler-filter
- compilation-reason
- dex2oat-cmdline(完整命令行,含 —image-format、—compiler-filter、-Xtarget-sdk-version 等)
- 没有 vdexdump 时,如何解析 VDEX(可用方法)
ROM 没带 vdexdump,常见替代方法: 魔数扫描(最快速判断 dex 是否在 vdex、dex/cdex)
读取 base.vdex 头:vdex + version
扫描 dex\n035\0 / cdex001\0
三、primary.prof文件(非内存重点)
primary.prof 文件本体通常不是主要内存占用,但它会影响 dex2oat 的 AOT 覆盖范围与后续 JIT 频率,从而间接影响:.odex 映射/fault-in、JIT code cache 行为与启动期资源占用。
find /data -name “*.prof” | grep “com.xiaomi.market” /data/misc/profiles/ref/com.xiaomi.market/primary.prof
speed-profile模式下的流程起来:
- App 运行:用户打开 App,ART 记录哪些方法跑得最勤,写入
primary.prof(Profile)。- 后台编译 (bg-dexopt):设备空闲时,
dex2oat启动。
- 读取:
dex2oat读取base.vdex拿到原始 DEX 代码。- 查询:
dex2oat读取primary.prof知道哪些是热点方法。- 生成:
dex2oat只把热点方法编译成高效机器码,写入base.odex。- 下次启动:App 直接加载
base.odex(机器码)跑得快,同时依赖base.vdex做类结构校验。如果
primary.prof的热点函数命中很高,jit 就会少;如果命中低,运行过程中就会 jit 频繁,热点函数在运行期间被编译为机器码并缓存。热点重排类似上文提到的布局优化,主要解决上述问题,目前来看收益比较小。
四、so 文件解析
so 对”内存”的影响往往比 dex 更直接:
.text/.rodata多为文件映射(可共享干净页居多,PSS 取决于是否 fault-in).data/.bss更容易贡献私有页/匿名页(更接近真实内存压力)
因此不要只看 so 的”文件大小”,要结合段布局与运行时 smaps。
验证结果汇总
目标文件:/data/app/~~qonKcRzUxPlYkHTCXa0GwQ==/com.xiaomi.market-xy5vGUO4lRVZfVyVS6i_bw==/lib/arm64/libsdkcore.so
📊 嵌入表格(sheet token: SHjUsWMlEhcIvgtyACJcwMQXnnc,sheet_id: nZ9GAg)— 内容未提取
编译级别验证
通过分析 C:\Users\bobo.wang\Desktop\1.txt 文件内容,可以看出该文件是经过优化的(编译级别至少为 -O2 或 -O3),绝非未优化版本(-O0)。
📎 附件:1.txt(text/plain,token: MoWhbNbcdobqpux0SEJcAsdQnGc)— MinIO 不可用,未下载
.\llvm-objdump.exe -d分析结论和验证步骤:
- 分析结论
- 文件类型: ARM64 (AArch64) 架构的动态库反汇编代码 (libmilink.so)。
- 编译级别: 高度优化 (High Optimization),通常对应 GCC/Clang 的 -O2 或 -O3 级别。
- 依据: 代码中大量使用了寄存器来保存变量(而不是频繁读写栈内存),并且使用了条件选择指令 (csel)
来替代跳转指令,这是优化编译器生成的典型特征。
- 分析验证步骤
步骤一:检查”条件选择”指令 (Branchless Logic)
优化后的编译器会将简单的 if-else 语句转换为无分支的指令,以提高流水线效率。
- 验证方法: 在文件中搜索 csel (Conditional Select) 指令。
- 文件中的证据:
在地址 48220 处:48220: 9a8a0178 csel x24, x11, x10, eq
解释: 这行代码的意思是”如果条件(eq)成立,则 x24 = x11,否则 x24 = x10”。在未优化 (-O0)的代码中,这通常会被编译成两个跳转指令 (Branch)。只有开启优化 (-O1 及以上) 才会生成这种高效指令。
步骤二:检查寄存器使用情况 (Register Allocation)
未优化的代码会频繁地将变量写回栈内存 (Stack),而优化后的代码会尽可能将变量保留在寄存器中。
- 验证方法: 观察函数内部是否大量使用 x19 到 x28 这些”被调用者保存寄存器” (Callee-saved registers)。
- 文件中的证据:
在函数 Java_com_milink_kit_session_SessionManagerNative_unsubscribeSessionChangeCallback 中:
1 48238: aa1503e1 mov x1, x21 ; 直接使用寄存器 x21 中的值
2 48254: aa1403e8 mov x8, x20 ; 直接使用寄存器 x20 中的值
解释: 代码在整个函数执行过程中,一直将关键变量保存在 x19, x20, x21 等寄存器中,而不是每次使用前都从 [sp, offset]读取。这是 -O2 级别优化的显著特征。
步骤三:检查栈操作的冗余性 (Stack Spills)
- 验证方法: 寻找是否存在”刚写入又立即读取”的指令序列。
- 对比:
* 未优化 (-O0): 常见 str x0, [sp, #8] 紧接着 ldr x0, [sp, #8]。
* 优化后 (当前文件): 这种冗余操作已被消除。文件中的 ldr/str大多用于函数开头/结尾的现场保护,或者访问结构体成员,而不是用于局部变量的临时存取。
总结
该文件显示了极高的代码密度和寄存器利用率,且使用了高级指令 (csel, csinc),可以确定是 Release 版本 (优化版) 的编译产物。
总结推断
编译级别:通过 objdump -d 分析,编译级别至少为 -O2 或 -O3,绝非未优化版本(-O0), Strip 彻底。
安全性:符合基础 PIE 要求,检测到显式的栈溢出保护(Stack Canary)。
体积优化:符号表已去除,符合发布标准。