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

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

代码体积优化。

相关推荐
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台5 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone5 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo5 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui