Gradle插件
什么是 Gradle
Gradle 是一个基于 Apache Ant 和 Apache Maven 概念的项目自动化构建工具。Gradle 就是工程的管理,帮我们做了依赖、打包、部署、发布、各种渠道的差异管理等工作。
Gradle 脚本是基于 Groovy 语言来编译执行的,Java、Groovy、Kotlin 等都是基于 JVM 运行的,所以他们在语法上共性很多,熟悉 Java 的同学应该对 Groovy 上手很快
gradle插件
gradle插件修改代码是在最后一步
编写方法
在 Android 下的 gradle 插件共分为 两大类:
- 脚本插件:同普通的 gradle 脚本编写形式一样,可以直接写在build.gradle文件中,也可以自己新建一个 gradle 脚本文件中写
- 对象插件:通过插件全路径类名或 id 引用,它主要有 三种编写形式,如下所示:
1)在当前构建脚本下直接编写。
2)在 buildSrc 目录下编写。
加载自定义插件 group + module + version
3)在完全独立的项目中编写。
buildSrc
由于buildSrc目录是gradle默认的目录之一,该目录下的代码会在构建是自动编译打包,并被添加到buildScript中的classpath下,所以不需要任何额外的配置,就可以直接被其他模块的构建脚本所引用。
这就是:buildScript
在buildSrc/src/main目录下,再分别创建groovy、resources文件夹。
随便定义的需要自己写classpath:
优点:
- 项目构建时,Gradle 会自动编译项目目录下的 buildSrc 文件夹下的构建脚本和源码,并将其添加到项目构建脚本的 classpath 中,因此在使用 buildSrc 中创建的插件时,无需再手动指定 classpath(依赖的名字)(当然也可以自己创建id)
- buildSrc 文件夹中构建脚本和 Gradle 插件同一项目均可见,因此同一项目中的其他模块也可以使用 buildSrc 中创建的插件
- 不需要 uploadArchives task
缺点:
此处创建的插件对外部项目不可见,无法在其他项目中复用
id引入
引用的方式可以是通过类名引用,也可以通过给插件映射一个id,然后通过id引用。
通过类名引用插件的需要使用全限定名,也就是需要带上包名,或者可以先导入这个插件类,如下
1 |
|
或者
1 |
|
通过简单的id的方式,我们可以隐藏类名等细节,使的引用更加容易。映射的方式很简单,在buildSrc目录下创建resources/META-INF/gradle-plugins/xxx.properties,这里的xxx也就是所映射的id,这里我们假设取名CustomPlugin。具体结构可参考上文buildSrc目录结构。
基础概念
Plugin
作用
- 一般自定义Plugin就是入口
- 模块化构建脚本的功能
- 公共的功能可以抽取出来成为插件,可以供多个 build.gradle 使用,增加复用性。
和task的关系
如果有个你想要在好几个项目中重用的Gradle task集合,把这些task提取到一个自定义的plugin中是有意义的。这使得重用你自己的build逻辑和与他人共享该逻辑都是可能的。
Extension
为了能让 App 传入相关的版本信息和生成的版本信息文件路径,我们需要一个用于配置版本信息的 Extension,其实质就是一个实体类
与创建扩展属性一样,扩展Task也需要在project中创建注入。
1 |
|
自定义Task
右边就都是task
- 使用自定义扩展属性 Extension 仅仅是为了让使用插件者有配置插件的能力。而插件还得借助自定义 Task 来实现相应的功能
- 创建扩展属性一样,扩展Task也需要在project中创建注入
1 |
|
- task 的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容。。
- 一个Task表示一个逻辑上较为独立的执行过程,比如编译Java源代码,拷贝文件,打包Jar文件,甚至可以是执行一个系统命令或者调用Ant。另外,一个Task可以读取和设置Project的Property以完成特定的操作。
- 一个Task是由一序列Action组成的,当运行一个Task的时候,这个Task里的Action序列会按照顺序执行
构建生命周期
每次构建的本质其实就是执行一系列的Task,某些Task可能依赖其他Task,那些没有依赖的Task总会被最先执行,而且每个Task只会被执行一遍,每次构建的依赖关系是在构建的配置阶段确定的,在gradle构建中,构建的生命周期主要包括以下三个阶段:
初始化(Initialization)
构建工具会根据每个build.gradle文件创建出一个Project实例,初始化阶段会执行项目根目录下的Settings.gradle文件,来分析哪些项目参与构建。
include ‘:app’
配置(Configuration)
执行(Execution)
有关于命令行调用的信息,只需要使用 exec {} 闭包
exec 闭包中的几个参数提及下——
- workingDir:工作环境,参数为 File 格式。默认为当前 project 目录。
- commandLine:需要命令行执行的命令,参数为 List 格式。
1 |
|
task taskZ(dependsOn: [taskX, taskY]) {
// 依赖多个task,需要用数组[]表示
doLast { println ‘taskZ’ } }
taskZ依赖了taskX与taskY,所以在执行taskZ时,会先执行taskX、taskY。
Transformer
- Transform 可以被看作是Gradle 在编译项目时的一个 task,在 .class 文件转换成 .dex 的流程中会执行这些 task,对所有的 .class 文件(可包括第三方库的 .class)进行转换,转换的逻辑定义在 Transform 的 transform 方法中。实际上平时我们在 build.gradle 中常用的功能都是通过 Transform 实现的,比如混淆(proguard)、分包(multi-dex)、jar 包合并(jarMerge)
- 在 Booster 中,跟字节码相关的操作都是通过 Transformer 来完成,它是对字节码转换的简单抽象,以字节码的二进制做为输入,经过转换后,输出字节码二进制,它与具体使用哪种字节码操作框架无关,开发者可以自己选择跟字节码操作框架相关的特定实现, Booster 提供了两种实现:
基于 ASM 的实现:AsmTransformer
基于 Javassist 的实现:JavassistTransformer
1 |
|
写法
其实就是:把输入内容写入到作为输出内容
输出地址不是由你任意指定的。而是根据输入的内容、作用范围等由TransformOutputProvider生成,比如,你要获取输出路径:
1 |
|
Transform的inputs有两种类型,一种是目录,一种是jar包,要分开遍历
一旦注册了transform,就要处理输入和输出(默认实现是没有处理的),否则编译失败。
字节码操作框架
ASM vs Javassist /əˈsɪst/
https://www.jianshu.com/p/f2a4c7d3745d
Javassist简单,asm性能好
Transform API 起因
从 Android Gradle Plugin 1.5.0-beta1 开始,为了简化注入自定义 class 的操作,Android 提供了 Transform API,允许第三方插件在 class 文件被转换成 dex 之前对其进行修改,在此之前,如果要实现同样的操作,只能通过 Hook Task 的方式才能做到
参数说明
具体看代码
解释说明:Transform 主要作用是检索项目编译过程中的所有文件。通过这几个方法,我们可以对自定义 Transform 设置一些遍历规则,具体如下:
getName:
设置我们自定义的 Transform 对应的 Task 名称。Gradle 在编译的时候,会将这个名称显示在控制台上。比如:
Task :app:transformClassesWithXXXForDebug。
getInputType:
在项目中会有各种各样格式的文件,通过 getInputType 可以设置 LifeCycleTransform 接收的文件类型,此方法返回的类型是 Set<QualifiedContent.ContentType> 集合。
Gradle脚本的执行时序
- 首先解析settings.gradle来获取模块信息,这是初始化阶段;
- 然后配置每个模块,配置的时候并不会执行task;
- 配置完了以后,有一个重要的回调project.afterEvaluate,它表示所有的模块都已经配置完了,可以准备执行task了;
- 执行指定的task。
备注:如果注册了多个project.afterEvaluate回调,那么执行顺序等同于注册顺序。在上面的例子中,由于buildSrc中的回调注册较早,所以它也先执行。
gradle提供了对project状态配置监听的接口回调,以方便我们来配置一些Project的配置属性,监听主要分为两大类,一种是通过project进行 回调,一种是通过gradle进行回调,作用域也有不同 ,project是只针对当前project实现进行的监听,gradle监听是针对于所有的project而言的。接下来就其方式和具体的实现进行介绍说明。
//在 Project 进行配置前调用
void beforeEvaluate(Closure closure)
//在 Project 配置结束后调用,一般用这个
void afterEvaluate(Closure closure)
beforeEvaluate 必须在父模块的 build.gradle 对子模块进行配置才能生效,因为在当前模块的 build.gradle 中配置,它自己本身都没配置好,所以不会监听到。
什么是配置
1 |
|
Gradle用处
gradle插件修改第三方代码
1、我们知道在打包过程中,可以通过动态修改字节码,来进行插桩,实现埋点等业务,那么,在什么时机插入呢?;
2、随着项目越来越大,编译项目的时间会越来越长,我们需要统计各个任务的执行时间,来优化我们的打包编译速度,那么,如何统计呢?;
3、在我们的项目、第三方库和系统遇到一些bug的时候,我们有没有什么比较好的hook方法,对我们的代码做到无侵入?
4、多想想使用场景,例如打包完成发送钉钉机器人等
好文章
调试gradle
最后需要执行 gradle assembleDebug
https://www.jianshu.com/p/6bbe9352f75d 也可以
或者右侧:APP —— build —— assemble(可以停止)
下一次直接点bugbug就可以调试了
如何debug自定义AbstractProcessor 好用
- 开源库和自己写的插入代码注意不要混淆
- buildSrc中build.gradle的AGP版本要和app模块中一致
- 插入代码引用的类要使用全路径
- 插入代码中用到的类需要将类路径添加到classPool中,否则会编译不过
buildSrc不要在settings.gradle中配置 - 不管我们有没有修改jar的操作,也要拷贝到目标路径
常见问题
Could not find implementation class ‘xxx’ 的话
1
2
3implementation-class=com.lqr.gradle.study.GradleStudyPlugin
// 如果报错 Could not find implementation class 'xxx' 的话,一般是类全路径有问题,默认包不需要写包路径,修改如下即可:
// implementation-class=GradleStudyPlugin重新部署插件时,需要先在 app module 的 build.gradle 中将插件依赖注释,否则报错。
不生效时,可以先注释,编译,再打开试试