APK编译构建流程
背景
APK编译构建流程是开发者编写的Java、资源等源文件,以及引用的第三方库,被打包到一起,生成最终APK的流程,这里面涉及到多个输入、输出文件和工具。
整体打包流程可以分为7步,如下图所示:
汇总表格如下:
步骤 | 作用 | 工具 | 输入 | 输出 |
---|---|---|---|---|
1.资源处理 | 将资源文件打包为R.java和编译后的资源文件 | AAPT | 原始资源文件 | R.java, resources.arsc, 编译后的资源 |
2.AIDL处理 | 根据AIDL文件生成Java文件 | aidl | .aidl文件 | .java文件 |
3.Java编译 | 将.java编译为.class | javac | .java文件 | 编译后的.class文件 |
4.DEX合成 | 把第三方库和.class文件合成dex | DX,D8,R8,Multidex | 第三方库,.class文件 | classes.dex |
5.APK打包 | 组装APK | apkbuilder | 原始资源文件 | 未经编译的资源,已编译的资源.so库文件,dex文件 |
6.签名 | 使用私钥进行签名 | Jarsigner | APK | 签名后的APK |
7.对齐 | 文件对齐,提升在设备上性能 | zipalign | 签名APK | 签名且对齐后的APK |
1.资源处理
Android Asset Packaging Tool (AAPT)
用于处理资源文件,并生成R.java
,为代码中使用资源能提供索引。同时也生成了编译后的资源文件。
写代码时会自动调用aapt.exe
,以下是AAPT执行的主要操作:
- 检查
AndroidManifest.xml
- 引用其它资源包,如系统
android
命名空间下的资源 - 收集资源文件并处理overlay
- 加载资源到资源表
ResourceTable
- 编译
values
资源并添加到资源表 - 给
bag
资源分配id
,在编译其它资源前,先要给bag
资源(如attrs
)分配id
,以便其它资源引用 - 编译
xml
资源文件:对layout
、anims
、animators
等资源进行编译,扁平化为二进制文件 - 编译
AndroidManifest
文件,生成资源表 - 生成
R.java
文件 - 生成
resources.arsc
文件
2.AIDL处理
Android Interface Definition Language (AIDL)
工具用于在不同进程之间提供开放式接口,它将.aidl
文件转换为相应的.java文件。
3.Java编译
将所有.java
代码(源文件、AIDL
文件、R.java
)编译为.class
文件。
4.DEX合成
Dalvik Executable (DEX)
文件,由D8
或DX
工具将.class
文件、依赖的三方库转换得到,有以下主要职责:
- 从.class到.dex:因为Android虚拟机不能直接运行Java字节码,需要转换为Dalvik字节码,最新的工具是R8
- 字节码优化:删除死代码(用不到的class),减少dex文件大小并提升运行效率
- dex分包:超过65535的话,生成classes.dex, classes2.dex, classes3.dex等,工具是Multidex
5.APK打包
使用apkbuilder或者gradle工具将【未经编译/已编译的资源、.dex文件、so库】打包到一个未签名的APK文件中。
6.签名
未经签名的设备需要经过签名才能在设备上面运行,可使用debug
或者release
密钥,通过jarsigner
工具进行签名。
签名的目的
- 身份验证:保证应用来源的正当和合法
- 更新和版本管理:通过签名的应用表示,保证应用更新时不被劫持
V1签名与V2签名
有V1
、V2
两种签名模式,从Android 7.0
开始,默认使用V2
签名,但是为了保证向后兼容,大部分开发者同时使用这两种签名方式。
这两种签名在范围、存储位置有所不同。
- V1签名:即JAR签名,为APK中每一个文件单独签名
- V2签名:即完整APK签名,为整个APK文件进行一次签名,更安全且验证效率更高
7.对齐
使用zipalign
工具对已签名的APK进行优化,从原理上来讲就是通过格式化zip文件夹中二进制文件的序列,达到提升系统解析速度。zipalign
使用了4字节
的边界对齐方式来映射内存,是的运行时系统可以直接mmap
APK,通过空间换时间的方式提高执行效率。
对齐会改变APK的内容,因此会破坏之前的签名。
增量更新与差分包
在开发下载功能时,尤其是应用商店类APP,增量更新是一个很重要的功能,可以有效减少下载的数据量,提升下载完成率,优化用户使用体验。
什么是增量更新
增量更新基于BSDiff
算法,计算两个APK字节码的差异,首先在服务端生成差分patch
包,经过下载后,客户端使用同样的BSDiff
算法,把patch
与老APK合并,生成新APK,进行安装。
- 服务器计算并生成差分包
- 客户端下载差分包
- 客户端合并老APK与差分包,生成新APK
- 客户端安装新APK
相比于下载完整的新APK,很明显,增量更新最大的优点是数据传输量更小,下载安装更快。
BSDiff算法详解
什么是BSDiff
BSDiff
是一种可执行文件的二进制差异构建和应用修补工具。
BSDiff的核心思想
是尽可能多的利用old文件中已有内容,尽可能少的加入新的内容来构建new文件。BSDiff算法的提出,基于可执行文件更新前后二阶变动的两个重要规律:
- 没有被更新代码所影响的代码段,在变为可执行文件后,该区域的二进制内容变化是极为稀疏的,即仅仅有部分指针或寄存器变动,通常不会超过一两个字节
- 更新后的代码和数据会有很大的位置变动,但这种变动大多为整块的移动,即某一块位置中代码内的指针地址变动均会有相同的位移值
这两个规律可以推导出一个重要事实:相同源代码对应的两个代码块中,大部分内容字节差为0,而少部分需要更新的地址位移数据又存在大量相同的位移值,即源代码相同的代码块差异数据可以被高效压缩。
基于此思想,BSDiff算法使用如下步骤来进行生成差异更新包:
- 将旧文件二进制使用后缀排序或哈希算法形成一个字符串索引
- 使用该字符串索引对比新文件,生成差异文件(difference file)和新增文件(extra file)
- 将差异文件和新增文件及必要的索引控制信息压缩为差异更新包
差分过程共生成3个文件,分别是差异文件、更新文件和控制文件。,这三个文件加一起会比新文件略大,但其中控制文件和差异文件是高度结构化的,意味着其均可被高效压缩,所以可以使用类似bzip2
等压缩工具将更新包总文件进行非常有效的压缩。
实战:增量更新APK
这部分请参考掘金 Android_增量更新(BSDiff)详解
对增量更新的思考
- 在生产场景中,由于线上存在多个版本的APP,因此需要针对不同版本生成差分包
- 如果版本变更过大,可能导致差分包大于new包,此时直接使用new包下载更新即可
- 出于安全和成功率考虑,需要对old包、new包、patch分别进行md5校验