APK 文件结构概述:它到底是个什么东西?

咱们直接点,APK 的本质是什么?

说白了,就是一个 ZIP 压缩包。

你完全可以把它当成一个普通的 ZIP 文件来解压。我自己刚开始接触 Android 开发时,就这么干过——把 APK 改名为 .zip,然后直接解压。嗯,里面就是咱们 App 的全部家当。

核心结论:APK = ZIP,但不仅仅是 ZIP。
它被 Google Play 和 Android 系统赋予了特殊的使命和结构要求。

为什么非得用 ZIP 格式?

因为 Android 系统需要一种高效的方式来打包和分发你的代码、资源和签名信息。ZIP 的压缩特性能让包体变小,而它的目录结构又便于系统快速索引文件。我个人觉得,Google 选 ZIP 是很务实的选择——简单、通用、性能好。

好,那 APK 内部到底长什么样?我直接给你们画个结构图,一目了然。

APK 文件核心结构一览

APK 内部结构示意图 APK 根目录 AndroidManifest.xml META-INF/ classes.dex CERT.RSA / CERT.SF / MANIFEST.MF resources.arsc res/ lib/ layout/ drawable/ values/ armeabi-v7a arm64-v8a 说明 • AndroidManifest.xml — 应用的身份证 • META-INF/ — 签名与完整性验证 • classes.dex — 编译后的 Java/Kotlin 代码 • resources.arsc — 编译后的资源索引表 • res/ — 原始资源文件(布局、图片、字符串等) • lib/ — 原生 so 库,按 CPU 架构分目录存放

每个目录是干什么的?

来,我一个个给你们拆开讲。

目录/文件 作用
AndroidManifest.xml App 的「身份证」。声明包名、组件、权限、版本号等。
META-INF/ 签名信息 + 完整性校验。一旦被篡改,系统会拒绝安装。
classes.dex 你的 Java/Kotlin 代码编译后的字节码,一个 APK 可能有多个 .dex。
resources.arsc 编译后的资源索引表,系统用它快速查找资源 ID 对应的实际文件。
res/ 原始资源文件。布局(layout/)、图片(drawable/)、字符串(values/) 等。
lib/ 原生 so 库,按 CPU 架构分目录(armeabi-v7a、arm64-v8a、x86 等)。
assets/ 可选。存放不经过编译的原始文件,比如一些大字体、视频等。
我个人的习惯:每次分析 APK 结构时,我第一件事就是看 AndroidManifest.xml 和 lib/ 目录。前者告诉我这个 App 用了哪些权限,后者能看出它用了哪些第三方库的 so 文件。

再说说 AndroidManifest.xml

它不是普通的 XML。Android 系统编译时会把可读的 XML 转换成 AXML(Android Binary XML)格式。你直接解压看是乱码,得用 aapt dump badging 命令或者反编译工具才能看。

我给你们演示一下怎么用命令行看 APK 的 manifest:

# 使用 aapt 工具
aapt dump badging your_app.apk

# 或者用 apktool 反编译后看
apktool d your_app.apk -o output_dir
cat output_dir/AndroidManifest.xml
注意:用 aapt 输出的信息有限,但足够快。apktool 能恢复完整的 manifest 结构,但反编译过程会慢一些。我以前做个竞品分析时,就是用 apktool 看到对方 App 居然声明了十几个不必要的权限——明显是第三方 SDK 乱加的。

META-INF:签名与安全

这个目录里的文件是 APK 的灵魂。你能安装 App,全靠它。

  • MANIFEST.MF — 列出 APK 内所有文件的 SHA1 摘要
  • CERT.SF — 对 MANIFEST.MF 再次做摘要,并签名
  • CERT.RSA — 开发者证书,包含公钥和签名

为什么要搞这么复杂?

为了保证包体内每个文件都不被篡改。Android 安装时,会逐级验证:先解出 CERT.RSA 里的公钥,再用它验证 CERT.SF 的签名,然后用 CERT.SF 里的摘要对比 MANIFEST.MF 的内容,最后用 MANIFEST.MF 对比实际文件。

我曾经踩过的坑:团队有个同事手动改了 APK 里的图片后,没重新签名就扔给测试。结果测试机死活装不上。后来排查发现——META-INF 里的摘要匹配不上,系统直接拒绝安装。所以记住:改完 APK 必须重签名。

classes.dex:你的代码在哪里?

以前一个 APK 只有一个 classes.dex。但现在 App 业务复杂了,方法数轻易超过 65535。这时候就得用 multidex——生成多个 dex 文件,比如 classes.dex、classes2.dex、classes3.dex……

怎么看 APK 里有多少个 dex?

unzip -l your_app.apk | grep classes.*\.dex

我见过最夸张的一个 App,里面有 12 个 dex 文件。那简直是噩梦。

resources.arsc:资源索引的秘密

这个文件就像一个字典。App 里用 R.string.app_name 引用一个资源 ID,系统就去 .arsc 里查这个 ID 对应哪个 res 文件,然后加载它。

.arsc 文件是编译后的二进制格式,不能直接看。但你可以用 aapt dump resources 把它导出成可读的格式。

小技巧:如果你想减少 APK 体积,检查 .arsc 文件是否包含了不必要的语言资源。很多第三方 SDK 会自动引入几十种语言,但你 App 可能只用中英文。用 resConfigs "zh","en" 可以过滤掉其他语言的资源表。

res/ 和 assets/ 有什么不同?

这个问题我经常被问到。直接说区别:

  • res/ — 会被编译,资源 ID 由系统分配(R.class),访问速度快。
  • assets/ — 不编译,保持原样,用 AssetManager 按文件名读取。

我个人建议:能用 res/ 就用 res/,因为它能利用 Android 的资源管理机制(比如屏幕适配、主题切换)。但如果你需要保持文件原始路径结构(比如放一个数据库文件),就用 assets/。

lib/:小心多架构适配

arm64-v8a — 64 位 ARM 设备,目前主流

armeabi-v7a — 32 位 ARM 设备,逐渐被淘汰

x86 / x86_64 — 模拟器和少数设备

你在 build.gradle 里配置 abiFilters 可以控制只打包哪些架构的 so 库。

android {
    defaultConfig {
        ndk {
            abiFilters "arm64-v8a", "armeabi-v7a"
        }
    }
}
避坑指南:如果 APK 里同时有 armeabi-v7a 和 arm64-v8a 目录,但你只提供了其中一个架构的 so 文件,另一架构的设备可能会直接崩溃。我曾经遇到过——测试用的 Pixel 6 是 64 位,但项目里的 so 库只打了 32 位,结果测试跑不起来。啊,又是一次血的教训。

小节小结

APK 的结构没那么神秘。你可以直接把它当 ZIP 玩。看懂每个目录的用途,是你进行包体优化、问题排查、甚至竞品分析的基础。

下一次我们深入到 AAB(Android App Bundle)的格式里,看看 Google 用它来做什么新花样。不过别急,先把 APK 搞透再说。