App的组成
1、AndroidManifest.xml:Apk的清单文件, 记录了 App 的名称、权限声明、所包含的组件等一系列信息.
2、classes.dex:它是由项目源码生成的 .class 文件经过进一步地转换而生成的 Android 系统可识别的 Dalvik Byte Code。并且,由于 Android 系统中的字节码和标准 JVM 中的字节码是有区别的,所以如果 App 中引用了第三方 jar 包的话,那么通常情况下它也会被包含在 classes.dex 中.
3、resources.arsc:资源索引表,包含编译后的二进制资源文件.
4、res 目录:未编译的资源文件.
5、asserts:额外建立的资源文件夹.
6、libs 目录:如果存在的话,存放的是 ndk 编出来的 so 库.
7、META-INF 目录:用于保存 App 的签名和校验信息,以保证程序的完整性。当生成 APK 包时,系统会对包中的所有内容做一次校验,然后将结果保存在这里。而手机在安装这一 App 时还会对内容再做一次校验,并和 META-INF 中的值进行比较,以避免 APK 被恶意篡改。其中包含如下 三个文件,如下所示:
- MANIFEST.MF(清单文件):
Name: 就是指定的文件
SHA-256-Digest: 将Name指定的文件经过SHA256后再base64编码得到到值
- CERT.SF文件:
开头处定义的SHA-256-Digest-Manifest 值是将MANIFEST.MF文件经过SHA-256后再base64后的值。
后面每一项也是对 MANIFEST.MF 文件中的每项再次进行SHA256数字签名并经过base64编码后的值。
- CERT.RSA文件:包含了公钥、加密算法等信息。首先是对前一步生成的 CERT.SF文件进行 SHA256进行数字签名,然后RSA算法加密,私钥使用开发者生成的私钥,然后在安装时使用公钥解密。最后,将其与未加密的摘要信息(MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被修改。
下面我们来看看,如果apk文件被篡改后会发生什么。
首先,如果apk包中的任何文件被改动,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是验证失败,程序就不能成功安装。
其次,如果你对更改的过的文件相应的算出新的摘要值,然后更改MANIFEST.MF文件里面对应的属性值,那么必定与CERT.SF文件中算出的摘要值不一样,照样验证失败。
最后,如果继续计算MANIFEST.MF的摘要值,相应的更改CERT.SF里面的值,那么数字签名值必定与CERT.RSA文件中记录的不一样,还是失败。
那么能不能继续伪造数字签名呢?不可能,因为没有数字证书对应的私钥。
所以,如果要重新打包后的应用程序能再Android设备上安装,必须对其进行重签名。
App的编译打包流程
Android官方打包流程如下图
打包流程可简述为如下 八个步骤:
- 首先,.aidl(Android Interface Description Language)文件需要通过 aidl 工具转换成编译器能够处理的 Java 接口文件。
- 同时,资源文件(包括 AndroidManifest.xml、布局文件、各种 xml 资源等等)将被 AAPT(Asset Packaging Tool)(Android Gradle Plugin 3.0.0 及之后使用 AAPT2 替代了 AAPT)处理为最终的 resources.arsc,并生成 R.java 文件以保证源码编写时可以方便地访问到这些资源。
- 然后,通过 Java Compiler 编译 R.java、Java 接口文件、Java 源文件,最终它们会统一被编译成 .class 文件。
- 因为 .class 并不是 Android 系统所能识别的格式,所以还需要通过 dex 工具将它们转化为相应的 Dalvik 字节码(包含压缩常量池以及清除冗余信息等工作)。这个过程中还会加入应用所依赖的所有 “第三方库”。
- 下一步,通过 ApkBuilder 工具将资源文件、DEX 文件打包生成 APK 文件。
- 接着,系统将上面生成的 DEX、资源包以及其它资源通过 apkbuilder 生成初始的 APK 文件包。
- 然后,通过签名工具 Jarsigner 或者其它签名工具对 APK 进行签名得到签名后的 APK。如果是在 Debug 模式下,签名所用的 keystore 是系统自带的默认值,否则我们需要提供自己的私钥以完成签名过程。
- 最后,如果是正式版的 APK,还会利用 ZipAlign 工具进行对齐处理,以提高程序的加载和运行速度。而对齐的过程就是将 APK 文件中所有的资源文件距离文件的起始位置都偏移4字节的整数倍,这样通过 mmap 访问 APK 文件的速度会更快,并且会减少其在设备上运行时的内存占用。
编译 --> DEX --> 打包 --> 签名 --> 对齐
用工具链的方式展示过程:
用文字的方式描述一下:
首先,需要参与编译的资源文件:
- 资源文件res目录下文件
- AIDL接口文件
- 源代码文件
- 第三方资源包: a.java(AAR/JAR java类)文件和 b.java(.so 非java类)文件
- aapt工具编译res资源文件, 把大部分xml文件编译成二进制文件,但不包含图片资源,生成 R.java和resources.arsc 两个文件,如果项目中包含 .aidl文件,也会编译生成相应但 .java文件。
- 将所有以java结尾的文件通过 javac 工具编译成以 class 结尾的文件
- 将所有以class结尾的文件通过 dx 工具生成 class.dex文件 如果分包的话 会多几个dex文件
- 将资源文件,dex文件和第三方的资源文件(非java类文件 如so文件等), 通过apkbuilder工具生成未签名的apk包
- 将apk包通过 jarsigner进行签名, debug用模式用默认签名,release用开发者签名
- 对apk中未压缩资源(图片 视屏等)通过 zipalign 工具进行对齐, 让资源按照4字节对齐。可以让资源的访问速度加快
从上面扩展出的几个问题:
- Android是如何通过R文件引用到真正的资源文件?
答:
aapt工具对每个资源文件生成唯一ID, 保存在R.java文件中,资源ID是一个无符号4字节整数,在R.java文件中用16进制形式表示。 其中最高的字节表示 Package ID, 下一个字节表示Type ID, 最低2字节表示 Entry ID. 资源ID和资源路径的对应关系就存储在 resource.arsc文件中
- 打包流程中的对齐是什么意思,有什么作用?
答:
让资源按照4字节方式进行对齐, 这样如果每个资源的位置都是上个资源位置的 4 * n, 那么访问资源就不用遍历的方式,这样可以大大的提升资源访问速度。
- 为什么将 .xml 文件编译成二进制?
答:
第一 占用空间更小, 因为所有xml文件的标签, 属性名称 属性值和内容都被搜集到一个资源池中处理,并且会去重,原来使用字符串的地方会被替换成一个索引到资源池的整数值,从而降低了存储空间。
第二 解析效率高, 二进制格式的 xml 文件解析更快,这是由于文件中不包含字符串,可以减少解析字符串所带来的开销,从而提高解析效率。
参考链接