gradle版本对应关系_深入理解gradle框架之一:Plugin,Extension,buildSrc

d789ec0531abc93cc188dfb2f96bf486.png

写在前面

这篇文章首发在字节跳动技术团队公众号上,可能有的童鞋已经看过了,不过考虑到可能有的童鞋没关注那个公众号,所以这里再发一遍。

另外,最近创建了一个知识星球,名字跟专栏名字一样,也叫AndroidGeek, 主要是分享gradle框架知识,以及我在爱奇艺和字节跳动做插件化和热修复的实践经验,大家日常会遇到的编译问题,以及移动端开发的职业规划,欢迎感兴趣的童鞋加入。日常开发遇到的任何问题,以及职业规划等,都可以向我提问。

c948195eead6a15684af6b5f24ed2825.png

缘起

从2018年下半年开始,因为工作需要,开始深入了解android gradle plugin和gradle框架,在看完android gradle plugin 3.1.x和3.2.x版本的源码之后,发现目前开源的几乎所有插件化框架,因为没有理解android gradle plugin的原理,打包代码的实现都非常混乱,导致的结果就是很难随着android gradle plugin的升级而快速升级,所以目前几乎所有开源的插件化项目都因不适应新的gradle版本问题而不可用。

另一方面,随着项目的迭代,引入越来越多的gradle plugin, 其中对于Transform的滥用是导致项目编译速度越来越慢的最重要的一个原因了,然而,实际上,对于Transform的使用是有很大的优化空间的。

加上目前不管是中文还是英文,几乎所有这方面的文章都停留在基础使用的阶段,真正深入分析原理的几乎没有。

所以一直在酝酿写一个gradle系列的文章,一方面让大家了解android gradle plugin的原理(尽管各个大版本之间有差别,然而大版本内基本是一脉相承), 另一方面是在介绍原理的过程中,也会加入一些我觉得是Best Practice的Demo. 或许通过这个系列,能够引导大家都去改进自己实现的Plugin, 最终能够更快更好地实现自己的编译时功能。

p.s:因为基础使用的文章已经有太多了,对于这一块我基本上就是一笔带过,不会花太多笔墨。

系列说明

在讲解整个系列之前,先看一下gradle的架构是怎样的,如下图所示:

8441d4f251ca51aea75a7f5d3af9ad1e.png
  • 在最下层的是底层gradle框架,它主要提供一些基础服务,如task的依赖,有向无环图的构建等
  • 上面的则是google编译工具团队的android gradle plugin框架,它主要是在gradle框架的基础上,创建了很多与android项目打包有关的task及artifacts
  • 最上面的则是开发者自定义的Plugin, 一般是在android gradle plugin提供的task的基础上,插入一些自定义的task, 或者是增加Transform进行编译时代码注入

为了简单起见,我将底层的gradle框架和android gradle plugin框架统称为gradle框架,整个系列文章其实分析的就是底层gradle框架和android gradle plugin框架的原理,其中侧重点在andorid gradle plugin框架,因为这与我们日常编译息息相关,也是收益最大的部分。

这是深入理解Gradle框架系列的第一篇。整个系列共分为9篇,文章列表如下:

  • 第1篇是入门文章,主要讲解Gradle Plugin以及Extension的多种用法,以及buildSrc及gradle插件调试的方法。
  • 第2篇是从dependencies出发,阐述DependencyHandler的原理
  • 第3篇则是关于gradle configuration的预备知识介绍,以及artifacts的发布流程
  • 第4篇则是artifacts的获取流程
  • 第5篇是从TaskManager出发,分析如ApplicationTaskManager, LibraryTaskManager中各主要的Task,最后给出当前版本的编译流程图
  • 第6篇比较3.2.1相比3.1.2中架构的变化
  • 第7篇关于Gradle Transform
  • 第8篇从打包的角度讲解app bundles的原理
  • 第9篇分析资源编译的流程,特别是aapt2的编译流程

Plugin

语言选择

其实只要是JVM语言,都可以用来写插件, 比如Android Gradle Plugin团队,在3.2.0之前一直是用java编写gradle插件。

国内很多开源项目都是用groovy编写的,groovy的优势是书写方便,而且其闭包写法非常灵活,然而groovy的缺点也非常明显,最大的一点不好就是IDE对其的支持非常不好,不仅仅是语法高亮没做好,还有导航跳转都极为有限,比如build.gradle中的方法跳转不到其定义处。

当然,我自己长期使用groovy下来,也发现了它的一些缺点,比如each这个闭包,在运行时竟然会出现找不到其成员的情况。

以及出现开发者自定义的成员与其默认成员(groovy中会为每个类增加一些默认成员)名称重合时,不能给出有效的提示,当然,这个问题我不确定是IDE的问题还是groovy自身的编译器实现不够完善的问题。

其实到目前为止,使用kotlin进行插件开发是最好的选择,有如下两个原因:

  • kotlin的语法糖完全不输于groovy, 可以有效提高开发效率
  • kotlin是jetbrain自家的,IDE对其的支持更完善

可能正是这个原因,Google编译工具组从3.2.0开始,新增的插件全部都是用kotlin编写的。

插件名与Plugin的关系

比如我们常用的apply plugin: 'com.android.application', 其实是对应的AppPlugin, 其声明在源码的META-INF中,如下图所示:

391eb5cb68e19e11053299b9d4628d41.png

可以看到,不仅仅有com.android.appliation, 还有我们经常用到的com.android.library,以及com.android.feature, com.android.dynamic-feature.

以com.android.application.properties为例,其内容如下:

implementation

其含义很清楚了,就表示com.android.application对应的插件实现类是com.android.build.gradle.AppPlugin这个类。

其他的类似,就不一一列举了。

定义插件的方法

要定义一个gradle plugin,则要实现Plugin接口,该接口如下:

public 

以我们经常用的AppPlugin和LibraryPlugin, 其继承关系如下:

72f60a69b1870b1d909aa53f68240cee.png

注意,这是3.2.0之前的继承关系,在3.2.0之后,略微有些调整。

可以看到,LibraryPlugin和AppPlugin都继承自BasePlugin, 而BasePlugin实现了Plugin接口,如下:

public 

这里继承的层级多一层的原因是,有很多共同的逻辑可以抽出来放到BasePlugin中,然而大多数时候,我们可能没有这么复杂的关系,所以直接实现Plugin这个接口即可。

Extension

Extension其实可以理解成java中的java bean, 它的作用也是类似的,即获取输入数据,然后在插件中使用。

最简单的Extension为例, 比如我定义一个名为Student的Extension,其定义如下:

class 

然后在Plugin的apply()方法中,添加这个Extension, 不然编译时会出现找不到的情形:

project

这样,我们就可以在build.gradle中使用名为student的Extension了,如下:

student

注意,这个名称要与创建Extension时的名称一致。

而获取它的方式也很简单:

Student 

嵌套的Extension类似,不再赘述。

如果Extension中要包含固定数量的配置项,那很简单, 类似下面这样就可以:

class 

其配置如下:

fruit

下面要说的是包含不定数量的配置项的Extension, 就需要用到NamedDomainObjectContainer, 比如我们常用的编译配置中的productFlavors,就是一个典型的包含不定数量的配置项的Extension. 但是,如果我们不进行特殊处理,而是直接使用NamedDomainObjectContainer的话,就会发现这个配置项都要用=赋值,类似下面这样。

接着使用Student, 如果我需要在某个配置项中添加不定项个Student输入,其添加方式如下:

NamedDomainObjectContainer

然而,此时其配置只能如下:

team

注意,这里不需要name了,因为John和Daisy就是name了。

可是,这不科学呀,groovy的语法不是可以省略么?就比如productFlavors这样:

3da419dd5581dda13c9de8fc32c3b89e.png

要达到这样的效果其实并不难,只要做好以下两点:

  • item Extension的定义中必须有name这个属性,因为在Factory中会在创建时为这个名称的属性赋值。定义如下:

```groovy class Cat{ String name

String from
  float weight

} ```

  • 需要定义一个实现了NamedDomainObjectFactory接口的类,这个类的构造方法中必须有instantiator这个参数,如下:

```groovy class CatExtFactory implements NamedDomainObjectFactory{ private Instantiator instantiator

CatExtFactory(Instantiator instantiator){
      this.instantiator=instantiator
  }

  @Override
  Cat create(String name){
      return instantiator.newInstance(Cat.class, name)
  }

} ```

此时,gradle配置文件中就可以类似这样写了:

animal

Plugin Transform

Transform是android gradle plugin团队提供给开发者使用的一个抽象类,它的作用是提供接口让开发者可以在源文件编译成为class文件之后,dex之前进行字节码层面的修改。

借助javaassist, ASM这样的字节码处理工具,可在自定义的Transform中进行代码的插入,修改,替换,甚至是新建类与方法。

像美团点评的Robust,以及我开源的Andromeda项目中,都有在Transform中插入代码的示例。

如下是一个自定义Transform实现:

public 

gradle插件的发布

绝大多数gradle插件,我们可能都是只要在公司内部使用,那么只要使用公司内部的maven仓库即可,即配置并运用maven插件,然后执行其upload task即可。这个很简单,不再赘述。

特殊的buildSrc

前面说过gradle插件的发布,那如果我们在插件的代码编写阶段,总不能修改一点点代码,就发布一个版本,然后重新运用吧?

有人可能会说,那就不发布到maven仓库,而是发布到本地仓库呗,然而这样至多发布时节省一点点时间,仍然太麻烦。

幸好有buildSrc!

在buildSrc中定义的插件,可以直接在其他module中运用,而且是类似这种运用方式:

apply 

即直接apply具体的类,而不是其发布名称,这样的话,不管做什么修改,都能马上体现,而不需要等到重新发布版本。

gradle插件的调试

以调试:app:assembleRelease这个task为例,其实很简单,分如下两步即可:

  • 新建remote target
  • 在命令行输入./gradlew --no-daemon -Dorg.gradle.debug=true :app:assembleRelease
  • 之后选择刚刚创建的remote target, 然后点击调试按钮即可
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值