乐趣区

关于前端:手把手教你写-Gradle-插件-数据采集

一、前言

在上一篇文章《神策 Android 全埋点插件介绍》中,咱们理解到神策 Android 插件其实是自定义的 Gradle 插件。Gradle 是一个专一于灵活性和性能的开源自动化构建工具,而插件的作用在于打包模块化的、可重用的构建逻辑。能够通过插件实现特定的逻辑,并打包起来分享给他人应用。例如:神策 Android 全埋点插件正是通过插件在编译时对特定函数进行解决,从而实现控件点击和 Fragment 页面浏览全埋点的采集。

本文咱们会先针对 Gradle 的基础知识作肯定的介绍,再举例说明如何实现一个自定义的 Gradle 插件。这里须要留神的是:文中采纳 ./gradlew 去执行 Gradle 的命令,如果是 Windows 用户的话须要改成 gradlew.bat。

二、Gradle 根底

Gradle 有两个重要的概念:Project 和 Task,本节将会介绍它们各自的作用以及之间的关系。

2.1 Project 简介

Project 是与 Gradle 交互中最重要的 API,咱们能够通过 Android Studio 的我的项目构造来了解 Project 的含意,如图 2-1 所示:


图 2-1 Android Studio 我的项目结构图
图 2-1 是写作过程中应用到的一个我的项目(名为 BlogDemo),蕴含 app 和 plugin 这两个 Module。这里不论是“我的项目”还是“Module”在构建时都会被 Gradle 形象成 Project 对象。它们的次要关系是:

1、Android Studio 构造中的我的项目相当于一个父 Project,而一个我的项目中所有的 Module 都是该父 Project 的子 Project;

2、每个 Project 都会对应一个 build.gradle 配置文件,因而应用 Android Studio 创立一个我的项目的时候在根目录下有一个 build.gradle 文件,在每个 Module 的目录下又各有一个 build.gradle 文件;

3、Gradle 是通过 settings.gradle 文件去进行多我的项目构建,从图 2-1 中也能够看出我的项目之间的关系。

父 Project 对象能够获取到所有的子 Project 对象,这样就能够在父 Project 对应的 build.gradle 文件中做一些对立的配置,例如:治理依赖的 Maven 核心库:


allprojects {

repositories {google()
    jcenter()}

}

2.2 Task 简介

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.gradle
task hello {

println 'Hello World!'

}
这段代码的含意是创立了一个名为“hello”的 Task,想要独自执行该 Task 的话,能够在 Android Studio 的 Terminal 中输出“./gradlew hello”,执行后就能够看到控制台输入“Hello World!”。

三、Gradle 插件构建

3.1 Plugin 简介

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”块实例化的对象,进而依据对象的属性值运行不同的编译逻辑。

3.2 构建独立我的项目的 Gradle 插件

Gradle 插件有三种实现形式,别离为 Build script、buildSrc project 和 Standalone project:

1、Build script 会把逻辑间接写在 build.gradle 文件中,Plugin 只对以后 build.gradle 文件可见;

2、buildSrc project 是将逻辑写在 rootProjectDir/buildSrc/src/main/java(最初一个门路文件夹也能够是 groovy 或 kotlin,次要取决于你用什么语言去实现自定义插件)目录下,Plugin 只对以后我的项目失效;

3、Standalone project 是将逻辑写在独立我的项目里,能够间接编译后把 JAR 包公布到近程仓库或者本地。

基于本文的写作目标,这里咱们次要解说 Standalone project,即独立我的项目的 Gradle 插件。

3.2.1 目录构造解析

一个独立我的项目的 Gradle 插件大抵构造如图 3-1 所示:

图 3-1 Gradle 插件我的项目目录示意图
在 main 文件夹下分为 groovy 文件夹与 resources 文件夹:

groovy 文件夹下是源码文件(Gradle 插件也反对应用 Java 与 Kotlin 编写,此处文件夹名依据理论语言确定);
resources 文件夹上面是资源文件。
其中,resources 文件夹下是固定格局的 META-INF/gradle-plugins/XXXX.properties,XXXX 就代表当前应用插件时须要指定的 plugin id。

目前 Android Studio 对于 Gradle 插件开发的反对不够好,很多 IDE 本能够实现的工作都须要咱们手动实现,例如:

1、Android Studio 不可能间接新建 Gradle 插件的 Module,只能先新建一个 Java Library 类型的 Module,再把多余的文件夹删除;

2、新建类默认是新建 Java 的类,新建的文件名后缀是“.java”,想要新建 Groovy 语法的类须要手动新建一个后缀为“.groovy”的文件,而后增加上 package、class 申明;

3、resources 整个都须要手动创立,文件夹名须要留神拼写;

4、删除掉 Module 的 build.gradle 全部内容,新加上 Gradle 插件开发须要的 Gradle 插件、依赖等。

3.2.2 编写插件

在写插件的代码之前,咱们须要对 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'
}

}
这里次要分为三局部内容:

1、apply 插件:利用 ‘groovy’ 插件是因为咱们的我的项目是应用 Groovy 语言开发的,’maven’ 插件在前面公布插件时会用到;

2、dependencies:申明依赖;

3、uploadArchive:这里是一些 maven 相干的配置,包含公布仓库的地位、groupId、artifactId、版本号,这里为了调试不便把地位选在我的项目根目录下的 repo 文件夹。

做好以上筹备之后,就能够开始源码的编写。Gradle 插件要求入口类须要实现 org.gradle.api.Plugin 接口,而后在实现办法 apply 中实现本人的逻辑:

package com.sensorsdata.plugin
class 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.properties
implementation-class=com.sensorsdata.plugin.MyPlugin

3.2.3 公布插件

实现编写插件的所有内容后,在终端执行

./gradlew uploadArchive
就能够公布插件。在上一大节编写插件的 build.gradle 文件中提前配置好了公布到 maven 仓库相干的配置,因而咱们这里执行该命令后,在我的项目根目录下就会呈现 repo 文件夹,文件夹中蕴含打包后的 JAR 文件。

3.2.4 应用插件

应用插件次要别离两个步骤:

(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!”。

3.3 可配置的插件

如果心愿插件的性能更加灵便的话,个别会预留一些可配置的参数,就像能够在主 Module 的“android”块配置编译的 Android SDK 版本、Build-Tools 版本等。“android”块的这个配置就是 Gradle 的 Extension,上面咱们来做一个自定义的 Extension。

3.3.1 创立 Extension 类

创立一个用于 Extension 的类非常简单:只须要新建一个一般的类,类中定义的属性就是 Extension 能够接管的配置。它不须要继承任何类,也不须要实现任何接口,如下所示:

class MyExtension{

public String nam = "name"
public String sur = "surname"

}

3.3.2 实例化 Extension 对象

能够通过 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);
1、name:代表要创立的 Extension 的名字,例如:build.gradle 中名为“android”的块,Android Gradle 插件在创立这个 Extension 的时候 name 就须要填“android”。Extension 的 name 不能和已有的反复,例如:Android Gradle 插件创立的 Extension name 为“android”,那么其它 Extension name 就不能够再应用“android”;

2、type:该 Extension 的类类型,这里的类就是上一大节创立的类,留神类的属性名与 Extension 中的属性名须要统一;

3、constructionArguments:类的构造函数参数值。

应用 create 办法之后,你可能会急不可待的在下一行立刻打印出 Extension 对象的值,不过这么做的话你会发现 Extension 对象打印进去的值并不对。不管你在 build.gradle 中怎么配置,Extension 对象就是读不到值。具体起因能够回顾下这里的示例,你会发现示例里打印的逻辑写在了 afterEvaluate 办法中。这里的写法跟插件的生命周期有很大的关系,咱们将在下一节中介绍 Gradle 插件的生命周期。

四、Gradle 构建的生命周期

官网对于 Gradle 构建的生命周期的定义:Gradle 的外围是一种基于依赖的语言,用 Gradle 的术语来说这意味着你可能定义 Task 和 Task 之间的依赖关系。Gradle 会保障这些 Task 依照依赖关系的程序执行并且每个 Task 只会被执行一次,这些 Task 依据依赖关系形成了一个有向无环图。Gradle 在执行任何 Task 之前都会用外部的构建工具去实现这样这样一个图,这就是 Gradle 的外围。这种设计使得很多本来不可能的事件成为可能。

每次 Gradle 构建都须要通过三个不同的阶段:

1、初始化阶段:Gradle 是反对单我的项目和多我的项目构建的,因而在初始化阶段,Gradle 会依据 settings.gradle 确定须要参加构建的我的项目,并为每个我的项目创立一个 Project 实例。Android Studio 的我的项目和 Module 对 Gradle 来说都是我的项目;

2、配置阶段:在这个阶段会配置 Project 对象,并且所有我的项目的构建脚本都会被执行。例如:Extension 对象、Task 对象等都是在这个阶段被放到 Project 对象里;

3、执行阶段:通过了配置阶段,此时所有的 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 插件提供肯定的帮忙。

文章起源:公众号神策技术社区

退出移动版