谈一谈如何加快android的项目的编译速度

这篇具有很好参考价值的文章主要介绍了谈一谈如何加快android的项目的编译速度。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

随着android的组件化的到来,一个项目后期功能越来越多,模块拆分的越来越多,作为android的开发的小伙伴就不得不面对运行一下android项目可能需要5,6分钟甚至10几分钟的等待期,开发时间都浪费在编译上了,你说烦不烦呢!那么怎么解决这个困境,总不能就这么一直凑合着吧,反正也没太大影响开发,只是速度慢了一点而已。

那么怎么让编译加快呢,自从把eclipse改用android studio之后后,出现了一种新的引用格式aar,aar格式是编译好的资源和类,那么有没有一种可能将主模块依赖的的所有模块库转化为aar依赖,在某个模块库变化的时候将它转化为aar依赖,不变化的时候转化为库依赖,那么编译速度会不会大大提高,答案是肯定的了。有人为说了,把库都部署到maven不就行了,当然,maven远程库确实有这个功效,但是,每写一个新的模块库是不是都得把它部署到maven上,有没有一种可能就是写一个脚本插件上这么新的模块自动转化为Maven的库文件,这种方案就就非常ok了。

要实现这个方案,首先咱们要面对以下这些问题:

  • 需要通过 gradle plugin 的形式动态修改没有改动过的 module 依赖为 相对应的 aar 依赖,如果 module 改动,退化成 project 工程依赖,这样每次只有改动的 moduleapp 两个模块编译。
  • 需要把 implement/api moduleB,修改为implement/api aarB,并且需要知道插件中如何加入 aar 依赖和剔除原有依赖
  • 需要构建 local maven 存储未被修改的 module 对应的 aar(也可以通过 flatDir 代替速度更快)
  • 编译流程启动,需要找到哪一个 module 做了修改
  • 需要遍历每一个 module 的依赖关系进行置换, module 依赖怎么获取?一次性能获取到所有模块依赖,还是分模块各自回调?修改其中一个模块依赖关系会阻断后面模块依赖回调?
  • 每一个 module 换变成 aar 之后,自身依赖的 child 依赖 (网络依赖,aar),给到 parent module (如何找到所有 parent module) ? 还是直接给 app module ? 有没有 appmodule 依赖断掉的风险? 这里需要出一个技术方案。
  • 需要hook 编译流程,完成后置换 loacal maven 中被修改的 aar
1、如何手动添加 aar 依赖,分析implement 源码实现入口在 DynamicAddDependencyMethods 中的 tryInvokeMethod 方法。他是一个动态语言的 methodMissing 功能
 public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) {
       //省略部分代码 ...
       return DynamicInvokeResult.found(this.dependencyAdder.add(configuration, normalizedArgs.get(0), (Closure)null));
 }

private class DirectDependencyAdder implements DependencyAdder<Dependency> {
        private DirectDependencyAdder() {
        }
        public Dependency add(Configuration configuration, Object dependencyNotation, @Nullable Closure configureAction) {
            return DefaultDependencyHandler.this.doAdd(configuration, dependencyNotation, configureAction);
        }
    }


public Dependency add(String configurationName, Object dependencyNotation) {
        return this.add(configurationName, dependencyNotation, (Closure)null);
    }

    public Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure) {
       //这里直接调用到了 doAdd 
        return this.doAdd(this.configurationContainer.getByName(configurationName), dependencyNotation, configureClosure);
    }
    

doAdd 方法三个参数通过 debug 源码发现,configuration 就是 "implementation", "api", "compileOnly" 这三个字符串生成的对象,dependencyNotation 是一个 LinkHashMap 有两个键值对,分别是 name:aarName, ext:aar,最后一个configureActionnull 就可以了,调用 project.dependencies.add 最终会调到 doAdd 方法,也就是说直接调用 add 即可。

2、localMave 优先使用 flatDir 实现通过指定一个缓存目录 getLocalMavenCacheDir 把生成 aar/jar 包丢进去,依赖修改时候通过 上面的 4.1 添加对应的 aar 即可

fun flatDirs() {
        val map = mutableMapOf<String, File>()
        map.put("dirs", File(getLocalMavenCacheDir()))
        appProject.rootProject.allprojects {
            it.repositories.flatDir(map)
        }
    }

3、编译流程启动,需要找到哪一个 module做了修改

  • 使用遍历整个项目的文件的 lastModifyTime 去做实现
  • 已每一个 module 为一个粒度,递归遍历当前 module 的文件,把每个文件的 lastModifyTime 整合计算得出一个唯一标识 countTime
  • 通过 countTime 与上一次的作对比,相同说明没改动,不同则改动. 并需要同步计算后的 countTime 到本地缓存中

计算每个module文件改变的时间代码如下:

  project.rootProject.allprojects.onEach {
            if (it == project.rootProject || it.childProjects.isNotEmpty()) {
                return@onEach
            }
            var countTime = 0L
            it.projectDir.eachFileRecurse { file ->
                // 过滤掉build目录及该目录下的所有文件
                isCodeFile = !(file.isDirectory && Contants.BUILD == file.name)
                if (isCodeFile) {
                    countTime += file.lastModified()
                    count++
                }
                return@eachFileRecurse isCodeFile
            }
            newModuleList.add(ModuleChangeTime(it.path, countTime))
        }

原理就是将所有moudle下的代码和资源的最后修改时间累加,在编译的时候对比前后时间,如果一只就用aar依赖,如果不一致就用项目依赖。

4、module 依赖关系 project 替换成 aar 技术方案

  • 每一个 module 依赖关系替换的遍历顺序是无序的,所以技术方案需要支持无序的替换
  • 目前使用的方案是:如果当前模块 A 未改动,需要把 A 通过 localMaven 置换成 A.aar,并把 A.aar 以及 Achild 依赖,给到第一层的 parent module 即可

5、hook 编译流程,完成后置换 loacal maven 中被修改的 aar

  //如果当前模块是改动模块,需要打 aar
        if (mAllChangedProject?.contains(childProject.path) == true) {
            //打包aar
            val bundleTask = getBundleTask(childProject, buildType.capitalize())?.apply {
                task.configure {
                    it.finalizedBy(this)
                }
            }

            if (enableLocalMaven) {
                // publish local maven
                bundleTask?.let { bTask ->
                    LogUtil.d("bTask=$bTask")
                    val buildType = if (bTask.name.contains("release")) {
                        "Release"
                    } else {
                        "Debug"
                    }
                    try {
                        val publishMavenTask =
                            childProject.project.tasks.named("publishMaven${buildType}PublicationToLocalRepository").orNull
                        publishMavenTask?.let {
                            bTask.finalizedBy(it)
                        }
                    } catch (e: Throwable) {
                        e.printStackTrace()
                    }

                }
            } else {
                //copy aar
                val localMavenTask =
                    childProject.tasks.maybeCreate("uploadLocalMaven" + buildType.capitalize(),
                        FlatTask::class.java)
                localMavenTask.localMaven = this@AarFlatLocalMaven
                bundleTask?.finalizedBy(localMavenTask)
            }
        }

这些代码的意思就是获取将每个moudle转化为aar的任务,然后在这个任务结束后创建新的任务FlatTask将这些aar放到localMaven中。FlatTask源码如下文章来源地址https://www.toymoban.com/news/detail-661804.html

 open class FlatTask : DefaultTask() {
        @Internal
        var inputPath: String? = null
        @Internal
        var inputFile: File? = null
        @Internal
        var outputPath: String? = null
        @Internal
        var outputDir: File? = null
        @Internal
        lateinit var localMaven: AarFlatLocalMaven

        @TaskAction
        fun uploadLocalMaven() {
            val flatAarName = getFlatAarName(project)
            this.inputPath = FileUtil.findFirstLevelAarPath(project)
            this.outputPath = FileUtil.getLocalMavenCacheDir()
            inputFile = inputPath?.let { File(it) }
            outputDir = File(this.outputPath)

            inputFile?.let {
                File(outputDir, flatAarName + ".aar").let { file ->
                    if (file.exists()) {
                        file.delete()
                    }
                }
                it.copyTo(File(outputDir, flatAarName + ".aar"), true)
                localMaven.putIntoLocalMaven(flatAarName, flatAarName + ".aar")
            }
        }
    }


}

到了这里,关于谈一谈如何加快android的项目的编译速度的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 谈一谈redis脑裂

    (1)一主多从架构中,主节点与客户端通信正常,主节点与哨兵、从节点连接异常,客户端仍正常写入数据 (2)哨兵判定主节点下线,重新选主 (3)原主节点与哨兵和其他节点通信恢复,成为新主节点的从节点,drop本身所有的数据,从新主节点全量copy数据 (4)原主节点

    2024年02月12日
    浏览(36)
  • 谈一谈缓存穿透,击穿,雪崩

    缓存穿透是指在使用缓存系统时,频繁查询一个不存在于缓存中的数据,导致这个查询每次都要通过缓存层去查询数据源,无法从缓存中获得结果。这种情况下,大量的请求会直接穿透缓存层,直接访问数据源,从而增加了系统的负载,降低了系统的性能。 通常情况下,当一

    2024年02月14日
    浏览(41)
  • 谈一谈扫码登录原理

      今天给大家介绍下扫码登录功能是怎么设计的。 扫码登录功能主要分为三个阶段: 待扫描、已扫描待确认、已确认 。 整体流程图如图。 下面分阶段来看看设计原理。 1、待扫描阶段 首先是待扫描阶段,这个阶段是 PC 端跟服务端的交互过程。 每次用户打开PC端登陆请求,

    2024年02月10日
    浏览(35)
  • 谈一谈Python中的装饰器

    1.1 何为Python中的装饰器? Python中装饰器的定义以及用途: 装饰器是一种特殊的函数,它可以接受一个函数作为参数,并返回一个新的函数。装饰器可以用来修改或增强函数的行为,而不需要修改函数本身的代码。在Python中,装饰器通常用于实现AOP(面向切面编程),例如日

    2023年04月16日
    浏览(49)
  • 谈一谈冷门的C语言爬虫

    C语言可以用来编写爬虫程序,但是相对于其他编程语言,C语言的爬虫开发可能会更加复杂和繁琐。因为C语言本身并没有提供现成的爬虫框架和库,需要自己编写网络请求、HTML解析等功能。 不过,如果你对C语言比较熟悉,也可以尝试使用C语言编写爬虫程序,这样可以更好地

    2024年02月08日
    浏览(43)
  • 【大数据面试题】007 谈一谈 Flink 背压

    一步一个脚印,一天一道面试题 (有些难点的面试题不一定每天都能发,但每天都会写) 在流式处理框架中,如果下游的处理速度,比上游的输入数据小,就会导致程序处理慢,不稳定,甚至出现崩溃等问题。 上游数据突然增大 比如数据源突然数据量增大多倍,下游处理速

    2024年02月20日
    浏览(44)
  • 谈一谈Vue怎么用extend动态创建组件

    Vue.js是一个流行的JavaScript框架,它提供了许多功能来帮助我们构建交互式Web应用程序。其中之一是使用extend方法动态创建组件。   extend方法是Vue.js提供的一个方法,它允许我们创建一个新的Vue组件构造函数。这个新的构造函数可以继承现有的组件,也可以添加新的选项。 我

    2023年04月24日
    浏览(31)
  • 谈一谈SQLite、MySQL、PostgreSQL三大数据库

    每一份付出,必将有一份收货,就像这个小小的果实,时间到了,也就会开花结果… SQLite、MySQL 和 PostgreSQL 都是流行的关系型数据库管理系统(RDBMS),但它们在功能、适用场景和性能方面有一些不同。 SQLite : 轻量级 : SQLite 是一个嵌入式数据库,它不需要一个独立的数据库

    2024年02月05日
    浏览(53)
  • [轻科普]谈一谈最近手机上的2亿像素

    最近很多厂商发布了2亿像素的手机,2亿像素比较火热,如realme 11 pro + ,荣耀的honor 90 pro,以及之前小米发布的Redmi note 12 pro +。 下图为honor 90 Pro上搭载的2亿像素 ,为S5KHP3 下图为 红米上搭载的S5kHPX 2亿像素传感器。    下图为 Realme的两亿像素,S5KHP3的超级变焦版本   以上三

    2024年02月06日
    浏览(43)
  • 【谈一谈】: 我们工作中的单例模式有哪些写法?

    我们要实现一个单例,首先最重要的是什么? 当然是把构造函数私有化,变成 private 类型,(为啥? 单例单例,如果谁都能通过构造函数创建对象,还叫单例吗?是不~) 嗯~我们构造函数私有化后,我们应该 操作啥 呢? 接着我们需要提供 一个方法 ,这个方法要保证初始化 有且仅 初始化 一

    2024年02月21日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包