Android 包体积优化(一):APK 到底大在哪里?从 APK 结构到体积分析

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

重要的事情说三遍

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

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

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

做 Android 包体积优化时,我以前最容易犯的一个错误是:

看到 APK 大,就马上开始删图片、开 shrinkResources、检查第三方库。

这些手段当然没有错,但顺序错了。

因为一个 APK 变大的原因可能完全不同:

  • 有的项目是 DEX 太大;

  • 有的是第三方 SDK 带进来了大量代码;

  • 有的是 Native .so 占了几十 MB;

  • 有的是图片资源过多;

  • 有的是 assets 中塞了模型、数据库或者数据文件;

  • 还有一些项目是多语言、多 Density、多 ABI 同时打进了 APK。

如果连体积主要花在哪里都不知道,直接开始优化,很容易变成:

优化了半天,删掉 200 KB 图片,结果发现真正的大头是一个 15 MB 的 SO。

所以我现在认为:

Android 包体积优化的第一步不是"优化",而是"分析"。

这一篇先不讨论 R8、资源压缩、SO 裁剪、ReDex 等具体手段,而是从 APK 本身开始,把 APK 的体积组成和分析方法搞清楚。


一、为什么 Android 应用需要关注包体积?

包体积最直接影响的是用户下载。

尤其对于网络条件一般、流量敏感或者存储空间有限的用户,一个明显偏大的应用会增加下载和安装成本。Google 的 Android 官方文档也一直把降低下载大小作为应用质量优化的一部分,并指出更大的应用体积可能影响安装成功率和卸载行为。

但实际项目中,我们还需要区分几个概念:

APK 文件大小

用户实际下载大小

安装后的磁盘占用

这三个数字并不是完全相同的。

因此我们平时说:

"我的 App 有 30 MB。"

首先就应该问:

这 30 MB 指的是 APK 文件大小,应用市场下载大小,还是安装后的占用?

这一点后面会继续讲。


二、APK 到底是什么?

先从最基础的开始。

APK:

Android Package。

从文件组织形式上看,APK 遵循 ZIP 文件格式,所以可以把一个 APK 当成 ZIP 容器查看。

Android 官方 APK Analyzer 文档也明确说明:

APK 文件遵循 ZIP 文件格式,APK Analyzer 会按照 APK 内真实的目录和文件层级展示内容。

实际上我们把:

app-release.apk

改成:

app-release.zip

然后解压,也能够看到里面的大部分文件。

一个典型 APK 大概包含:

  • AndroidManifest.xml

  • classes.dex

  • classes2.dex

  • resources.arsc

  • res/

  • assets/

  • lib/

  • META-INF/

理解这些东西分别是什么,是包体积优化的基础。


三、AndroidManifest.xml

Android 项目源码中我们会维护:

AndroidManifest.xml

但是最终 APK 中的 Manifest,并不只是简单把源码文件复制进去。

项目可能存在:

  • App Manifest

  • Library Manifest

  • Flavor Manifest

  • BuildType Manifest

构建过程中这些 Manifest 会进行 Merge。

最终 APK 中保存的是构建后的 Manifest,而且通常使用 Binary XML 格式。Android Studio APK Analyzer 可以把这个二进制 Manifest 重新解析成人类可读的 XML。

从纯包体积角度来说:

AndroidManifest.xml

一般不是最大的部分。

但是它对于分析:

  • 第三方 SDK

  • Permission

  • Component

  • Provider

  • Service

  • Receiver

非常有价值。

有时候我们就是通过最终 Manifest 才发现:

某个已经不用的 SDK 其实还被打进了 APK。


四、classes.dex:Android 代码真正进入 APK 后的样子

对于大多数普通 Android 项目来说:

Java / Kotlin 代码最终不会以 .java.kt 文件存在 APK 中。

大体流程可以先理解成:

Java / Kotlin

Java Bytecode

D8 / R8

DEX

最终形成:

classes.dex

如果一个 DEX 放不下所有引用或者构建策略需要多个 DEX,还可能出现:

classes2.dex

classes3.dex

classes4.dex

......

也就是:

MultiDex。

官方文档将 classes.dex 描述为以 DEX 格式编译、供 Dalvik 或 ART 使用的应用代码。

DEX 也是我们做包体积优化时非常值得重点关注的一部分。

它里面不仅仅有方法代码,还包括:

  • String

  • Type

  • Proto

  • Field

  • Method

  • Class Definition

  • Code

  • Debug Information

等等。

所以一个项目:

代码越来越多

第三方依赖越来越多

各种 SDK 越来越多

最终都会反映到 DEX 体积上。

DEX 具体结构以及 R8、第三方依赖如何影响 DEX,我们放到这个系列第二篇详细讲。


五、resources.arsc 是什么?

很多刚接触 APK 分析的人第一次看到:

resources.arsc

都会比较陌生。

它本质上是:

编译后的 Android Resource Table。

例如我们写:

strings.xml

colors.xml

styles.xml

以及各种不同 configuration 下的资源。

经过 AAPT2 编译和链接之后,一部分资源信息最终会进入:

resources.arsc

Android 官方说明,resources.arsc 包含编译后的资源,包括 res/values/ 不同 configuration 中的 XML 内容,以及指向布局、图片等其他资源的路径信息。

所以如果一个大型 App:

支持几十种语言

大量 string resource

大量 style

大量 configuration

那么:

resources.arsc

也可能变得比较明显。


六、res/:图片、布局和 Android 资源

res/

里面就是我们非常熟悉的:

  • drawable

  • mipmap

  • layout

  • anim

  • xml

  • raw

  • font

等等。

这里经常出现包体积大户:

图片。

特别是项目发展多年以后,很容易出现:

  • 大尺寸 PNG

  • 重复图片

  • 已经不用的图片

  • 多份不同 Density 图片

  • 动画帧图

  • 第三方 SDK Resource

所以:

res/

通常是包体积分析必须重点检查的目录。

不过这里有一个非常重要的原则:

看到 res 大,再做资源优化。

如果分析发现整个 APK:

DEX 占 80%

Resource 只占 5%

那么这个阶段疯狂压图片,收益必然有限。


七、assets/:最容易被忽略的体积大户之一

assets/

res/ 有一个很大的区别:

Assets 通常会以更接近原始文件的形式打包,并由 AssetManager 访问。Android 官方也将 assets/ 定义为应用可以通过 AssetManager 获取的资源目录。

这里经常出现:

  • JSON

  • HTML

  • 字体

  • 数据库

  • 音视频

  • Web 前端资源

  • ML 模型

  • 游戏资源

  • 第三方 SDK 数据文件

而且:

Assets 很容易被开发者忘记。

比如一个 SDK:

代码本身只有几百 KB,

但它可能附带:

2 MB 数据文件。

你如果只盯着 DEX:

根本发现不了。

这也是为什么包体积分析一定应该从:

APK 整体结构

开始,而不是直接钻进 DEX。


八、lib/:Native 项目最容易爆炸的地方

如果项目使用:

C / C++

或者某些第三方 SDK 使用 Native Code,

APK 里面会出现:

lib/

例如:

lib/arm64-v8a/xxx.so

lib/armeabi-v7a/xxx.so

lib/x86_64/xxx.so

这些:

.so

就是 Native Dynamic Library。

不同 ABI 可能包含一套独立的 SO。

例如一个库:

arm64:

10 MB

armeabi-v7a:

9 MB

x86_64:

12 MB

如果全部打进一个 APK:

体积可能一下增加几十 MB。

Android 官方列出的 APK lib/ 目录也是按照 CPU/ABI 分目录存放编译后的 Native Code。

所以:

对于使用大量 Native SDK 的项目,SO 往往比 Java/Kotlin 代码更值得首先检查。

Native 包体积优化我们会放到第三篇专门展开。


九、META-INF 是什么?

APK 中还经常存在:

META-INF/

其中可能包含:

MANIFEST.MF

CERT.SF

CERT.RSA

以及其他 metadata。

需要特别注意:

我们不能简单认为:

"APK 所有签名数据都在 META-INF。"

这是不准确的。

传统的 APK Signature Scheme v1 与 ZIP/JAR 的 META-INF 结构关系比较直接,但现代 Android APK 还存在 v2/v3 等不同签名方案。

关于 APK 签名我们后面的工程化篇会单独讲。

这一篇只需要知道:

META-INF 也是 APK 结构的一部分,但通常不是包体积优化的大头。


十、所以一个 APK 的体积可以先怎么拆?

实际做包体积分析时,我习惯先做一级分类:

APK

DEX

Resources

Native

Assets

META-INF

Other

也就是先回答:

钱花在哪了?

而不是马上回答:

怎么省钱?

这两个问题的顺序非常重要。


十一、文件大小其实还有"压缩前"和"压缩后"

APK 是 ZIP 格式,因此里面一个 Entry 通常可以存在:

原始大小

和:

压缩后的大小

两个概念。

Android Studio APK Analyzer 中会看到:

Raw File Size

以及:

Download Size

当前官方定义中,Raw File Size 表示该实体对 APK 总文件大小的贡献,而 Download Size 是 Google Play 传输时估算的压缩下载大小。

所以:

一个文件:

Raw:

2 MB

不代表用户一定:

下载 2 MB。

如果它非常容易压缩:

最终传输大小可能小很多。


十二、为什么这个区别很重要?

假设两个文件:

A:

Raw = 5 MB

压缩后 = 500 KB

B:

Raw = 3 MB

压缩后 = 2.9 MB

如果只看:

Raw Size

你会觉得:

A 更值得优化。

但站在:

下载体积

角度,

B 可能反而更值得关注。

所以包体积优化以后会逐渐涉及三个视角:

APK 文件视角

最终 APK 本身多大。

Download Size 视角

用户真正需要下载多少数据。

Install Size 视角

安装以后真正占设备多少空间。

不要把这几个数字混为一谈。


十三、Android Studio APK Analyzer 怎么用?

Android Studio 自带了非常好用的:

APK Analyzer。

目前可以通过:

Build → Analyze APK

选择 APK 或 App Bundle 进行分析。

打开以后,可以看到:

  • APK 内部文件

  • 文件大小

  • 相对占比

  • Manifest

  • Resources

  • DEX

  • Class

  • Package

  • Method Reference

  • 两个 APK/AAB 的差异

Android Studio 还支持直接对两个 APK 或 App Bundle 做 Side-by-side Compare。

所以:

包体积优化第一阶段,完全没必要一上来就找复杂第三方工具。

先把 APK 扔进 APK Analyzer。

很多问题其实已经非常明显了。


十四、DEX 还能继续往下看

APK Analyzer 不只是:

classes.dex 有多大。

点开 DEX 以后,还可以继续分析:

  • Package

  • Class

  • Defined Methods

  • Referenced Methods

官方特别区分:

Referenced Methods

和:

Defined Methods

简单理解:

Defined:

这个 DEX 中真正定义的方法。

Referenced:

这个 DEX 引用的方法。

Referenced Methods 还可能包括:

  • 自己代码的方法

  • 第三方依赖的方法

  • Java / Android API

这也是为什么:

Method Reference 数量不能直接等价成"项目源码写了多少个方法"。

这一点在 MultiDex 和 DEX 优化里非常重要。


十五、除了 Android Studio,还有 apkanalyzer

如果不想打开 Android Studio:

Android SDK 还提供:

apkanalyzer

命令行工具。

它支持分析:

  • apk

  • files

  • manifest

  • dex

  • resources

并且可以比较 APK。

它一般位于 Android SDK Command-Line Tools 的:

cmdline-tools/<version>/bin/apkanalyzer

下面。

这个工具对于:

脚本化

和:

CI

特别有意义。

因为我们后面真正做工程化包体积治理的时候,不可能要求:

每次 CI Build 完,都让一个开发者打开 Android Studio 人工看一下。

最终一定会走向:

自动分析。


十六、实战:一个 12.56 MB APK,应该先优化什么?

下面看一个真实测试。

我做了一个用于包体积实验的 Android 项目,故意加入:

  • FastUtil

  • Guava

  • BouncyCastle

  • Protobuf

  • Tink

等依赖。

最终 Debug APK:

约:

12.56 MB

包含:

7 个 DEX。

如果只看:

12.56 MB

其实你不知道从哪里开始。

但是把 APK 分类分析以后发现:

DEX 占 APK:

约 89.21%。

这个结论一下就改变优化方向。

说明这个 APK:

主要矛盾不是:

图片。

不是:

SO。

也不是:

普通 Resources。

而是:

DEX。

所以接下来应该优先调查:

  • 第三方依赖

  • Transitive Dependency

  • R8

  • 无用代码

  • DEX Layout

  • Method Reference

  • ReDex

而不是花大量时间压几张 PNG。


十七、继续检查 Largest Files

APK 分析还有一个非常实用的手段:

把 APK 里面最大的文件按大小排序。

很多隐藏问题:

一下就出来了。

例如这个测试 APK 中:

BouncyCastle Picnic 相关数据文件大约:

1.16 MB。

这意味着:

即使后面把 DEX 优化得再好:

这 1.16 MB 数据文件也不会因为 DEX Optimizer 自动消失。

正确方向反而应该是:

这个 Dependency 是否必要?
能不能换轻量版本?
这份数据文件到底有没有真正使用?

这也引出一个非常重要的包体积优化原则:

不同类型的体积问题,需要不同的优化工具。


十八、我现在分析一个 APK,通常先看什么?

可以总结成一套比较简单的流程。

第一步:看 APK 总大小

先建立:

Baseline。

比如:

12.56 MB。


第二步:看一级分类

分别统计:

DEX

Resource

Native

Assets

Other

占多少。


第三步:找占比最大的 Category

例如:

DEX = 89%

那么:

第一优化方向就是 DEX。


第四步:看 Top Largest Files

查:

单文件大户。

例如:

SO

图片

模型

数据库

第三方数据文件。


第五步:继续钻入大类

如果:

DEX 大

看 Dependency / R8 / Method Reference。

如果:

Native 大

看 ABI / SO。

如果:

Resource 大

看图片 / Density / Language。

如果:

Assets 大

看模型 / 数据 / 字体 / Web Resource。


第六步:建立 Baseline

例如记录:

APK Size

DEX Size

DEX Count

Native Size

Resources Size

Assets Size

Largest Files

以后所有优化:

都必须和这个 Baseline 对比。


十九、为什么一定要建立 Baseline?

比如你做一次优化:

原来:

12.56 MB

优化以后:

12.10 MB。

你可以明确知道:

减少:

460 KB。

但是还不够。

继续问:

460 KB 从哪里减少的?

可能:

DEX:

-800 KB

但是:

Resources:

+340 KB。

最后总包:

只减少:

460 KB。

如果只看最终 APK Size:

这个信息就看不到。

所以成熟的包体积治理:

不能只有:

Total Size。

还应该跟踪:

Size Composition。


二十、包体积优化最容易犯的几个错误

错误一:APK 大就开始压图片

先看 Resource 到底占多少。


错误二:DEX 多就认为一定有问题

DEX Count 是一个信号,不是最终结论。

真正还要看:

  • 每个 DEX 大小

  • Method Reference

  • Class Distribution

  • R8

  • MultiDex 策略


错误三:只看总包大小

总大小下降:

不代表所有维度都改善。

最好同时观察:

DEX

Resources

SO

Assets。


错误四:把 Method Reference 当作源码方法数量

Referenced Method:

不只是你自己定义的方法。Android Studio APK Analyzer 官方也明确区分 Referenced Methods 和 Defined Methods。


错误五:只优化,不对比

如果没有 Baseline:

你甚至无法准确回答:

这个方案到底优化了多少?


二十一、Android 包体积优化应该形成什么思维?

看到一个大 APK:

不要马上问:

怎么把它变小?

先问:

1. 大在哪里?

DEX?

SO?

Resources?

Assets?


2. 为什么大?

代码?

Dependency?

图片?

ABI?

语言?

SDK?


3. 哪种工具能解决?

R8?

Resource Shrinker?

图片优化?

ABI Split?

Dependency Governance?

ReDex?


4. 优化以后减少了什么?

不是只说:

APK 减少 2 MB。

而应该能回答:

DEX 减少多少?
Resource 减少多少?
SO 减少多少?


5. 优化有没有副作用?

比如:

资源有没有丢?

Class 有没有被错误删除?

SO ABI 是否完整?

优化后的 APK 是否还能正常运行?

这才是一套完整的包体积优化思路。


二十二、总结

这一篇其实没有真正开始:

"瘦身"。

因为我认为:

包体积优化最重要的第一步,就是先不要优化。

先把 APK 分析清楚。

APK 本质上是一个包含 Android 特定内容的 ZIP 容器,里面主要包括:

  • DEX

  • Resources

  • Native Library

  • Assets

  • Manifest

  • Metadata

不同项目:

体积主要来源完全可能不同。

所以正确流程应该是:

APK

Analyze

Size Composition

Find Bottleneck

Choose Optimization

而不是:

APK 大

随便找一个"Android 包体积优化大全"

所有配置全部打开。

真正有效的包体积优化,一定是:

数据驱动。

先知道:

APK 到底大在哪里。

下一步才讨论:

为什么会这么大,以及怎么把它真正减下来。


下一篇

《Android 包体积优化(二):深入 DEX、R8 与第三方依赖优化》

下一篇会重点讨论:

  • DEX 文件到底是什么

  • DEX Header 有什么

  • Method ID 是什么

  • 64K 限制到底限制了什么

  • MultiDex 为什么出现

  • D8 和 R8 的关系

  • R8 如何 Shrink Code

  • minifyEnabled 到底发生了什么

  • 第三方依赖为什么是 DEX 体积大户

  • Transitive Dependency 怎么排查

  • 为什么"少引一个 SDK"有时候比压几十张图片更有效

  • 如何系统治理大型 Dependency

从第二篇开始,真正进入:

代码体积优化。

相关推荐
Super 含2 小时前
Android 包体积优化(四):深入 ReDex——StripDebugInfo、InterDex 与 DEX 重排
android
安卓与AI研习社2 小时前
主线程明明是空闲的,为什么仍然 ANR?3 个真实 Trace 的证据链复盘
android
Promising_GEO3 小时前
新电脑科研环境配置指南:Miniforge + PyCharm + GitHub 从安装到可用
ide·python·pycharm·github·地理
恋猫de小郭3 小时前
Dart 3.13 大更新,感觉比 Flutter 更带劲
android·前端·flutter
舞动青春883 小时前
vscode copilot配置kimi k3
ide·vscode·copilot
大锅盖14 小时前
HarmonyOS 6.1.1 ArkWeb:交付门户下载资料前-为什么必须先登记下载代理与来源字段
android·华为·harmonyos
爱上纯净的蓝天4 小时前
AtomCode 多语言开发支持:一个 AI IDE 搞定全栈开发
ide·多语言·全栈开发·atomcode
峥嵘life12 小时前
Android16 311Y3 EAP-TLS 网络连接失败分析与修复总结
android·开发语言·人工智能·php
Kapaseker13 小时前
破坏性更新 - 解读 Jetpack Compose 1.12
android·kotlin