正文

经过一段时刻的工作(摸鱼划水),从几个很小的地方给大家介绍下我是如何提升编译构建速度的,可是本次共享内容仍是主要针对当前阿逼的工程架构,不一定对你们有协助哦。

FileWalker 剪枝 + 多线程优化

咱们工程内会在编译和同步阶段首先获取到整个工程的模型,之后计算出每个模块的version版别。之前咱们经过java.nio.file.FileVisitor来进行工程文件遍历的操作。相同文件展开的api也是能够进行剪枝的,可是因为是用groovy写的,我仍是不太喜爱。

本次优化咱们采用了kotlin的file相关的walkTopDown(能够快速的从上到下的遍历一个文件树)语法糖。然后经过其间的onEnterdsl进行剪枝,然后咱们能够经过filter进行第二波过滤,筛选出咱们实际要访问的目录。最后再进行一次foreach。

fun File.walkFileTree(action: (File) -> Unit) {
    walkTopDown().onEnter {
        # 关于不需求的目录进行剪枝
        val value = if (!(it.isDirectory && (it.name == "build" || it.name == "src"))) {
            !it.isHidden
        } else {
            false
        }
        value
    }.filter {
        val value = if (this == it) {
            false
        } else {
            if (it.isDirectory) {
                val build = File(it, "build.gradle")
                build.exists()
            } else {
                false
            }
        }
        value
    }.forEachIndexed { _, file ->
        action.invoke(file)
    }
}

优化作用如下,本来没有剪枝的版别,咱们本机进行一次FileWalker需求1分钟左右的时刻。使用剪枝的版别之后,咱们能够把时刻优化到2s左右的时刻。咱们越过了些什么?

格式 是否越过
隐藏文件夹
build
src
文件
其他文件夹

经过上述的剪枝,咱们能够减少十分十分多的文件遍历操作,在完结相同的才能的状况下,能够大大的加速文件的遍历操作。

别的,咱们会获取对应文件下的git commit sha值,然后作为该模块的version版别,而这个操作也是有几百毫秒的耗时,而咱们工程大约有800+这样的模块,所以假如按照同步的办法去履行,就会变得耗时了。

而优化方案就比较简单了,咱们经过线程池供给的invokeAll办法,并发履行完之后再继续向下履行就能够完结该优化了。

        Executors.newCachedThreadPool().invokeAll(callableList)

整体优化下来,本来在CI上一次FileWalker需求1mins,因为CI的机器足够牛逼,所以优化完只是只需求3.5s就能够完结整个工程的遍历以及获取对应git commit sha值的操作。

修正前:

一点点编译优化

修正后:

一点点编译优化

skip 一些非有必要task

在AGP的打包流程中,会插入很多预查看的使命,比如相似kotlin版别查看, compileSdk版别查看等等使命。而这些使命即时不履行,也并不会影响本次打包使命,仍是能够打出对应的apk产品的。当然条件是编译没有啥问题的状况下。

咱们仔细调查了下apk打包下的一切的task,并调查了下task使命耗时状况,找到了几个看起来并没有什么实际用途的使命。

一点点编译优化

接下来就是如何去封闭这个使命了,其实办法仍是比较简单的。咱们主要用字符串的形式去获取到这个Task,然后把enable设置成false就能够了。可是条件是这个Task的输出并不会影响到后续的Task就行了。别的这几个应该是高apg新增的task,7.x才出现的低版别的是没有的。

afterEvaluate {
    def metaDebugTask = tasks.findByName("checkApinkDebugAarMetadata")
    def debugCheck = System.getenv().containsKey("ENABLE_APINK_DEBUG_CHECK")
    if( metaDebugTask != null && !debugCheck) {
        metaDebugTask.setEnabled(false)
    }
    def preCheck = tasks.findByName("checkApinkDebugDuplicateClasses")
    if( preCheck != null && !debugCheck) {
        preCheck.setEnabled(false)
    }
}

一点点编译优化

这样咱们就能够在一个构建中优化掉大约1min30s的时刻了。这些手法相对来说都比较简单,别的咱们还能够考虑把一些不互相依靠的使命从线性履行变成并行履行。能够参考gradle的Worker相关api。

worker_api

二进制产品发布

原始的baseversion是根据文件内容的md5 生成的一个大局统一版别号,然后再结合仓库内的gitsha生成二进制缓存。可是因为大仓内的代码量越来越大,所以一旦改变baseversion,需求耗费大约80min左右的时刻重新生成一切的二进制缓存。

虽然可是,其实并没有这个必要悉数模块都进行一次发布。办法签名出现问题,咱们只需求让对应的模块重编即可。所以这种大局性的baseversion 就需求进行迭代了。需求让baseversion被拆解成多个,然后进行混合生成一个新值,底层改动能够影响到上层,而最上层模块也能够具备独立更新的才能。

# 后续该文件改动不会导致整个baseVersion改变
# 根据文件途径匹配的规矩,能够给每个文件途径设置一个version,可是因为工程之间存在依靠,所以能够兼并多个baseVersion到一起
# 测验成果如下,framework改变80min comm 改变 50min  app上改变10min
# 后续会配合a8查看,直接通知各位那些模块的办法签名查看不经过,之后直接修正部分version版别就好了
# 大局默认混入一切  假如非必要状况下 别改这个版别号 !!!!!!!
- name: global
  path: 
  version: 1
# app目录缓存
- name: app
  path: app
  version: 1
  mixin: [ framework,common ]
# framework根底,该层目录改变之后一切向上的悉数需求改变  非必要也最好不要改
- name: framework
  path: framework
  version: 2
  mixin:
# comm 模块,该层目录改变之后一切向上的悉数需求改变  非必要也最好不要改
- name: common
  path: common
  version: 2
  mixin: [ framework ]

经过定义出一个新的yaml文件,咱们能够定义出namepath代表相对途径,version表明版别号,mixin代表混合入别的name,而相对底层改动状况下会影响到上层模块重编。

这样,咱们就能够在app下进行独立的版别号添加,让app目录下的模块进行一次重编,然后解决一部分办法签名问题导致的上层库缓存刷新。

测验成果大约如下:

目录 耗时状况
global 80min
common 50min
app 10min

结尾

以下仅代表个人观点哦,我觉得编译优化仍是要从本工程实际状况出发,你的东西箱内的东西要足够多,要先知道哪些东西是慢的你才会有考虑的去进行一些优化,而不是很盲目的进行尝试。

别的优化不应该损坏整个工程的状况,咱们不应该魔改整个编译流程,最好就是经过最小的手法去进行一些微量的优化,小步慢跑的进行一些对应的优化。