在上一篇文章《神策 Android 全埋点插件介绍》中,咱们了解到神策 Android 插件实际上是自定义的 Gradle 插件。Gradle 是一个专一于灵活性和性能的开源自动化构建工具,而插件的做用在于打包模块化的、可重用的构建逻辑。能够经过插件实现特定的逻辑,并打包起来分享给别人使用。例如:神策 Android 全埋点插件正是经过插件在编译时对特定函数进行处理,从而实现控件点击和 Fragment 页面浏览全埋点的采集。java
本文咱们会先针对 Gradle 的基础知识做必定的介绍,再举例说明如何实现一个自定义的 Gradle 插件。这里须要注意的是:文中采用 ./gradlew 去执行 Gradle 的命令,若是是 Windows 用户的话须要改为 gradlew.bat。android
Gradle 有两个重要的概念:Project 和 Task,本节将会介绍它们各自的做用以及之间的关系。api
Project 是与 Gradle 交互中最重要的 API,咱们能够经过 Android Studio 的项目结构来理解 Project 的含义,如图 2-1 所示:bash
图 2-1 Android Studio 项目结构图闭包
图 2-1 是写做过程当中使用到的一个项目(名为 BlogDemo),包含 app 和 plugin 这两个 Module。这里不论是 “项目” 仍是 “Module” 在构建时都会被 Gradle 抽象成 Project 对象。它们的主要关系是:app
一、Android Studio 结构中的项目至关于一个父 Project,而一个项目中全部的 Module 都是该父 Project 的子 Project;maven
二、每一个 Project 都会对应一个 build.gradle 配置文件,所以使用 Android Studio 建立一个项目的时候在根目录下有一个 build.gradle 文件,在每一个 Module 的目录下又各有一个 build.gradle 文件;ide
三、Gradle 是经过 settings.gradle 文件去进行多项目构建,从图 2-1 中也能够看出项目之间的关系。模块化
父 Project 对象能够获取到全部的子 Project 对象,这样就能够在父 Project 对应的 build.gradle 文件中作一些统一的配置,例如:管理依赖的 Maven 中心库:函数
...allprojects { repositories { google() jcenter() }}...复制代码
Project 在构建过程当中会执行一系列的 Task。Task 的中文翻译是 “任务”,它的做用其实也就是抽象了一系列有意义的任务,用 Gradle 官方的话说就是:Each task perform some basic work。例如:当你点击 Android Studio 的 Run 按钮的时候,Android Studio 会把项目编译、运行,实际上这个过程就是执行了一系列的 Task 来完成的。可能包含:编译 Java 源码的 Task、编译 Android 资源的 Task、编译 JNI 的 Task、混淆的 Task、生成 Apk 文件的 Task、运行 App 的 Task 等。也能够在 Android Studio 的 Build Output 看到真正运行的是哪些 Task,如图 2-2 所示:
图 2-2 Android Studio Build 输出日志
从图中右侧咱们能够看到,Task 由两个部分组成:Task 所在的 Module 名和 Task 的名称。在运行 Task 的时候,也须要按照这样的方式去指定一个 Task。
另外,能够自定义实现本身的 Task,咱们来建立一个最简单的 Task:
// add to build.gradletask hello { println 'Hello World!'}复制代码
这段代码的含义是建立了一个名为 “hello” 的 Task,想要单独执行该 Task 的话,能够在 Android Studio 的 Terminal 中输入 “./gradlew hello”,执行后就能够看到控制台输出 “Hello World!”。
Plugin 和 Task 从它们的做用来看其实区别不大,都是把一些业务逻辑封装起来,Plugin 适用的场景是打包须要复用的编译逻辑(即把一部分编译逻辑模块化出来)。能够自定义 Gradle 插件,实现必要的逻辑后把它发布到远程仓库或者打成本地 JAR 包分享出去。这样,后续想要再次使用它或者想分享给别人使用的时候,就能够直接引用远程仓库的包或者引用本地的 JAR 包。
最多见的 Plugin 应该就是 Android 官方提供的 Android Gradle Plugin。能够在项目主 Module 的 build.gradle 文件第一行看到:“apply plugin: 'com.android.application'”,这便是 Android Gradle Plugin。“com.android.application” 指的是 plugin id,该插件的做用是帮助你生成一个可运行的 APK 文件。
插件还能够读取写在 build.gradle 文件中的配置。主 Module 的 build.gradle 文件中会有一个名为 “android” 的块,块中定义了一些属性,例如:App 支持的最低系统版本、App 的版本号等。你能够把这里的 “android”android 块类比成数据类或者基类,定义的属性类比成类的成员变量。Android Gradle Plugin 在运行时能够拿到 “android” 块实例化的对象,进而根据对象的属性值运行不一样的编译逻辑。
Gradle 插件有三种实现方式,分别为 Build script、buildSrc project 和 Standalone project:
一、Build script 会把逻辑直接写在 build.gradle 文件中,Plugin 只对当前 build.gradle 文件可见;
二、buildSrc project 是将逻辑写在 rootProjectDir/buildSrc/src/main/java(最后一个路径文件夹也能够是 groovy 或 kotlin,主要取决于你用什么语言去实现自定义插件) 目录下,Plugin 只对当前项目生效;
三、Standalone project 是将逻辑写在独立项目里,能够直接编译后把 JAR 包发布到远程仓库或者本地。
基于本文的写做目的,这里咱们主要讲解 Standalone project,即独立项目的 Gradle 插件。
一个独立项目的 Gradle 插件大体结构如图 3-1 所示:
图 3-1 Gradle 插件项目目录示意图
在 main 文件夹下分为 groovy 文件夹与 resources 文件夹:
其中,resources 文件夹下是固定格式的 META-INF/gradle-plugins/XXXX.properties,XXXX 就表明之后使用插件时须要指定的 plugin id。
目前 Android Studio 对于 Gradle 插件开发的支持不够好,不少 IDE 本能够完成的工做都须要咱们手动完成,例如:
一、Android Studio 不可以直接新建 Gradle 插件的 Module,只能先新建一个 Java Library 类型的 Module,再把多余的文件夹删除;
二、新建类默认是新建 Java 的类,新建的文件名后缀是 “.java”,想要新建 Groovy 语法的类须要手动新建一个后缀为 “.groovy” 的文件,而后添加上 package、class 声明;
三、resources 整个都须要手动建立,文件夹名须要注意拼写;
四、删除掉 Module 的 build.gradle 所有内容,新加上 Gradle 插件开发须要的 Gradle 插件、依赖等。
在写插件的代码以前,咱们须要对 build.gradle 作些修改,以下所示:
apply plugin:
'groovy'
apply plugin:
'maven'
dependencies {
implementation gradleApi()
implementation localGroovy()
}
uploadArchives{
repositories.mavenDeployer {
//本地仓库路径,以放到项目根目录下的 repo 的文件夹为例
repository(url: uri(
'../repo'
))
//groupId ,自行定义
pom.groupId =
'com.sensorsdata.myplugin'
//artifactId
pom.artifactId =
'MyPlugin'
//插件版本号
pom.version =
'1.0.0'
}
}
这里主要分为三部份内容:
一、apply 插件:应用 'groovy' 插件是由于咱们的项目是使用 Groovy 语言开发的,'maven' 插件在后面发布插件时会用到;
二、dependencies:声明依赖;
三、uploadArchive:这里是一些 maven 相关的配置,包括发布仓库的位置、groupId、artifactId、版本号,这里为了调试方便把位置选在项目根目录下的 repo 文件夹。
作好以上准备以后,就能够开始源码的编写。Gradle 插件要求入口类须要实现 org.gradle.api.Plugin 接口,而后在实现方法 apply 中实现本身的逻辑:
package com.sensorsdata.pluginclass MyPlugin implements Plugin<Project>{ @Override void apply(Project project) { println 'Hello,World!' }}复制代码
在这里的示例中,apply 方法就是咱们整个 Gradle 插件的入口方法,做用相似于各类语言的 main 方法。apply 方法的入参类型 Project 在第二节中已经进行了解释,这里再也不赘述。因为 Plugin 类和 Project 类有很是多的同名类,在导入的时候必定注意选择 org.gradle.api 包下的类。
最后,还须要作一项准备工做:Gradle 插件并不会自动寻找入口类,而是要求开发者把入口类的类名写在 resources/META-INF/gradle-plugins/XXXX.properties 里,内容格式为 “implementation-class=入口类的全限定名”,此处示例项目的配置以下所示:
// com.sensorsdata.plugin.propertiesimplementation-class=com.sensorsdata.plugin.MyPlugin复制代码
完成编写插件的全部内容后,在终端执行
./gradlew uploadArchive复制代码
就能够发布插件。在上一小节编写插件的 build.gradle 文件中提早配置好了发布到 maven 仓库相关的配置,所以咱们这里执行该命令后,在项目根目录下就会出现 repo 文件夹,文件夹中包含打包后的 JAR 文件。
使用插件主要分别两个步骤:
(1)声明插件
声明插件须要在 Project 级别的 build.gradle 文件中完成,在 build.gradle 文件中有一个块叫作 buildscript,buildscript 块又分为 repositories 块和 dependencies 块。repositories 块用来声明须要引用的依赖所在的远程仓库地址,dependencies 块用来声明具体引用的依赖。这里使用刚刚发布到本地 repo 文件夹 JAR 包为例,参考代码以下:
buildscript { repositories { maven{ // 刚刚咱们把插件发布到了根目录下面的 repo 文件夹 url 'repo' } } dependencies { // classpath '$group_id:$artifactId:$version' classpath 'com.sensorsdata.myplugin:MyPlugin:1.0.0' }}复制代码
(2)应用插件
应用插件须要在 Module 级别的 build.gradle 文件中完成:
// apply plugin: 'plugin id'apply plugin: 'com.sensorsdata.plugin'复制代码
完成上述步骤以后,在每次编译的时候均可以在编译日志中看到插件输出的 “Hello,World!”。
若是但愿插件的功能更加灵活的话,通常会预留一些可配置的参数,就像能够在主 Module 的 “android” 块配置编译的 Android SDK 版本、Build-Tools 版本等。“android” 块的这个配置就是 Gradle 的 Extension,下面咱们来作一个自定义的 Extension。
建立一个用于 Extension 的类很是简单:只须要新建一个普通的类,类中定义的属性就是 Extension 能够接收的配置。它不须要继承任何类,也不须要实现任何接口,以下所示:
class MyExtension{ public String nam = "name" public String sur = "surname"}复制代码
能够经过 ExtensionContainer 来建立和管理 Extension,ExtensionContainer 对象能够经过 Project 对象的 getExtensions 方法获取:
def extension = project.getExtensions().create('myExt',MyExtension)project.afterEvaluate { println("Hello from " + extension.toString())}复制代码
上面的代码片断能够直接复制到 apply 方法中或者放在 build.gradle 文件中使用。这里使用到了 create 方法来建立 Extension,咱们来看下 create 方法的定义:
<T> T create(String name, Class<T> type, Object... constructionArguments);复制代码
一、name:表明要建立的 Extension 的名字,例如:build.gradle 中名为 “android” 的块,Android Gradle 插件在建立这个 Extension 的时候 name 就须要填 “android”。Extension 的 name 不能和已有的重复,例如: Android Gradle 插件建立的 Extension name 为 “android”,那么其它 Extension name 就不能够再使用 “android”;
二、type:该 Extension 的类类型,这里的类就是上一小节建立的类,注意类的属性名与 Extension 中的属性名须要一致;
三、constructionArguments:类的构造函数参数值。
使用 create 方法以后,你可能会火烧眉毛的在下一行当即打印出 Extension 对象的值,不过这么作的话你会发现 Extension 对象打印出来的值并不对。不论你在 build.gradle 中怎么配置,Extension 对象就是读不到值。具体缘由能够回顾下这里的示例,你会发现示例里打印的逻辑写在了 afterEvaluate 方法中。这里的写法跟插件的生命周期有很大的关系,咱们将在下一节中介绍 Gradle 插件的生命周期。
官方对于 Gradle 构建的生命周期的定义:Gradle 的核心是一种基于依赖的语言,用 Gradle 的术语来讲这意味着你可以定义 Task 和 Task 之间的依赖关系。Gradle 会保证这些 Task 按照依赖关系的顺序执行而且每一个 Task 只会被执行一次,这些 Task 根据依赖关系构成了一个有向无环图。Gradle 在执行任何 Task 以前都会用内部的构建工具去完成这样这样一个图,这就是 Gradle 的核心。这种设计使得不少本来不可能的事情成为可能。
每次 Gradle 构建都须要通过三个不一样的阶段:
一、初始化阶段:Gradle 是支持单项目和多项目构建的,所以在初始化阶段,Gradle 会根据 settings.gradle 肯定须要参与构建的项目,并为每一个项目建立一个 Project 实例。Android Studio 的项目和 Module 对 Gradle 来讲都是项目;
二、配置阶段:在这个阶段会配置 Project 对象,而且全部项目的构建脚本都会被执行。例如:Extension 对象、Task 对象等都是在这个阶段被放到 Project 对象里;
三、执行阶段:通过了配置阶段,此时全部的 Task 对象都在 Project 对象中。而后,会根据终端命令指定的 Task 名字从 Project 对象中寻找对应的 Task 对象并执行。
Gradle 提供了不少生命周期的监听方法,用来在特定的阶段执行特定的任务。这里选取了部分回调方法,按照执行顺序关系画了一份 Gradle 生命周期流程简图,如图 4-1 所示:
图 4-1 Gradle 生命周期流程简图
图中的生命周期回调方法里,属于 Project 有 project.beforeEvaluate 和 project.afterEvaluate,它们的触发时机分别是在 Project 进行配置前和配置结束后。在以前的示例中,正是使用了这里的 afterEvaluate,因为方法的最后一个参数是闭包因此写法优化成了 afterEvaluate{}。
使用 create 建立的对象,在对应 Project 还没配置完成的时候打印出来的值天然是不正确的,须要在配置完成后才能正确获取到写在 build.gradle 中的 Extension 值。由于直接写在 apply 方法里的逻辑是在配置阶段执行的,因此会出现这种状况。
本文首先对 Gradle 的基础知识作了必定的介绍,包含 Project 与 Task,而后重点讲解了自定义 Gradle 插件从建立到使用的详细过程。但愿可以为编写自定义的 Gradle 插件提供必定的帮助。
顾鑫
神策数据 | SDK 技术顾问
我是顾鑫,神策数据 Android 技术顾问。神策数据是我就任的第一家公司,他很是的棒~学习中我喜欢作 Android 相关开发,也喜欢接触新兴技术,但愿在开源社区能与你共同窗习、共同进步。
本文著做权归「神策数据开源社区」全部,商业转载请联系咱们得到受权;非商业转载请注明出处,并附上神策数据开源社区服务号二维码。