背景
随着app的持续迭代更新,新功能的持续集成等原因。项目中的library project越来越多。
特别是最近一两年来。流行起来了组件化,这更是直接增加了很多的本地library project的数量。导致每次需要打包apk运行时,都会需要对所有module都进行再次编译。严重影响打包速度。
加速插件简介
Speedup是一款专用于对多module环境下进行打包加速的gradle插件。主要依赖的基本原理有两个:
- 动态依赖替换
- 打包任务特性
插件地址
动态依赖替换
我们先来看看常规多module环境下的打包流程:
从上图可以看到。每次打包的时候。都会经过以下几个步骤:
- 对依赖的module进行进行打包编译,生成各自library的aar依赖。
- 对aar依赖进行合并。包括classes.dex的合并、资源合并等。
- 将合并后的文件、资源进行打包,生成apk。
而如果。我们使用maven依赖的方式进行依赖的话。编译流程就会优化成下图所示的方式:
可以看到。与上面直接使用module依赖的方式相比:使用Maven仓库依赖的方式,由于maven仓库中提供的直接就是已打包好的aar依赖,所以直接干掉了module -> aar打包编译的过程,起到了加速编译的作用!
所以,现在我们就可以得出第一个结论:使用maven依赖比使用module依赖更快速!
所以这个时候。很多人的常规做法就是。想办法将这些module依赖的library,编译打包发布到各自的远程中央仓库中去,这样就能避免每次运行的时候都去进行额外编译,从而节省时间了。
但是这种做法存在几个问题:
- 绝大部分library并不稳定。需要经常修改使用,若放到远程仓库,修改起来很麻烦
- 远程仓库的version版本号唯一,每次进行修改后都会需要更新升级版本号再发布。多人协作时容易造成混乱且也不容易维护
所以将module打包发布到远程仓库的做法,并不可取!
现在我们来考虑一下,如果将仓库设置在本地会怎样?也就是说使用本地maven仓库进行依赖管理。
我们来列举一下这里使用本地依赖的优点:
-
本地仓库中version不唯一:
即可以做到同版本覆盖发布,修改代码后直接重新发布即可,不需要提升version版本。这也正是远程仓库所做不到的。可避免频繁升级改动。
-
本地仓库不与网络打交道。
读取依赖更快速。事实上,远程仓库中所依赖的库,也是先存放到本地仓库中去再进行读取的。
可以看到,使用本地仓库,可以完美的解决频繁发包的问题!因为如果你的library不需要频繁修改的话,你肯定也早就已经把它单独拆出去放远程仓库中去了~
所以我们只要解决依赖不统一的问题。那么这个加速方案就算是彻底打通了!
打包任务特性
打包任务这里特指的assembleXXX任务。这个任务相信大家平时也见过无数次了。
此任务是由android插件所提供的,编译、合并、打包一条龙的总线任务。平时我们点击运行按钮、打包apk时。也是通过的调用此任务进行打包。打包后通过内置的adb命令将apk发送到手机并执行安装。
这个任务有个很重要的特性。这里我称之为按需加载特性
打个比方:假如我们使用的组件化开发模式,项目依赖链如下图所示:
上图所示的依赖链,对组件化不熟的可能会比较懵逼。对组件化有兴趣的可以去这里了解一下组件化的项目结构:聚美组件化实践之路
这里有三个可运行的壳工程。其各自壳工程的依赖链如下:
- [app shell 1] -> [component 1] -> [base library]
- [app shell 2] -> [component 2] -> [base library]
- [Main app] -> [component 1] & [component 2] -> [base library]
而按需加载特性是指:在进行打包任务过程中,打包任务只会触发当前自身依赖链中的module进行编译。而不会对别的非自身依赖链中的module进行编译。
所以,当你运行app shell1的时候。这个时候会参与打包编译的就是[app shell 1]、[component 1]、[base library]这三个module. 这就是按需加载特性
原理
基于以上分析,我建立了此Speedup插件。利用gradle脚本,方便的进行本地aar打包发布,并通过对打包任务流程进行监听,动态的将module依赖替换为本地仓库中的maven依赖。
流程图
这里我画了个简单的流程图。便于直观的理解此gradle插件的运行机制。
特性
- 原理简单:整个插件由三个文件组成,并有详细注释,很容易理解
- 配置简单:简单得出乎你意料!
- 不同机器配置独享,不冲突:
- 兼容性好:实现逻辑依赖最简单的api。最大程度降低gradle版本升级时sdk改动造成的影响。
使用方式
依赖配置
在项目根目录下添加依赖并添加应用插件
buildscript {
repositories {
// 添加jitpack仓库地址
maven { url "https://jitpack.io" }
}
dependencies {
// 添加插件依赖
classpath "com.github.yjfnypeu:Speedup:0.9"
}
}
// 应用插件
apply plugin: 'speedup'
复制代码
属性配置
Speedup支持配置三种属性,均配置于项目根目录下的local.properties文件中。
-
speedup.enable:
是否激活speedup插件。默认为false,即不激活。这样做的目的是为了使得在发版机器上进行打包apk时。自动忽略speedup插件。进行全量编译
-
localRepo:
本地maven仓库的地址。默认为项目根目录下的_repo文件夹。若你需要使用别的地址。在此配置个本地地址即可
-
excludeModules:
由于我们开发的业务线的不同。很多时候我们是需要对部分的module每次都进行即时编译的:比如说现在正在做的功能。是需要对某个lib进行频繁修改的。所以这个时候。就需要使用此属性。指定此lib不需要进行加速。那么每次运行时。都会对此lib进行编译。
把所有属性全放置于local.properties中,出于以下几点考虑:
- 每个人所需要使用的配置方案不一样,放于local文件中,可以起到配置独立的作用。
- 发版apk不使用加速方案。一般打包发版apk的均运行在独立的机器上.
配置示例
github上我提供了一个组件化demo地址, 此demo中有添加此插件配置使用。以下内容建议结合demo进行查看。
由于本地配置是放置于local.properties文件当中。所以默认依赖下来是没有开启激活此插件的。可以参考下方的配置:放入自身的local.properties中去。
speedup.enable=true
# 当需要排除多个module时,中间用英文的逗号隔开
excludeModules=:usercenter:componentusercenter
复制代码
当配置speedup.enable为true,并且同步完成之后。你就可以在gradle任务列表中发现一组speedup任务:
可以看到。speedup任务组中。含有大量的uploadXXX任务提供使用。这些任务都是在同步时根据各个library module自动生成的上传任务。主要目的是将本地的library module方便的进行打包、编译。并发布到本地的仓库中去。提供使用。
当你使用upload进行本地打包发布后,你就可以在项目根目录下的_repo文件夹下找到对应的打包出来的aar:
然后,你可以试试直接运行。并且观察右下角的Gradle Console控制台输出日志。注意看所有执行的任务列表。就会发现:执行的gradle任务明显减少了,并且被打包到本地中的module。并没有被再次进行编译。
这是因为已经在运行时。动态的将对应的module替换为使用本地仓库中的aar地址了。
以上即是此插件的整个用法示例。可以总结为以下几步:
- 添加依赖、配置
- 执行upload任务进行本地发布
- 直接运行。
upload任务列表说明
Speedup插件任务中,很重要的一点即是生成的upload任务列表了。此upload任务主要分为三类:
-
uploadAll
作用:用于对项目中所有的library(除了excludeModules所指定排除的module)进行打包发布。此任务默认将会对项目下所有的module执行clean操作。所以可以保证打包出来的aar是不受数据污染的
使用场景:在进行分支切换后、或者进行合并代码后。此时很多module的数据都有修改。可以使用此任务一次性全部重新打包上传。
-
uploadForClean
作用:用于触发项目下所有module的clean任务。将其build文件夹删除。避免打包时出现数据污染。
与AS自带的clean任务不同。自带的clean任务附带了很多额外的任务,所以每次clean会很慢。而uploadForClean。只会触发所有module自身的clean任务,对自身的build文件夹进行删除,并无其他额外操作。这样clean的速度会得到很大提升。
此任务一般来说外部不需要直接使用。主要是内部直接提供给uploadAll进行使用
-
uploadXXX
作用:用于对指定的module进行重新打包发布。比如uploadbaselib。此任务代表对baselib重新进行打包发布。