深刻探索编译插桩技术(1、编译基础)

前言

成为一名优秀的Android开发,须要一份完备的知识体系,在这里,让咱们一块儿成长为本身所想的那样~。

现现在,Gradle + 编译插桩 的应用场景愈来愈多,不管是 各类性能优化中的插件工具制做,仍是用来支持 插件化、热修复的各类插件,都会使用到这个组合,所以,掌握 Gradle + 编译插桩 技术可以大大提高咱们的技术竞争力。从本篇开始,笔者将会与你们一块儿深刻探索编译插桩技术,关于整个编译插桩技术研究系列的大纲以下所示:html

一般来讲,整个《深刻探索编译插桩技术系列》到第四篇 ASM 也就结束了,可是 ReDex 的功能实在是太强大了,以致于我不得很少花两篇的篇幅来进行深刻讲解。须要注意的是,Dex 字节码与 ReDex 的实现基本是以 C/C++ 语言为主,并且其实现较为复杂,因此我会在所有更新完 Awesome-Android-NDK 的 一至四 部分以后,才会让你们和我一块儿去深刻研究 ReDex 的实现机制前端

众所周知,编译 主要分为 词法分析、语法分析 、语义检查和代码优化 等步骤。额。。等等,别慌,这篇文章并非要讲编译原理,对于绝大多数的 Android 开发来讲,咱们能将 App 的编译和打包流程理解清楚就 OK 了。所以,咱们这篇文章主要讲的是 App 的编译过程,本篇包含的主要内容以下所示:java

  • 一、App的编译和打包流程
  • 二、编译提速
  • 三、广义的编译-CI
  • 四、总结

本篇文章多是从如今到今年年末 最简单的一篇文章 了。放轻松,好好享受,后面可能就。。。android

好了,下面,咱们就先回顾下 App 的编译和打包过程。git

1、App 的编译和打包流程

一、APK 的组成

咱们都知道,APK 实际上是一个 zip 类型的压缩包,而一个典型的 APK 一般都会包含了如下七部分的内容github

  • 一、AndroidManifest.xml:若是 App 是一本书,那么这个文件就是它的 “封面” 和 “目录” 。它记载了 App 的名称、权限声明、所包含的组件等一系列信息
  • 二、classes.dex:它是由项目源码生成的 .class 文件通过进一步地转换而生成的 Android 系统可识别的 Dalvik Byte Code。而且,因为 Android 系统中的字节码和标准 JVM 中的字节码是有区别的,因此若是 App 中引用了第三方 jar 包的话,那么一般状况下它也会被包含在 classes.dex 中
  • 三、resources.arsc:资源索引表,包含编译后的二进制资源文件。每当在 res 文件夹下放一个文件时,aapt 就会自动生成对应的 id 并保存在 .R 文件中,但 .R 文件仅仅只是保证编译程序不会报错,实际上在应用运行时,系统会根据 ID 寻找对应的资源路径,而 resources.arsc 文件就是用来记录这些 ID 和 资源文件位置对应关系 的文件
  • 四、res 目录:未编译的资源文件
  • 五、asserts:额外创建的资源文件夹resassets 的不一样在于 res 目录下的文件会在 .R 文件中生成对应的资源 ID,而 assets 不会自动生成对应的 ID,而是经过 AssetManager 类的接口来获取。
  • 六、libs 目录:若是存在的话,存放的是 ndk 编出来的 so 库
  • 七、META-INF 目录:用于保存 App 的签名和校验信息,以保证程序的完整性。当生成 APK 包时,系统会对包中的全部内容作一次校验,而后将结果保存在这里。而手机在安装这一 App 时还会对内容再作一次校验,并和 META-INF 中的值进行比较,以免 APK 被恶意篡改。其中包含以下 三个文件,以下所示:算法

    • 1)、MANIFEST.MF:其中每个资源文件都有一个对应的 SHA-256-Digest(SHA1) 签名,MANIFEST.MF 文件的 SHA256(SHA1) 通过 base64 编码的结果即为 CERT.SF 中的 SHA256(SHA1)-Digest-Manifest 值。
    • 2)、CERT.SF:除了开头处定义的 SHA256(SHA1)-Digest-Manifest 值,后面几项的值是对 MANIFEST.MF 文件中的每项再次 SHA256(SHA1) 通过 base64 编码后的值。
    • 3)、CERT.RSA:其中包含了公钥、加密算法等信息。首先,对前一步生成的 CERT.SF 使用了 SHA256(SHA1)生成了数字摘要并使用了 RSA 加密,接着,利用了开发者私钥进行签名。而后,在安装时使用公钥解密。最后,将其与未加密的摘要信息(MANIFEST.MF文件)进行对比,若是相符,则代表内容没有被修改。

接下来,咱们来看看 App 的编译和打包过程。json

二、APK 的编译打包流程

早在 深刻探索 Android 包体积优化(匠心制做) 一文中咱们就探讨过打包的部分流程,这里咱们须要更加全面地了解下。Android 官方的编译打包流程图以下所示:api

为了 了解更多打包过程当中的细节,咱们须要查看更加详细的旧版 APK 打包流程图 ,以下图所示:缓存

打包流程可简述为以下 八个步骤

  • 一、首先,.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 文件的速度会更快,而且会减小其在设备上运行时的内存占用

至此,咱们已经了解了整个 APK 编译和打包的流程。

那么,为何 XML 资源文件要从文本格式编译成二进制格式?

主要基于如下 两点缘由

  • 一、空间占用更小由于全部 XML 元素的标签、属性名称、属性值和内容所涉及到的字符串都会被统一收集到一个字符串资源池中,而且会去重。有了这个字符串资源池,原来使用字符串的地方就会被替换成一个索引到字符串资源池的整数值,从而能够减小文件的大小
  • 二、解析效率更高二进制格式的 XML 文件解析速度更快。这是因为二进制格式的 XML 元素里面再也不包含有字符串值,所以就避免了进行字符串解析,从而提升了解析效率

而 Android 资源管理框架又是如何快速定位到最匹配资源的?

主要基于两个文件,以下所示:

  • 一、资源 ID 文件 R.java赋予每个非 assets 资源一个 ID 值,这些 ID 值以常量的形式定义在 R.java 文件中
  • 二、资源索引表 resources.arsc用来描述那些具备 ID 值的资源的配置信息

除此以外,APK 的签名也是相当重要的,那么,其签名算法的实现原理是怎样的呢?下面咱们就来了解下 APK 签名算法的实现原理。

三、签名算法的原理

什么是签名?

在 Apk 中写入一个 “指纹”。指纹写入之后,Apk 中有任何修改,都会致使这个指纹无效,Android 系统在安装 Apk 进行签名校验时就会不经过,从而保证了安全性

那么,为何要签名?

主要有 两点缘由,以下所示:

  • 一、确保 Apk 来源的真实性
  • 二、确保 Apk 没有被第三方篡改

在了解 APK 签名的实现以前,咱们还必须知道什么是数字摘要。

数字摘要

对一个任意长度的数据,经过一个 Hash 算法计算后,均可以获得一个固定长度的二进制数据,这个数据就称为 “摘要”

在签名和校验的流程之中,应用了许多密码学的知识,这里咱们须要先大体了解一下。

Hash(散列算法)的基础原理

Hash 算法就是 将数据(如一段文字)运算变为另外一固定长度值。它的特色主要有以下 三点

  • 一、惟一性
  • 二、固定长度比较经常使用的 Hash 算法有 MD5 和 SHA1,MD5 的长度是128位,SHA1 的长度是160位
  • 三、不可逆性

而经常使用的 Hash 算法有以下 三种

  • 一、SHA-1在密码学中,SHA-1(安全散列算法1)是一种加密散列函数,它接受输入并产生一个160 位(20 字节)散列值,称为消息摘要
  • 二、MD5MD5 消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被普遍使用的密码散列函数,能够产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致
  • 三、SHA-2名称来自于安全散列算法2(Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,其下又可再分为六个不一样的算法标准,包括了:SHA-22四、SHA-25六、SHA-38四、SHA-5十二、SHA-512/22四、SHA-512/256

签名和校验的主要过程

签名就是 在摘要的基础上再进行一次加密,对摘要加密后的数据就能够看成数字签名。

签名过程:

签名过程能够细分为 三步,以下所示:

  • 一、计算摘要经过 Hash 算法提取出原始数据的摘要
  • 二、计算签名再经过基于密钥(私钥)的非对称加密算法对提取出的摘要进行加密,加密后的数据就是签名信息
  • 三、写入签名将签名信息写入原始数据的签名区块内

校验过程:

校验过程一样也能够分为 三步,以下:

  • 一、提取摘要首先用一样的 Hash 算法从接收到的数据中提取出摘要
  • 二、解密签名使用发送方的公钥对数字签名进行解密,解密出原始摘要
  • 三、比较摘要若是解密后的数据和提取的摘要一致,则校验经过;若是数据被第三方篡改过,解密后的数据和摘要将会不一致,则校验不经过

那么,咱们该如何保证公钥的可靠性呢?答案是 数字证书

数字证书

数字证书是 身份认证机构(Certificate Authority)颁发的,主要包含了如下 六类信息

  • 一、证书颁发机构
  • 二、证书颁发机构签名
  • 三、证书绑定的服务器域名
  • 四、证书版本、有效期
  • 五、签名使用的加密算法(非对称算法,如 RSA)
  • 六、公钥等

接收方收到消息后,须要先向 CA 验证证书的合法性,再进行签名校验

须要注意的是,Apk 的证书一般是自签名的,也就是由开发者本身制做,没有向 CA 机构申请。Android 在安装 Apk 时并无校验证书自己的合法性,只是从证书中提取公钥和加密算法,这也正是对第三方 Apk 从新签名后,还可以继续在没有安装这个 Apk 的系统中继续安装的缘由。

keystore 和证书格式

keystore 文件中包含了 私钥、公钥和数字证书。根据编码不一样,keystore 文件分为不少种,Android 使用的是 Java 标准 keystore 格式 JKS(Java Key Storage),因此经过 Android Studio 导出的 keystore 文件是以 .jks 结尾的。

keystore 使用的 证书标准是 X.509,X.509 标准也有多种 编码格式,经常使用的有两种:pem(Privacy Enhanced Mail)和 der(Distinguished Encoding Rules)jks 使用的是 der 格式,可是,Android 也支持直接使用 pem 格式的证书进行签名

下面,咱们了解下两种证书编码格式的区别,以下所示:

  • DER(Distinguished Encoding Rules)二进制格式,全部类型的证书和私钥均可以存储为 der 格式
  • PEM(Privacy Enhanced Mail)base64 编码,内容以-----BEGIN xxx----- 开头,以-----END xxx----- 结尾

jarsigner 和 apksigner 的区别

Android 提供了 两种对 Apk 的签名方式,一种是基于 JAR 的签名方式,另外一种是基于 Apk 的签名方式,它们的 主要区别在于使用的签名文件不同:jarsigner 使用 keystore 文件进行签名;而 apksigner 除了支持使用 keystore 文件进行签名外,还支持直接指定 pem 证书文件和私钥进行签名

在咱们签名时,除了要指定 keystore 文件和密码外,也要指定 alias 和 key 的密码,这是为何呢?

keystore 是一个密钥库,也就是说它能够存储多对密钥和证书,keystore 的密码是用于保护 keystore 自己的,每一对密钥和证书是经过 alias 来区分的。因此 jarsigner 是支持使用多个证书对 Apk 进行签名的,apksigner 也一样支持。

Android Apk V1 验证签名的原理

Android Apk V1 验证签名的过程主要能够分为以下 四步

  • 一、解析出 CERT.RSA 文件中的证书、公钥,解密 CERT.RSA 中的加密数据
  • 二、解密结果和 CERT.SF 的指纹进行对比,保证 CERT.SF 没有被篡改
  • 三、接着,将 CERT.SF 中的内容再和 MANIFEST.MF 中的指纹对比,保证 MANIFEST.MF 文件没有被篡改
  • 四、MANIFEST.MF 中的内容和 APK 全部文件指纹逐一对比,保证 APK 没有被篡改

在整个 App 的编译打包过程当中,Gradle 自动化构建工具发挥出了重要做用,而编译速度但是须要咱们迫切解决的一大痛点。下面,咱们就来看看如何对编译进行提速。

2、编译提速

一、了解 Android Studio 3.0 依赖类型的变化

Android Studio 3.0 以前 共有 六种 依赖方式,以下所示:

  • 一、Compile对全部的 build type 以及 falvors 编译而且打包到 APK
  • 二、Provided对全部的 build type 以及 falvors 只编译,不打包到 APK
  • 三、APK只会打包到 APK,不参与编译,好比引用 jar 中的类或者方法, 编译时就会报错
  • 四、Test compile仅对单元测试的代码和打包的测试 APK 有效,而对 debug 或者 release APK 包无效
  • 五、Debug compile仅对 debug 模式的编译和打包的 debug APK 有效,而对 test 或者 release APK 打包无效
  • 6、Release compile仅对 Release 模式的编译和打包的 Release APK 有效,而对 test 或者 debug APK 打包无效

而在 Android Studio 3.0 以后,新增了两种方式:api 和 implementation。其中 api 彻底等同于 compile

api

等同于 compile, 用 api 指令编译,表示 三方库的依赖对 module 是可见的,即等同 app Module 可使用此三方库依赖。

implementation

特色是 将该依赖隐藏在内部,而不对外部公开。好比在组件化项目中,有一个 app module 和一个 base module,app moudle 引入了 base module。其中 base module 使用 implementation 依赖了 Glide 库,由于 implementation 是内部依赖,因此是没法调用到 Glide 库的功能的。所以 implementation 可 以 对外隐藏没必要要的接口,而且,使用它能够有效地 提升编译速度。好比,在组件化项目中通常含有多个 Moudle 模块,如 Module A => Module B => Moudle C, 好比 改动 Moudle C 接口的相关代码,若是使用的是 implementation,这时候编译只须要单独编译 Module B 模块就行,可是若是使用 api 或者旧版本的 compile,由 于Module A 也能够访问到 Moudle C,因此 Module A 部分也须要从新编译。因此,在使用无错的状况下,能够优先使用 implementation

二、现有编译方案

Gradle 的官方方案 Instant Run在 Android Plugin 2.3 以前,它使用了 Multidex 实现。在 Android Plugin 2.3 以后,它使用了 Android 5.0 新增的 Split APK 机制。以下图所示:

image

可是,若是你的应用较大,会有以下四个问题:

  • 一、多进程的限制若是应用存在多进程,热交换和温交换都不能生效。此时,Instant Run 的速度就会下降很多
  • 二、Split APK 安装耗时虽然 Split APK 的安装不会生成 Odex 文件,可是这里依然会进行签名校验和文件拷贝,这可能须要几秒到几十秒
  • 三、Annotation Processor 需全量 javac 的问题在 Gradle 4.6 及以前,若是项目中运用了 Annotation Processor,本次修改以及它依赖的模块都须要全量 javac,这可能会须要几十秒
  • 四、常量需全量 javac 的问题此时,常量池会直接把值编译到其余类中,Gradle 并不知道有哪些类使用了这个常量

阿里的 FreeLine 在大部分状况比 Instant Run 更快,可是,它 牺牲了正确性。由于,为了追求更快的速度,它直接忽略了 Annotation 和常量改变可能带来错误的编译产物。而 Instant Run 做为官方方案,它优先保证了 100% 的正确性

可是,在 Android Studio 3.5 以后,Android 8.0 之后的设备将会使用新的方案 Apply Changes 去代替 Instant Run。而 ApplyChange 采用了跟 InstantRun 不同的原理来加快 AndroidStudio 部署安装 APK 的流程。下面,咱们就来了解下他们之间的区别。

InstantRun

InstantRun 主要解决如下两个问题:

  • 一、减小构建和部署 app 到手机的时间
  • 二、热更新代码改动,无需重启 app 或者 activity

为了实现这两个目标,InstantRun 经过重写 apk 的构建流程往每一个类里去注入 Hook(钩子) 来达到类的热替换。关于 InstantRun 详细的实现原理能够看看我以前写的深刻探索Android启动速度优化一文。

对于小型的应用,InstantRun 确实很好用,可以节省构建和部署的时间,而且不会出错。可是,对于大型的复杂应用,它会致使更长的构建时间,同时因为 InstantRun 构建过程和正常的 app 构建存在冲突,经常出现让开发者意想不到的错误。AS 开发团队在连续几个大版本中都尝试去解决这些问题,可是效果不理想。

因此基于此,AS 开发者团队 从新设计了底层的架构,推出了 ApplyChangs。和 InstantRun 不一样的是,它不会在构建过程当中去修改 apk。取而代之,它使用了 Android 8.0(Oreo)上支持的 Runtime Instrumentation 以及更新的设备和模拟器在运行时重定义类

ApplyChanges

对于 运行在 Android 8.0 或者更新版本上的设备和虚拟机Android Studio 如今有 三个按钮 来控制应用程序重启的程度:

  • Run会部署全部的改动并重启应用程序
  • Apply Changes会尝试应用资源和代码的更改,并只重启 Activity, 而不是重启应用程序
  • Apply Code Changes会尝试应用代码的更改,而不重启任何东西

一般只有方法体内部的代码更改才会对 Apply Changes 具备兼容性。而 ApplyChanges 的 实现原理 就是找出 AndroidStudio 构建出来的 apk 和已经安装到手机设备 apk 的差别。找出差别后,而后将差别发送到手机上执行差别合并。ApplyChanges 的 整体架构 以下图所示:

image

那么,理想的编译方案是怎么样的呢?

三、理想的编译方案

咱们能够把安装的 Base APK 做为一个壳 APK,而真正的业务代码都放到 Assets 的 ClassesN.dex 中。该方案须要包含如下 三个优化点

  • 一、免安装能够参考 Tinker 热修复的实现原理,每次只把修改以及依赖的类插入到 pathclassloader 的最前方便可
  • 二、使用 ReDex 源码中的 Oatmeal首次安装运行时 ClassesN.dex 转换成 Odex 很耗时,咱们可使用 ReDex 优化模块下的 Oatmeal,经过它应用能够在 100 ms 内生成一个彻底解释执行的 Odex 文件
  • 三、关闭 JIT 优化:使用把修改和依赖的类插入到 pathclassloader 最前方的这种方式在 Android N的混合编译 会遇到一些问题:不管是使用插入 pathlist 仍是 parent classloader 的方式,若补丁修改的 class 已经存在于 app image(app image 的做用是记录已经编译好的 “热代码”,而且在启动时一次性把它们加载到缓存,而预先加载是为了代替用时查找以提高应用的性能),它们都是没法经过热补丁更新的。它们在启动 app 时已经加入到 PathClassloader 的 ClassTable 中,所以系统在查找类时会直接使用 dex 中的 class。这时咱们须要关闭虚拟机的 JIT 优化,经过 在 AndroidManifest 指定 android:vmSafeMode=“true” 便可。

四、编译速度优化

除了将电脑更换为 Mac Pro 顶配版以外,还有如下方式能够提高编译速度:

  • 一、及时升级 Gradle 和 Build Tools 编译工具链,充分利用谷歌最新的优化成果
  • 二、能够将项目中基本不变的模块拆离出去,使用远端 Cache 的模式保留编译后的缓存,具体的搭建流程可参见 Caching for faster builds
  • 三、使用 Android Gradle Plugin 3.0.0 和更高的版本,由于此时在默认状况下启用 AAPT2,它替代了 AAPT 来编译资源,并实现了资源的增量编译,其中并将资源的编译拆分为了两个步骤:Compile 和 Link。Compile 负责将资源文件编译为二进制格式,Link 则会合并全部已编译的文件,并将它们打包到一个软件包中
  • 四、Android Studio 3.1 开始默认使用 D8 编译器(3.0推出),它取代了以前的 dx 工具,将 .class 文件转换为 Dex 文件,使用它可以提高编译速度和减小生成的 Dex 的大小。此外,在 Android Studio 3.1 开始,你能够在 gradle.properties 开启 R8,它的目标是取代混淆和 D8,对于 D8 和 R8 的详细介绍,具体能够看看我写的深刻探索Android包体积优化一文
  • 五、此外,能够尝试将构建系统切换到 Buck 或使用 Flutter 混合开发,Flutter 中的 Hot Reload秒级编译功能比较强大,其堪称为开发者的神兵利器,它可以在快速修改 UI,增长功能,修复bug的状况下,而不须要去从新启动应用,便可看到改动效果
  • 六、最后,Gradle 官方有一个 build-scan 的功能,它能够生成构建期间的详细报告,其中有性能相关的统计,能够用于帮助分析一些耗时的 task

那么,Flutter 的 Hot Reload 的实现原理是什么呢?

在回答这个问题以前,咱们必须先了解 Flutter 的编译模式

Flutter 的编译模式

编译模式大致能够分为 两种,以下所示:

  • AOT(Ahead Of Time)编译是在程序运行前就已经编译,所以,在运行时不须要进行分析、编译,所以执行速度更快
  • JIT(Just In Time)编译代码能够在程序执行时期编译,由于要在程序执行前进行分析、编译,JIT 编译可能会致使程序执行时间较慢

而 Flutter 使用了不同凡响的编译模式,在开发阶段下,使用了 Kernel Snapshot 模式(对应 JIT 编译),将 dart 代码生成了标记化的源代码,而在运行时编译使用的是解释执行。在 release 阶段,iOS 使用 AOT 编译,编译器将 dart 代码生成汇编代码,最终生成 app.framwork,而 android 使用了 Core JIT 编译,将 dart 转化为二进制模式,并在 VM 启动前载入

所以,在开发阶段的 Kernel Snapshot 编译模式下,Hot Reload 会经过扫描项目文件,将有改动的 dart 文件转化为标记化源代码 kernel files,并发送到正在运行的 DartVM,等待 DartVM 替换资源,而后通知 Flutter Framework 重建、从新布局、从新绘制 WidgetsTree,便可看到改动效果

那么,flutter 又是如何触发 WidgetsTree 的重建呢?

Flutter framework 中 BindingBase 注册了名为 reassemble的Dart VM 服务,用于外部与正在运行的 Dart VM 通讯,这样,便可以触发根节点树实现重建操做。当 Hot Reload 致使需重建 WidgetsTree时,reassemble 的 Dart VM 服务就会被触发,触发后,就会由根节点开始一步步实现widgets树重建,其重建流程以下所示:

ext.flutter.reassemble => BindingBase.reassembleApplication => 
WidgetsBinding.performReassemble => BuildOwner.reassemble => Element.reassemble

3、广义的编译-CI

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

image

一、持续集成的缘由

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

一、项目依赖复杂

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

  • 一、若是某个开发同窗须要修改代码,极有可能会影响到其它业务,牵一发而动全身
  • 二、人工维护组件间复杂的依赖关系很是困难

二、琐碎的研发流程

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

image

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

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

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

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

随着 App 的体积变大,依赖变多,项目的编译构建速度会愈来愈慢,缓慢的编译速度会严重拖垮开发同窗的研发效率。所以,提高 App 的编译构建速度刻不容缓。

二、持续集成的主要步骤

持续集成涉及的流程很是多,可是有 两个主要的步骤是很是重要 的,具体以下所示:

一、代码检查

为了防止不符合规范的代码提交到远程仓库中,咱们须要 自定义一套符合自身项目的编码规范,并使用专门的插件来检测。自定义代码检测能够经过彻底本身实现或者扩展 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 检查,跑完将警告汇总分配到对应的负责人身上,并邮件告知他,直到上线。

二、Code Review

Code Review 很是重要,在每一次提交代码时,咱们都须要本身进行一次 Code Review,而后再让别人去 Review,以创建自身良好的技术品牌。

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

4、总结

到这里,关于 Android 编译相关的知识就介绍完了。下面,总结一下本篇文章涉及的 三大主题

  • 一、App 的编译和打包流程APK 的编译打包流程、签名算法的原理
  • 二、编译提速了解 Android Studio 3.0 依赖类型的变化、现有编译方案、理想的编译方案、编译速度优化
  • 三、广义的编译-CI持续集成的缘由、持续集成的主要步骤

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

参考连接:

一、极客时间之Android开发高手课《关于编译,你须要了解什么?》

二、《深刻理解Android内核设计思想》第20章 Android应用程序的编译和打包

三、Android应用程序资源的编译和打包过程分析

四、Caching for faster builds

五、Hot Reload秒级编译

六、build-scan

七、Code Arbiter

八、Find Security Bugs

九、Infer

十、MCI:移动持续集成在大众点评的实践

十一、aapt2官方教程

十二、FreeLine

1三、Apply Changes

1四、Android N的混合编译

Contanct Me

● 微信:

欢迎关注个人微信: bcce5360

● 微信群:

微信群若是不能扫码加入,麻烦你们想进微信群的朋友们,加我微信拉你进群。

● QQ群:

2千人QQ群, Awesome-Android学习交流群,QQ群号:959936182, 欢迎你们加入~

About me

很感谢您阅读这篇文章,但愿您能将它分享给您的朋友或技术群,这对我意义重大。

但愿咱们能成为朋友,在 Github掘金上一块儿分享知识。