增量编译简介

增量编译是绝对全量编译而言的。所谓增量编译,是指当源程序的部分产生变更后进从新编译的工作只限于批改的局部及与之相干局部的内容,而不须要对全副代码进行编译。增量编译对软件开发,尤其是在调试期,能够大大缩短编译工夫, 进步编译效率。

而全量编译指的是,当用户源程序被部分批改后从新编译代码会波及全副源代码,并不只限于部分批改及其相干局部。换句话说,无论批改了什么,全量编译都将进行一次全新的残缺的编译,并不基于上一次的编译根底。

一般来说,在软件开发中,全量编译用于版本的构建与公布,比拟消耗工夫和资源。而处于调试阶段的程序,个别都采纳增量编译,这样对于问题的定位和解决都比省时省力。在Android开发中,随着工程代码量收缩,编译耗时也越来越长,拖慢了开发效率,因而Android官网推出了Instant Run和Apply Changes等增量更新的计划。

Instant Run简介

Instant Run是Android Studio 2.0版本推出的一个增量编译性能,应用Instant Run性能时,须要在build.gradle 文件中将 minSdkVersion 设置为 15 或以上时,并且为另外获得最佳性能,能够将 minSdkVersion 设置为 21 或更高。

之前在Android Studio 3.0版本,gradle为2.14.1的版本中做过一个测试,编译一个简略的Demo我的项目从之前的10秒升高到大略2、3秒。默认状况下,Instant Run是敞开的,如果要开启Instant Run,能够在Settings中关上Instant Run,如需所示。

不过,Android Studio在3.5版本废除了Instant Run,并应用HotSwap代替了Instant Run,如下图所示。

对于Instant Run的一些原理方面的内容,能够参考我之前的文章介绍:深刻了解Android Instant Run运行机制。

Apply Changes

在Android Studio 3.5及其以上版本,官网提供了Apply Changes,应用Apply Changes时,须要满足以下两个条件:

  • apk必须是debug包;
  • Android 8.0及以上的手机上运行

当咱们应用Android Studio运行我的项目后,会在菜单栏看见3个按钮,别离用来管制利用重启,如下图所示。

如上图所示,从左到右的按钮别离示意【Run】、【Apply Changes 】和【Apply Code Changes】。

  • Run:将部署所有的变动并重启利用 。
  • Apply Changes: 将尝试利用变动的资源和代码,并仅重启Activity而不须要重启整个利用。
  • Apply Code Changes :将尝试在不重启操作的状况下利用变动的代码,如果只有代码批改,能够应用此按钮来使代码失效。

不过,因为Apply Changes仅反对在Android 8.0 或者更高版本的手机上运行,并且实际操作时在工程中带来的提速成果也不显著。

Freeline

除了官网的计划外,阿里巴巴客户端团队还基于动静替换研发了一款针对Android平台的增量编译工具,它能够充分利用缓存文件,在几秒钟内迅速地对代码的改变进行编译并部署到设施上,无效地缩小了日常开发中的大量从新编译与装置的耗时。

性能方面:外部采纳了相似Facebook的开源工具buck的多工程多任务并发思维:端口扫描,代码扫描,并发编译, 并发dx,并发merge dex等策略,在多核机器上有显著减速成果,另外在class及dex,resources层面作了相应缓存策略,做到真正增量开发,另外引入并优化 buck的局部减速组件dx,DexMerger,资源编译方面,深刻革新了Aapt资源编译流程,当资源产生扭转时候,秒级实现增量包编译,其中增量包 仅含最小的变更汇合(10Kb~数百Kb内),前期也被使用到线上进行资源/代码动静替换。相比目前instant- run,buck,layoutcast等计划快数倍速度。

不过,Freeline同样存在着一些不可漠视的问题。首先是不反对Kotlin,这在Kotlin曾经被谷歌官宣为Android开发首选语言的明天,是比拟致命的。另外,不反对删除带id的资源,否则可能导致资源编译流程出错。

另外一个潜在的问题是,为了确保编译速度,Freeline是就义了一部分正确性的。例如,在改变私有动态常量的时候,只会编译对应的类文件,而援用到该常量的其余类,并不会参加编译的。因为常量内联优化的存在,就可能导致这些类在运行时,应用的依然是旧的值,进而呈现改变不失效的问题。

Android 编译打包流程

对于Android是如何从源码到安装包的过程,能够参考Android官网给的一幅图,次要会经验编译、链接和签名等操作。

下面展现的是Android源码编译成安装包的过程,而增量更新的残缺的流程是: 【批改代码】->【编译工程】->【装置APK】->【运行验证】。

对于编译阶段,首先是收集工程中的所有资源文件进行编译,失去资源包以及资源索引类。随后资源索引类会追随工程的所有源代码文件一起被编译为字节码文件,并且字节码文件还须要被进一步编译为Dex文件,这样能力被Android虚拟机所辨认。

Android的编译打包会分为以下几个阶段:

  • R文件的生成:R文件记录了每个资源的ID,之后要参加到java的编译过程,R文件是由aapt(Android Asset Package Tool)生成。
  • Java(Kotlin)源代码:咱们晓得有时app开发中会跨过程通信,这时能够通过aidl的形式定义接口,aidl工具能够依据aidl文件生成对应的java文件。之后R文件、aidl相干java文件、src中的java文件通过编译生成 .class文件。
  • dex生成:编译后的.class会又由dex工具打包成dex文件,其中,Android增量打包工具freeline中用到了Buck中提取的dex工具,freeline给出的数据是比原生的dex工具快了40%

资源文件编译

  • aapt(Android Asset Package Tool):aapt工具对app中的资源文件进行打包和归档。

下图残缺的演示了Android编译期和运行期的整个步骤。

增量编译原理

Android增量编译分为代码增量和资源增量,Android晚期的Instant Run计划在资源上并不是增量的,而是把整个利用的资源打成资源包,推送至手机的,因而效率极低。

代码编译

谷歌在反对multidex之后(即典型的65535问题),Android打包后会存在多个dex文件,运行时加载类时,会从一个dexList顺次查找,找到则返回,利用这个原理能够把增量的代码打包成dex文件,插入到dexList的前边,这样就能够实现类的替换。

对于代码的增量编译须要思考两个次要的问题,即获取改变文件并进行编译、对依赖的代码进行编译。对于代码的增量编译,能够参考QQ音乐的增量编译计划:QQ音乐Android编译提速之路

资源编译

资源编译与代码增量是相似的,即先收集被改变的资源,而后进行编译。Android的资源编译次要应用的是aapt或者aapt2。一般来说当初都是应用aapt2来进行资源的打包编译,因为aapt工具是不反对单个资源编译的。

aapt2(Android 资源打包工具)是Android Studio 和 Android Gradle 插件应用它来编译和打包利用的资源构建工具。aapt2 会解析资源、为资源编制索引,并将资源编译为针对 Android 平台进行过优化的二进制格局。

应用aapt2进行资源打包编译时,分为编译(compile)与链接(link) 两步,在编译阶段,负责将单个或者多个资源编译为二进制文件;链接阶段,则负责合并所有二进制文件再打包。

对于资源的增量编译,能够参考QQ音乐的增量编译计划:QQ音乐Android编译提速之路

有赞 Android 编译计划Savitar

当我的项目通过屡次迭代之后,就会遇到各种各样的问题,而编译慢是每个成熟 Android 团队都无奈回避的问题。在之前有赞批发 Android 团队的技术分享中,整个Android我的项目有 25 个业务模块,领有 45W+ 行源代码(Java + Kotlin)以及多个构建 Flavor。小伙伴在进行需要开发时,均匀的增量编译构建工夫达到了两分钟,再加上一些 Gradle 配置与APK装置过程,基本上验证一行代码的批改须要近三分钟(MacBook Pro 13-inch, 2016, i5-8G),这样的状况大大降低了团队的开发效率。

在 Savitar 诞生之前,咱们尝试了社区中一些成熟的解决方案,如 BUCK、Freeline、InstantRun 等出名框架。不过调研下来,都或多或少的存在一些问题。

比方FaceBook的BUCK框架,本身有弱小的构建零碎,通过增量构建缓存机制,能够无效晋升编译的速度,然而其应用和配置过于简单,对于工程的入侵比拟大,且对于一些 Databinding、 Kotlin 等 Android 的个性反对还有欠缺。

其次是阿里巴巴开源的Freeline ,Freeline以其极快的部署速度闻名,但对咱们来说致命毛病是不反对 Kotlin。

InstantRun 是 Google 举荐的减速形式,领有最全面的支持性,但因为咱们是多过程的工程,并且 InstantRun 在编译时的一些筹备 Task 也会耗费一些工夫,在实际过程中发现减速并不显著。

计划实现

Savitar 是有赞 Android 团队增量编译提效计划,它可能无效缩小模块批改编译工夫,蕴含配套 IDE 插件,使用方便,具备如下一些显著的特点。

    • 反对Java、Kotlin的增量编译
    • 反对layout、values、assets、images等资源文件的增量编译
    • 提供GUI界面插件
    • 基于 Git调试、多分支治理,能够依据理论状况更换分支

    结构设计

    如图所示,Savitar 整体分成四个局部:

    • GUI 插件局部:面向使用者的 GUI 界面,外部蕴含了可运行 Jar(以下简称 Runner)的自动更新、各种查看工作、编译脚本调用执行
    • Runner 局部:一个 Jar 包,蕴含 Savitar 外围逻辑代码,实现批改获取、脚本生成、编译执行等工作
    • 工程反对局部:一个 Gradle 插件,实现对工程信息的获取和产物加载代码的插入
    • 内部依赖局部:实现整个流程所须要的内部依赖程序

    下图演示了Savitar从代码批改到实现批改产物加载运行的残缺过程。

    能够发现,从代码批改到实现批改产物次要经验了以下几个步骤:

    1. 获取改变信息:获取代码和资源批改,是整个过程的前提
    2. 获取工程信息:获取以后工程的依赖信息,目录信息和 Git 信息,为后续编译做筹备
    3. 编译生成产物:进行代码、资源编译,生成 Dex 产物和 Apk 产物
    4. 重启加载产物:实现对编译产物的加载运行,实现整个减速过程

    对于Savitar是如何从获取改变信息到实现加载,能够参考Android 增量编译提效计划Savitar的具体介绍。

    如何应用

    为了不便开发者应用Savitar实现增量更新,Savitar 开发了一款 IDE 插件,只须要一键触发就能够实现整个编译打包流程。首先,关上Android Studio ,而后顺次抉择 【Preference】 -> 【Plugin】 搜寻Savitar并装置,如下图所示。

    装置实现后重启 IDE,而后在 Android Studio 中工具栏就会呈现 Savitar 的图标,如下图所示。

    点击图标后,能够在 Savitar Window 看到工具编译、打包、推送整个运行过程,蕴含错误信息,如下图所示。

    常见文件解答

    是否反对,如何反对Kotlinx ?

    如下所示,有上面一段代码:

    import kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() {      override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        buttonCpu.setOnClickListener {            ... //点击事件        }    }}

    对于下面的代码,想必应用过 Kotlin 的 Android 同学并不会生疏,利用 Kotlinx 个性,能够在 .kt 代码中应用 Xml 中定义过组件Id间接获取 View 实例进行操作,极大缩小 UI 开发成本。

    然而下面代码中的 import 并不是一个一般的模式,这样的语法如果间接应用规范 kotlinc 进行编译,会呈现找不到 import 谬误。此时须要借助到 Kotlin 编译器插件,在 Kotlin 编译时传入 Kotlinx 对应插件的 Jar 地址和参数,就能够实现蕴含 Kotlinx 语法的文件编译。

    sh kotlinc  -Xplugin=lib/android-extensions-compiler.jar-P plugin:org.jetbrains.kotlin.android:package=${package_name}-P plugin:org.jetbrains.kotlin.android:variant='${flovar};${resource_package}'

    文档参考 Kotlin 编译器插件

    Kotlinc 环境变量

    在应用 Android Studio 开发过程中,Kotlin 编译所需的依赖包都是由 IDE 主动治理,然而 Savitar 是应用 Shell 实现,这样的状况上面就须要关怀这个编译工具的问题了。咱们将获取 Kotlin 编译依赖的逻辑放在 Savitar 运行环境检测逻辑中,在检测到没有依赖包的状况下会主动从内网服务器下载对应版本的库,实现 Kotlin 代码编译。

    参考:
    QQ音乐Android编译提速之路
    Android 增量编译提效计划Savitar