Freeline - Android平台上的秒级编译方案



摘要: Freeline是蚂蚁金服旗下一站式理财平台蚂蚁聚宝团队在Android平台上的量身定做的一个基于动态替换的编译方案,稳定性方面:完善的基线对齐,进程级别异常隔离机制。性能方面:内部采用了类似Facebook的开源工具buck的多工程多任务并发思想, 并对代码及资源编译流程做了深入的性能优化。

Freeline

技术揭秘

Freeline是什么?

Freeline是蚂蚁金服旗下一站式理财平台蚂蚁聚宝团队15年10月在Android平台上的量身定做的一个基于动态替换的编译方案,5月阿里集团内部开源,稳定性方面:完善的基线对齐,进程级别异常隔离机制。性能方面:内部采用了类似Facebook的开源工具buck的多工程多任务并发思想:端口扫描,代码扫描,并发编译,并发dx,并发merge dex等策略,在多核机器上有明显加速效果,另外在class及dex,resources层面作了相应缓存策略,做到真正增量开发,另外引入并优化buck的部分加速组件dx,DexMerger,资源编译方面,深入改造了Aapt资源编译流程,当资源发生改变时候,秒级完成增量包编译,其中增量包仅含最小的变更集合(10Kb~数百Kb内),后期也被运用到线上进行资源/代码动态替换。相比目前instant-run,buck,layoutcast等方案快数倍速度。

有何优势?

1.真增量,构建过程快且增量包体积小,极大提升更改代码部署到手机速度,较Android studio2.0及 LayoutCast快3~5倍

2.跨平台Linux,mac,windows

3.全版本覆盖 2.x ~ 6.x版本均支持

4.部署流程简化,更改代码后,构建过程中,与手机建立了tcp长连接,一行命令即可完成增量部署,毋需到各自子bundle所在的目录构建完成后再进入portal/launcher进行打包再安装到手机的过程

5.事务支持,在开发过程引入的异常不会破坏工作空间

6.无缝支持mPass,解决了类似maven各个节点需merge合并等与常规开发流程不一致的问题

7.进程级别异常隔离,开发体验持续稳定

谁在用?

目前 Freeline 在阿里内部稳定支撑蚂蚁聚宝,高德地图等ANDROID技术团队日常开发,兼容mPaas/gradle架构

开源地址:
https://github.com/alibaba/freeline

技术内幕:方案对比

先看看传统的Android打包流程:


                                      4c1a0d0bbf2efd47af05db3f26d20044624891a0

单线程沿着流水式的任务从上到下进行打包构建,其中,aapt会执行2次,第一次是生成R.java,参与javac编译,第二次是对res里面的资源文件进行编译,最后APKBuilder会把DEX文件与编译好的资源文件及DEX文件进行打包成APK,签名并安装至手机。整个流程下来,没有任何缓存,没有并发,也没有增量,每次构建都是一个全新的过程,所以每次构建时间也比较恒定,代码量,资源量越多,构建时间越慢。


下面对业内3个比较主流的增量构建方案进行对比分析:

LayoutCast

增量的思想源自于LayoutCast,与LayoutCast不同的是,Freeline把连接设备与各个工程间扫描及构建增量包任务仿照Buck的思路进行拆解,而LayoutCast是传统流水式任务构建,性能可能会被中间某个环节的耗时被拖慢,没有充分利用到多核优势。另外在资源变化后,LayoutCast选用的方案与Android Studio2.0 instant-run思路一致 ,把整个应用的资源打成资源包,推送至手机,若资源包大小较大,该过程耗费的时间相当可观,而资源包的大小直接影响了后面通过tcp传输的耗时,从资源变更的角度的来说,LayoutCast并实现没有真正意义上的增量。

另外,LayoutCast通是过运行期手机端反射R Class field的方式来生成ids.xml及public.xml,用于保证增量包的资源id与全量包的资源id一致,该方案存在几个缺陷:

1.通过手机端运行期反射,R有上万个field,效率相当低下,Galaxy note4上耗费近1s
2.存在致命缺陷,举个例子,app声明的attrs.xml若存在一个定义如下:

 <attr name="ptrMode">
       <flag name="pullFromStart" value="0x1" />
        <flag name="pullFromEnd" value="0x2" />
   </attr>

其中 ”pullFromStart“,”pullFromEnd” 对应的id值如下:

    public static final class id {
        public static final int pullFromEnd=0x56050005;
        public static final int pullFromStart=0x56050004;
   }

实际上上面2个枚举常量生成的id的type类型是“id”,若生成ids.xml及public.xml时候,不排除这些枚举id,最终的结果就是aapt给每个资源分配id时候,发生数组越界,aapt程序coredump掉,无法构建出资源包,而手机端运行期反射时候,仅知道”pullFromStart“,”pullFromEnd”为id类型,不足以知道其对应的是枚举常量,故仅仅通过运行期反射,上述致命缺陷无法解决。
再有,由于没有缓存机制,LayoutCast编译速度会随着修改文件的增加越来越慢。最后,由于代码增量使用的是dex插入系统dexlist最前位置的方式,在4.x的机器上面系统安全校验不通过,所以LayoutCast并不支持5.0以下的手机。

BUCK

下面说说BUCK,先看一幅其官方的构建过程图

175f4f1bc9d2ab40c7f03fc1b7e4e413dd6205ba

BUCK把原来单流水线任务以工程为单位拆分成多个可并发执行的子任务节点,梳理好各个节点前后的依赖关系,整理出有向拓扑图,通过多条线程并发把各个子任务节点构建出来,充分利用多核优势。

BUCK建立了一套完善的依赖规则以及细化的缓存系统来缩减编译时间,其增量构建的原理,实际是以工程目录为单位进行增量构建,发生变更时候,变更的工程,以及该工程作为父节点或祖先节点的工程,均需要重新构建,构建完这些变更涉及的工程后,Buck需要重新走一次合并各工程DEX,对齐,签名,打包APK的过程,构建完毕后,还要继续走安装流程,到最后手机查看修改效果时,可能还需要几个页面的切换才能进入之前修改的页面,这些流程整个下来,耗费的时间是相当可观的,另外不支持windows,以及较强的入侵性(整个工程需要做较大的调整才能使用)均是接入BUCK的门槛,但不得不承认,若作为全量构建的角度,BUCK的确是不二的选择,背后还有强大的Facebook技术团队在维护,在Facebook内部,所有的app构建工具均为BUCK,在国内,BUCK也被微信应用为默认构建方案。

instant-run

最后是谷歌官方的增量解决方案Android Studio2.0 instant-run ,首先其基本流程与LayoutCast有点相似,但因其代码增量是通过运行期hack method实现,所以进行了instant-run后,实际App没有重新走原有该走的生命周期,导致要看到类似onCreate,onResume等生命周期方法修改后的效果,必须手动重启一次进程,另外因为不同手机指令集合的不同,instant-run还会有一定挂掉的机会,最后,因为instant-run采用hack的方式,导致debug包调试时候无法看到对应的method堆栈,不得不说,这是个巨大的弊端,最后,与LayoutCast一样,instant-run不支持5.0以下的机器。

核心思想

正因为上面几个方案各自有各自的优缺点,Freeline融合各自优点而生,核心技术思想源自于Buck,LayoutCast,并在此基础上进行一步改良,争取把增量思想做到极致。

主要有如下几点:
多任务并发,多级缓存,增量范围最小化,懒加载,基于长链接无安装式运行期动态替换,基线对齐触发机制,可调试


多任务并发

研究过Buck的同学应该清楚,Buck把原来单流水线任务以工程为单位拆分成多个可并发执行的子任务节点,梳理好各个节点前后的依赖关系,整理出有向拓扑图,通过多条线程并发把各个子任务节点构建出来,充分利用多核优势,在macbook上默认16条线程并发。
Freeline在启动时候仿照了Buck,根据工程间及任务间的依赖关系,提前计算好有向拓扑图,进行并发任务执行,默认开启8条线程(因聚宝工程数较少,没有必要开启过多线程),下面先简单介绍一下相关知识:

有向拓扑图

拓扑图是图的一种,“有向”保证了依赖关系和顺序关系,可以有多个根,
子可以有多个父


                                         d6bee667b31fc1efbe4c4f01fe526ebc6c2fede2

下面先以一张图简要说明Freeline构建期间各个工程任务工作次序:

d02e8d4d60bccb11cd4d0897d1464ec995cdf98a

整个工程角度来看,主要分成:
PC端与手机建立TCP长连接,扫描各个子工程文件变化,各个子工程的增量dex构建,增量资源包构建,合并所有工程dex,传输增量包

上图中,分叉的箭头代表任务是并发的,同一时间,不同的工程可能处于不同的构建阶段,Freeline在启动时候,会先定义好各个子工程及其子任务前后的依赖关系,每个任务的前置任务,后置任务,位于同一层级的工程会进行并发构建,默认8线程并发。

单个工程流程

上面从总体上介绍了Freeline工作的整体情况,接下来详细介绍Freeline每个子工程做的事情:先以一张图说明Freeline构建期间单个工程为单位的任务流程:


91c364a47a39bf23eb9aeb2af013ffe1bb1576c9

以app工程为例子,这里app依赖common ,构建过程有如下步骤:

1.scan扫描app工程内文件变化

2.根据扫描结果,若同时有资源及代码变化则并发运行inc-code-task 及inc-res-task,
以”inc“开头代表该任务是增量任务


inc-code-task介绍
从上到下分别为:
check-r-change
(校验R文件MD5是否发生变化),若发生变化则把新的R.java加入变更列表

begin-code-transaction:
该过程会把代码增量所必需的工作空间进行备份,若下面其中一个过程发生错误,则会把整个过程中构建的产物进行事务回滚

javac:
把扫描出来的java变更集合,进行编译,若存在dependency (上面例子为common工程)也在构建,则挂起,等待前置任务javac构建完毕后再往下执行

buck-dx:
这里实际上是把上面编译后的class文件变成dex文件,这里用“buck-”描述是因为该dx工具是从Buck中提取出来,经实测比Android原生的dx工具快40%左右

buck-smart-dex:
同上,该工具在buck工具中提取而得,目的是使上一步打出来的dex体积进一步减小,最后生成的dex则为该工程该次增量dex构建的最终结果

inc-res-task介绍

begin-res-transaction:
该过程会把资源增量所必需的工作空间进行备份,若下面其中一个过程发生错误,则会把整个过程中构建的产物进行事务回滚

merge xml:
若更改的文件在其他子工程也存在,以mPaas架构为例,存在api,biz,build,或tools,这些工程可能会存在同名的xml文件,这种情况需要对这些xml文件内对应的节点进行合并

merge ids:
若上面gen-r 阶段发现R的md5发生过变更,或更改的文件集合里面有ids.xml或public.xml,则把目标目录里面的ids.xml及public.xml与新变更的ids.xml 与public.xml进行xml节点合并

gen id files:
该过程是实现资源增量的关键,该过程会通过最后一次构建的资源包,反向生成
ids.xml及public.xml,该两个文件在构建增量资源包时候参与编译,可以使得
最后构建出来的资源包的内对于的资源ID与前一次构建的资源包保持一致,该过程原理后面篇幅会详细介绍

build-inc-res:
该过程会把上面scan过程扫描出来的资源变更集合参与作为参数传入我们自己改写过的increment版本的Aapt,该工具主要完成几个事情:

1.构建增量包,生成最终的资源包时候,仅仅包含编译后的变更资源集及“resources.arsc” 与 “AndroidManifest.xml”

2.兼容mPaas架构Base Package id 问题

3.根据ids.xml及public.xml生成保持id值与前一次构建结果里面的id值相同,若该任务有前置的资源任务(上面例子为common),则等待其前置增量资源任务先构建完毕,最后构建出来的包以“.pack”结尾

多级缓存

在代码变更方面,Freeline在各个工程的Class,dex层面加入了缓存,已经编译过的java文件,直接从增量工作空间里面的的Class pool获取,已经dx过的Class文件,会直接从dex pool中获取,最后实现的效果是,每次增量构建都是一个全新的流程,此前的修改不会参与到本次增量编译过程,不存在LayoutCast方案随着修改文件的增多越来越慢的问题。

                            6c82c6899af6bd7674ee7acb4d2269c4938c55cc

在资源变更方面,Freeline会在每次增量包构建后,把增量修改的资源文件与手机端对应的文件进行一次sync同步,每次资源增量构建范围仅仅是本次修改的集合,此前的修改均在此前的sync同步过程中同步至手机端。与代码变更一样,不需要构建此前修改的增量集合。

                   15c4627532582efc754a1e3b280fbd9ea4ae9945

加入多级缓存及多任务并发策略后数据对比


                                       51f331b63ec181ed1ec6de6017e14bc3cc5159b3

增量范围最小化

Freeline会尽可能把增量的范围缩小到单次修改对应的必须要更改的文件集合,不定期与手机端进行同步,以减少随着修改范围增大带来的性能损耗。
代码层面,运用了上面提到的多级缓存,每次仅仅编译本次修改的文件,此前修改过的文件不在本次编译范围。
资源层面,我们为了尽可能降低增量包的体积及构建成本,在aapt的基础上,拓展了一个叫IncrementAapt的工具,并把其编译成linux,mac,windows三个不同平台以做平台兼容,该工具会根据修改的资源文件,及最后一次资源构建结果,构建出对应的增量包,该增量包仅仅含变更的资源集合,且进行过7-zip压缩,大小视更改修改量而定,一般情况只有数百kb。极大程度降低打资源包及最后tcp传输的耗时。

懒加载

Freeline 把任务尽可能延后到真正需要的时候进行,例如对R文件的javac编译,若仅仅修改资源文件,即便是新增了资源文件,如:加了新的id,新的图片,layout等,触发了新的R文件与旧的R文件的id集合不一致,但此如果没有修改过java文件,则不会触发对R文件的编译,也就是如果只修改资源,没有更改过java代码的话,不管实际上应用的id集合是否已经变更,Freeline会以极小的代价构建出增量的资源包,推送至手机,直接在当前的Actvity刷新,不需要重启进程。对于新的R文件的编译,会延后到该工程有java文件更改才执行,这样也保证代码里面真正需要R文件新增的id值的时候,能找到对应的值,在没有代码更改前,进程无需重启,加快刷新效率。

可调试

Android studio instant-run 因采用的是Hack method 的方案,存在被修改的方法无法调试问题,LayoutCast构建的增量Class,在Debug调试下也存在参数值无法显示的问题,Freeline在该点上进行了处理,使得增量构建的类文件与全量构建一致,不影响日常调试。

基于长连接无安装式动态替换

无安装式动态替换与LayoutCast及Android Stduio2.0 instant-run一致,也是该两种增量构建方案的最大的优点,整个构建过程不需要重新安装app,动态替换代码及资源,省去了安装app及重启进程进入对应界面的过程。整个交互流程图见下:


                   0696345275c63612380f573baa8c2fcbc684cf8e


1.phone端会架设一个tcp socket作为服务器。

2.pc端会与手机端进行socket连接。

3.pc端与phone端会通过自定义协议进行交互,pc端会询问phone状态,比如获取手机端基线包版本,sdk版本号,当前手机是否支持资源增量,当前Activity名字等等,后续传输增量包,手机端向pc端返回增量构建结果等,整个通讯过程,均会沿用同一条长连接进行。

4.在同步完增量包后,phone端会根据当前变化是代码变化还是仅仅res变化来决定下一步操作,若仅仅res变化,则直接restart 整个Activity栈里面的Activity,若存在代码变更,则直接重启当前进程,由于Android系统Activity栈的管理,进程被杀若Activity栈还存在Activity,则在该app重启时候,会沿用原来的栈顺序重新创建这些Activity。最终的结果,重启后,界面就会出现最后显示的Activity,(这里有特殊情况,如果该Activity的launchmode设置的是singleTask,或singeInstance,则重启后除了最后的这个Activity,堆栈内的其他Activity均会被清空,这涉及到Android对Activity的管理机制问题,这里不细说,有兴趣的同学可以到自行google。)而按返回键后UI也会顺着原来栈里的Activity顺序显示。


基线对齐触发机制

Freeline会在下面情况重新构建基线包:

1.在git pull 或 一次性修改大量的文件情况下,会导致增量包体积大增,影响后期传输及手机重启后对增量包进行dexopt的速度,考虑到这种情况毕竟是少数,没必要为一次的变更影响后期的增量构建速度。

2.无法依赖增量实现的修改:修改AndroidManifest.xml,更改第三方jar引用,
依赖编译期切面,注解或其他代码预处理插件实现的功能等。

3.更换调试手机或同一调试手机安装了与开发环境不一致的安装包。

由于在重建基线包前,可能已经进行了若干次的增量构建,故在重建基线包时候,要把这些增量构建对应的module进行全量构建,以使得最新的基线包包含了所有过去的修改。整个流程如下图:(A,B, C 分别为3个不同子工程)

9e257f6c857ae448b4487261b7fcaba7fd77543f

head 作为指针,指向最新的基线包状态,base为对应初始的基线状态,在经过3次增量构建,手机端内app的状态会变成base + 3次增量的结果,上面例子里面,3次增量构建涉及A,B,C 3个Module,那么,在触发基线包对齐过程中,会对A ,B,C 按照原来的全量构建方式进行构建,与增量包构建一样,全量包的构建顺序会按照A,B,C前后的依赖关系按顺序进行,位于同层级的工程会进行并发构建,构建完毕后会重新安装至手机,在此之后,手机端内app以全量的方式包含A,B,C的修改,此前的3个增量包会在覆盖安装后第一次启动中被清除,此时基线指针head会从最初的base指向最新的base(with new A,B,C),至此,整个基线对齐就完成了,若中间发生异常,则在下次运行时候仍然会进行一次基线对齐过程,保证手机端安装上最新的全量包。

基线对齐的校验机制

上面的介绍的是基线对齐的整体思路,下面介绍一下校验部分的关键思路:

633f73544d24291b4e112714656310cd11b00a41

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值