我们已经准备好了,你呢?

2024我们与您携手共赢,助您领跑移动端创新增长!

  现如今随着组件化, 插件化框架以及热修复,AOP编程等高级用法的新起,不得不驱使大家去了解更加底层的原理,上一篇文章笔者介绍了关于JVM字节码的理解,这篇文章笔者就来讲解下我们apk从编译到安装的过程。

  首先来了解下我们apk的成员

  apk文件组成

  1.AndroidManifest.xml:

  apk的配置文件,内部包含了应用的名称,权限声明,四大组件等信息的声明,如果说app是一本书,它就是这本书的封面

  2.classes.dex:

  由源码编译后的的.class文件经过进一步转换成Android系统可识别的Dalvik Byte Code,包括第三方jar或aar中的class文件

  3.resources.arsc:

  资源索引表,使用的是一个二进制流的格式,记录R文件id和资源文件路径的一个映射表以及我们在res文件夹下放入一个资源文件,aapt就会自动在R文件中生成对应的资源文件id,而R文件只能保证编译不报错,实际运行时还是需要依赖resources.arsc中的映射关系,通过R文件id找到实际资源文件路径。

  4.res 目录:

  存放未编译的资源文件

  5.asserts:

  也是资源文件夹,和res的区别:res文件会在R文件中生成索引,运行时使用索引获取资源文件,而asserts目录下的文件不会在R文件中生成索引,使用AssetManager访问文件

  6.libs 目录:

  存储so动态库

  7.META-INF 目录:

  存储签名和校验信息,确保程序的完整性。Apk打包时会使用签名文件对apk进行签名校验,并将结果记录到META-INF 目录下,Apk安装的时候设备也会对应用进行一次校验,并和META-INF 目录下的校验信息对比,防止apk在出包后被篡改

  apk打包编译过程

  放一张官方apk编译过程图:

  旧版本打包编译过程更详细的描述了具体过程

  1.将aidl文件使用aidl工具转换为编译器能够识别编译的java文件2.将资源文件(AndroidManifest.xml,xml布局文件,资源resources文件,asserts下的资源文件)使用aapt工具(最近几个版本已经改为优化后的aapt2)一部分打包为编译后的资源文件(resources.arsc),并生成对应的R文件,以及生成Proguad 配置文件3.将java源文件以及aidl生成的java文件还有R文件,android类库文件使用javac一起编译为class文件4.使用proguard对class文件和资源文件进行混淆和优化。5.然后和第三方类库class文件一起打包生成Android虚拟机可以识别的Dalvik Byte Code类型的dex文件6.使用apkBuilder工具,将dex文件和资源文件以及so库打包生成未签名的apk文件7.使用jarSigner工具将apk文件使用keystore签名生成签名后的apk文件8.使用zipAlign对apk进行对齐处理,对齐的过程就是将 APK 文件中所有的资源文件距离文件的起始位置都偏移4字节的整数倍,这样通过 mmap 访问 APK 文件的速度会更快,并且会减少其在设备上运行时的内存占用

  到此我们已经了解另外apk的打包和编译的一个完整流程

  这里我们提出两个问题

  1.为什么要将xml文件编译为resources.arsc中的二进制流格式?2.app运行的时候,系统是如何找到对应的资源文件的?

  这两个问题其实答案都是相通的。

  在前面一篇文章讲解class文件结构的时候,说到过,class文件有一个常量池的结构,其存储了当前class文件所需的所有常量字符串,外部访问常量的时候,是通过索引的方式进行访问

  这里xml文件编译为resources.arsc过程也是这么一个方式。

  那为什么要这么使用呢?

  字符串复用,资源复用:当前xml文件的标签,属性名称以及属性值都会被编译到resources.arsc资源池中,且去除重复的字段,外部访问的时候,只需要访问使用当前资源索引就可以在资源池中找到对应的资源信息,且不需要每次都去解析对应的字符串,解析资源效率高。第二个问题在第一个问题解中就可以找到了

  前面都是讲解源文件编译过程,这里我们再来突出下apk的签名校验过程

  APK签名密钥算法:1.消息摘要

  如MD5加密后的字符串就是一个消息摘要特点:可以保证数据完整性,但是无法保证数据安全性和不可篡改性

  2.数字签名

  两部分组成:签名算法和验证算法

  知识点:

  公钥密码体制 对称加密算法和非对称加密算法

  1.公钥密码体制 :分为公钥和私钥和加密解密算法:常见为RSA算法

  加密:通过公钥使用加密算法对明文进行加密,得到密文

  解密:通过私钥使用解密算法对密文进行解密,得到明文

  公钥加密的内容一定需要知道私钥才能解出明文由管理员生成一套公钥和私钥。公钥和算法是公开的,私钥是只有管理员才有,保证数据不可以篡改

  2.对称加密算法:常见有DES 3DES AES等,对称加密算法使用同一个密钥对明文进行加解密操作,所以需要保证密钥的安全性。3.非对称加密算法:常见有RSA算法加密使用的密钥和解密使用的密钥是不相同的。

  RSA:

  1.加密:终端使用公钥加密,服务端使用私钥解密:数据只能由服务端解密,可以保证不可篡改性。2.签名:服务端使用私钥加密,终端使用公钥解密:可以保证数据是由服务端使用私钥加密后的数据,且服务端不能否认这个结果数字签名简介:非对称加密技术 + 消息摘要技术的结合

  如果单独使用私钥进行加密只可以保证数据的完整性,无法保证数据的保密性且数据大时加密的效率速度很低,所以采用加密摘要算法生成的摘要消息弥补缺点

  具体步骤:

  发送者:

  1.发送者使用使用MD5获得摘要2.发送者使用私钥对摘要加密获得数字签名3.发送者需要将原始信息和数字签名一同发送给接收者

  接收者:

  4.接收者先把接收到的原始消息用同样的摘要算法摘要,形成“准签体”。5.对附加上的那段数字签名,使用预先得到的公钥解密。6.比较前两步所得到的两段消息是否一致: 如果一致,则表明消息确实是期望的发送者发的,且内容没有被篡改过; 相反,如果不一致,则表明传送的过程中一定出了问题,消息不可信。v1签名:

  签名过程:

  签名三兄弟:1.MANIFEST.MF2.CERT.SF3.CERT.RSA

  1.MANIFEST.MF

  该内容保存的是逐一遍历 APK 中的所有条目,使用摘要算法如SHA1或者SHA256算出摘要信息,并使用base64编码后,存放到MANIFEST.MF块中每个块包含一个Name属性:存放该文件的路径

  2.CERT.SF

  SHA1-Digest-Manifest:对整个 MANIFEST.MF 文件做 SHA1(或者 SHA256)后再用 Base64 编码

  SHA1-Digest:对 MANIFEST.MF 的各个条目做 SHA1(或者 SHA256)后再用 Base64 编码

  3.CERT.RSA

  这里会把之前生成的 CERT.SF 文件,用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存

  这个数字证书就是我们android签名的时候使用的签名keystore文件:

  使用下面命令查看签名证书情况:

  penssl pkcs7 -inform DER -in /<文件存放路径>/Sample-release_new/original/META-INF/CERT.RSA -text -noout -print_certs

  完整签名流程:

  校验过程:在安装步骤校验:

  1.检查APK中所有的文件,对应的摘要值和MANIFEST.MF中的值保持一致

  2.对MANIFEST.MF中的值做二次摘要,查看是否和CERT.SF中的值保持一致

  3.使用数字证书文件CERT.RSA检查SF文件有没被修改过,使用公钥证书解密

  4.解密后的摘要信息和步骤2中的二次摘要信息对比,看下是否一致

  5.一致就通过,说明apk信息没有被更改过

  为什么使用这样的签名流程呢?

  1.更改APK文件里面的文件,MANIFEST.MF中的摘要信息不一致,校验失败

  2.在1的基础上更改MANIFEST.MF的摘要信息,那么和CERT.SF中的摘要信息会不一致,校验失败

  3.在2的基础上更改CERT.SF中的摘要信息,那么CERT.RSA中的签名信息不一致,校验失败。

  4.在3的基础上更改证书,不行,因为没有私钥。

  所有可以基本上保证数据的不可篡改性

  v2签名:

  v1缺点:

  签名速度慢,需要对每个文件进行签名完整性保持不够:因为META-INF 目录用于签名,所以不会记录到校验信息内,用户可以在这个文件中随意添加文件

  v2签名:

  就是把 APK 按照 1M 大小分割,分别计算这些分段的摘要,最后把这些分段的摘要在进行计算得到最终的摘要也就是 APK 的摘要。然后将 APK 的摘要 + 数字证书 + 其他属性生成签名数据写入到 APK Signing Block 区块。

  v2校验:

  v2 签名机制是在 Android 7.0 以及以上版本才支持。因此对于 Android 7.0 以及以上版本,在安装过程中,如果发现有 v2 签名块,则必须走 v2 签名机制,不能绕过。否则降级走 v1 签名机制。v1 和 v2 签名机制是可以同时存在的,其中对于 v1 和 v2 版本同时存在的时候,v1 版本的 META_INF 的 .SF 文件属性当中有一个 X-Android-APK-Signed 属性:X-Android-APK-Signed: 2

  v2 对多渠道打包的影响

  之前的渠道包生成方案是通过在 META-INF 目录下添加空文件,用空文件的名称来作为渠道的唯一标识。但在新的应用签名方案下 META-INF 已经被列入了保护区了,向 META-INF 添加空文件的方案会对区块 1、3、4 都会有影响。

  总结V2:

  1.V2并行计算加快签名速度2.V2保证META-INFO目录不会被篡改apk安装过程1.复制APK到/data/app目录下。解压并扫描包2.资源管理器解析apk里面的资源文件3.解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录4.然后对dex文件进行优化,并保存在dalvik-cache目录下。5.将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中6.安装完成后,发送广播。广义编译-CI

  CI 即 持续集成,在大型开发团队中,CI 的建设是重中之重,CI 主要包括 打包构建、Code Review、代码工程管理、代码扫描 等一系列流程。它的 整套运转体系 可以简化为下图:

  1、持续集成的原因

apk软件开发方法(浅谈apk从编译到安装过程)

  构建 CI 的目的主要是为了解决以下四个问题。

  1)、项目依赖复杂

  随着业务的发展,基础组件库的数量会持续上涨,这个时候组件间的关系就会变得错综复杂,这将会导致如下 两个问题:

  1、如果某个开发同学需要修改代码,极有可能会影响到其它业务,牵一发而动全身。2、人工维护组件间复杂的依赖关系非常困难。2)、琐碎的研发流程

  在日常的功能开发中,我们一般都会经 代码开发、组件发版、组件集成、打包、测试这五个步骤。如果测试发现 Bug 需要进行修复,然后会再次经历代码修改、组件发版、组件集成、打包、测试,直到测试通过交付产品。传统的研发流程如下图所示:

  可以看到,开发同学在整个开发流程中需要手动提交 MR、升级组件、触发打包以及去实时监控流程的状态,这样肯定会严重影响开发的专注度,降低研发的生产力。

  3)、与 App 性能监控体系的融合

  随着 App从 项目初期 => 成长期 => 成熟期,对性能的要求会越来越高,为了保障性能的足够稳定,我们需要制造出许多性能监控的工具,以实时监控我们应用的性能。而 App 性能监控体系必须和 CI 结合起来,以实现流程的自动化和平台化。

  4)、项目的编译构建速度缓慢

  随着 App 的体积变大,依赖变多,项目的编译构建速度会越来越慢,缓慢的编译速度会严重拖垮开发同学的研发效率。因此,提升 App 的编译构建速度刻不容缓。

  2、持续集成的主要步骤

  持续集成涉及的流程非常多,但是有 两个主要的步骤是非常重要 的,具体如下所示:

  1)、代码检查

  为了防止不符合规范的代码提交到远程仓库中,我们需要 自定义一套符合自身项目的编码规范,并使用专门的插件来检测。自定义代码检测可以通过完全自己实现或者扩展 Findbugs 插件,例如美团就利用 Findbugs 实现了 Android 漏洞扫描工具 Code Arbiter,其中 FindBugs 是一个静态分析工具,它一般用来检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比来发现可能存在的问题,它可以以独立的 JAR 包形式运行,也可以作为集成开发工具的插件形式而存在。而 FindBugs 插件具有着极强的可扩展性,只需要将扩展的 JAR 包导入 FindBugs 插件,重启 AS,即可完成相关功能的扩展。

  在 FindBugs 有一款专门对安全问题进行检测的扩展插件 Find Security Bugs,该插件主要用于对 Web 安全问题进行检测,也有极少对Android相关安全问题的检测规则。我们只需要 定制化自己的 Find Security Bugs,通过增加检测项来检测尽可能多的安全问题,通过优化检测规则来减少检测的误报 即可,这里我们可以直接使用 Android_Code_Arbiter 这个插件,它 去除了其中跟 Android 漏洞无关的漏洞,保留了与 Android 相关的,并增加了其它的一些检测项,以此形成了针对与于 Android 的源码审计工具。

  此外,我们也可以使用 第三方的代码检查工具,例如收费的 Coverity,以及 Facebook 开源的 Infer。

  然而,尽管将问题代码扫描出来了,可是还是会有不少开发同学不知道如何修改,对于这种情况,我们可以给在自定义代码扫描工具的时候,对于每一个问题检查项都给出对应的修改方针。

  我们可以据此建立一个解决项目异常的流程:建立一个服务专门每天跑项目的 Lint 检查,跑完将警告汇总分配到对应的负责人身上,并邮件告知他,直到上线。

  2)、Code Review

  Code Review 非常重要,在每一次提交代码时,我们都需要自己进行一次 Code Review,然后再让别人去 Review,以建立自身良好的技术品牌。

  有些同学可能会认为 CI 并不重要,它好像跟具体的技术并无关联。但是,我们需要知道,学会不仅仅是钻在开发角度看问题,跳脱出来,站在用户角度,站在产品角度,或许会有意外的收获。

  总结

  在本篇文章,我们即涉及到了 Android 编译的深度方面:App 的编译和打包流程、签名算法的原理,也涉及到了 Android 编译的广度方面:持续集成。因此,在我们学习的过程中,技术就像是一棵树,在顶部叶子上各个领域看似毫不相干,但是在一个领域越往下深入,各个领域相互交错到的知识或者设计方式就越多,所以技术深度和广度并不是对立面,对技术深度的探索不仅有利于你在特定领域有更深理解,更加可以帮助你轻松切换到另一个领域,特别是像前端的各细分领域的工作,很多领域的知识背后都殊途同归,而技术的广度也不是有的人说的那样不堪,在有技术深度的基础上,去拓展自己的技术广度,其实会让你对原有技术的理解变得更加地深入。

我们凭借多年的APP小程序开发经验,坚持以“个性定制 源码交付 独立部署 满意为止”为宗旨,累计为5000多家客户提供品质服务,得到了客户的一致好评。即刻开启你的小程序/APP,帮您轻松抢占千亿流量红利,助您轻松获客!
立即咨询: 13716188458 / 18588225959,助您抢占市场先机。项目经理在线

我们已经准备好了,你呢?

2024我们与您携手共赢,助您领跑移动端创新增长

售前咨询
咨询电话

13716188458

18588225959

在线留言
扫码加微信
微信
在线留言
* 请输入姓名
* 请输入有效联系方式
请输入您的需求:
* 请输入需求
提交成功