APK内容及编译过程分析
目录
APK内容及编译过程分析
1. 概述
2. APK解压后文件说明
3. APK编译过程分析
- 概述
APK全称Android application package,是一种Android应用程序包。
一个Android应用程序的代码想要在Android设备上运行,必须先进行编译,然后被打包成为一个被Android系统所能识别的文件才可以被运行,而这种能被Android系统识别并运行的文件格式便是“APK”。 一个APK文件内包含被编译的代码文件(.dex 文件),文件资源(resources), 原生资源文件(assets),证书(certificates),清单文件(manifest file)。
2. APK解压后文件说明
apk实际上是一种压缩包,将.apk文件修改后缀为.zip文件,解压后可以看到以下目录:
2.1 assets文件夹:
assets文件夹主要存放原生资源文件,即Android工程下的assets文件夹下的内容合集。
2.2 lib文件夹:
lib文件夹主要存放编译后的库文件。
2.3 META-INF文件夹:
META-INF文件夹主要存放的是Android组件版本相关信息,以及编译生成的签名校验信息,主要作用是在apk安装时校验apk是否被修改过。
例如组件版本信息:
例如签名校验信息:
MANIFEST.MF文件:
清单文件,记录了所有其他文件的SHA1并base64编码值。
CERT.SF文件:
分为两部分,头部的
SHA-256-Digest-Manifest: A9MrU52Yl1nxQtwWGN0GCnlKg0DFAbGsmH4H508Wt/8=为MANIFEST.MF文件的SHA1并base64编码值;后面的每一个子项,对应MANIFEST.MF子项的SHA1并base64编码值;
CERT.RSA文件:
包含了公钥信息和发布机构信息。
2.4 res文件夹:
即Android工程的res文件合集,在aapt编译期,所有res由gradle的task进行merge。改文件内的每一个文件都对应R.java下的一个静态常量。
2.5 .dex文件
上图上包含三个.dex文件,由java编译器生成的class文件经过dx工具转换而来。
DEX文件从整体上来看是一个索引的结构,类名、方法名、字段名等信息都存储在常量池中,这样能够充分减少存储空间,相较于Java字节码文件更适合手机设备。
逻辑上,可以把dex文件分成3个区,头文件、索引区和数据区。索引区的ids后缀为identifiers的缩写。
2.6 resource.arsc文件:
编译过程中产生的一个资源索引文件。
资源索引具有固定的格式:0xPPTTEEEE = PackageId(2位) + TypeId(2位) + EntryId(4位)。
PP:Package ID,包的命名空间,取值范围为[0x01, 0x7f],第三方应用均为7f。
TT:资源类型,有anim、layout、mipmap、string、style等资源类型。
EEEE:代表某一类资源在偏移数组中的值。
该文件的结构如下图示:
2.7 AndroidManifest.xml文件:
Android工程中AndroidManifest.xml的合集,主要记录Android组件声明,权限声明,应用名称声明等相关信息。
该文件直接打开会乱码,可使用AXMLPrinter2.jar逆向编译xml,命令如下:
java -jar AXMLPrinter2.jar AndroidManifest.xml ->AndroidManifest.txt
3. APK编译过程分析
通常一个Android工程生成一个apk文件,需要经过以下步骤:
3.1Aidl文件编译
通过 aidl 工具转换成编译器能够处理的 Java 接口文件。
3.2 aapt(Android Asset Packaging Tool):
3.2.1 aapt/aapt2编译资源文件为二进制,将资源文件生成扩展名为.flat的二进制文件。
3.2.2 合并所有已编译的文件并打包到一个软件包中。
3.2.3 AndroidManifest.xml、布局文件等xml 资源被aapt/aapt2处理成resource.arsc文件。
3.3 java compiler
javac工具编译java文件(R.java、Java 接口文件、Java 源文件)为class文件。
3.4 编译class文件
因为 .class 并不是 Android 系统所能识别的格式,所以还需要通过 dex 工具将它们转化为相应的 Dalvik 字节码(包含压缩常量池以及清除冗余信息等工作)。这个过程中通常还会加入应用所依赖的所有第三方库。
这个过程中涉及到早期的dx工具以及Android Studio3.1之后引入的D8编译器和R8工具。
早期,Android从源码到字节码再到dex的编译过程如下,中间使用Proguard进行字节码优化:
由于过程复杂,2015年左右Google退出了Jack&Jill编译器,减少了中间环节:
过与简化的编译过程,无法在过程中加入更多优化,因此Google在2017年废弃了Jack&Jill,重新回到了之前的编译流程,只是重写了最后一步dex编译器,称之为D8。一个完整的编译流程需要Proguard和D8同时参与其中。
R8的出现整合了Proguard和DB,减少了一个编译步骤,同时保留了字节码优化的能力。
R8通过开启Shrinking,可以对源码进行优化,例如不可达到代码删除,代码优化,代码混淆。
android { ... buildTypes { release { minifyEnabled false } } }
示例:
3.5 生成APK包(apkbuilder/zipflinger)
将manifest文件、resources文件、dex文件、assets文件等等打包成一个压缩包,也就是apk文件。
3.6 签名(apksignerr/jarsigner)
v1签名:
v1签名方式主要是利用META-INFO文件夹中的三个文件。
首先,将apk中除了META-INFO文件夹中的所有文件进行进行摘要写到 META-INFO/MANIFEST.MF;然后计算MANIFEST.MF文件的摘要写到CERT.SF;最后计算CERT.SF的摘要,使用私钥计算签名,将签名和开发者证书写到CERT.RSA。
所以META-INFO文件夹中这三个文件就能保证apk不会被修改。
但是缺点也很明显,META-INFO文件夹不会被签名,所以美团针对这种签名方式设计了一种多渠道打包方案:
利用pythone在META-INFO文件夹中创建一个文件,其名称就是渠道名,然后用java去读取文件名获取渠道。
v2签名:
Android7.0之后,推出了v2签名,为了解决v1签名速度慢以及签名不完整的问题。
apk本质上是一个压缩包,而压缩包文件格式一般分为三块:
文件数据区,中央目录结果,中央目录结束节。
而v2要做的就是,在文件中插入一个APK签名分块,位于中央目录部分之前,如下图:
V3签名
Android 9 推出了v3签名方案,和v2签名方式基本相同,不同的是在v3签名分块中添加了有关受支持的sdk版本和新旧签名信息,可以用作签名替换升级。
V4签名
Android 11 推出了v4签名方案。
v4 签名基于根据 APK 的所有字节计算得出的 Merkle 哈希树。它完全遵循 fs-verity 哈希树的结构,将签名存储在单独的.apk.idsig 文件中。
3.7 ZipAlign对齐
对齐的过程就是将 APK 文件中所有的资源文件距离文件的起始位置都偏移4字节的整数倍,这样通过 mmap 访问 APK 文件的速度会更快,并且会减少其在设备上运行时的内存占用。
zipalign 是一种归档对齐工具,可对 Android 应用 (APK) 文件提供重要的优化
它会使 APK 中的所有未压缩数据(例如图片或原始文件)在 4 字节边界上对齐。
这里涉及到一个Data structurealignment(数据对齐)的知识点,其大概意思就是如果数据是自然对齐的,CPU读写就会更高效。
签名工具的不同带来的对齐处理的顺序不同:
如果使用的是 apksigner,只能在为 APK 文件签名之前执行 zipalign。
如果使用的是 jarsigner,只能在为 APK 文件签名之后执行 zipalign。