请点赞加关注,你的反对对我十分重要,满足下我的虚荣心。
🔥 Hi,我是小彭。本文已收录到 GitHub · Android-NoteBook 中。这里有 Android 进阶成长常识体系,有气味相投的敌人,欢送跟着我一起成长。(联系方式在 GitHub)
前言
Gradle 实质上是高度模块化的构建逻辑,便于重用并与别人分享。例如,咱们相熟的 Android 构建流程就是由 Android Gradle Plugin 引入的构建逻辑。在这篇文章里,我将带你探讨 Gradle 插件的应用办法、开发步骤和技巧总结。
这篇文章是全面把握 Gradle 构建零碎系列的第 2 篇:
- 1、Gradle 根底
- 2、Gradle 插件
- 3、Gradle 依赖治理
- 4、APG Transform
1. 意识 Gradle 插件
1.1 什么是 Gradle 插件
Gradle 和 Gradle 插件是两个齐全不同的概念,Gradle 提供的是一套外围的构建机制,而 Gradle 插件则是运行在这套机制上的一些具体构建逻辑,实质上和 .gradle 文件是雷同。例如,咱们相熟的编译 Java 代码的能力,都是由插件提供的。
1.2 Gradle 插件的长处
尽管 Gradle 插件与 .gradle 文件实质上没有区别,.gradle 文件也能实现 Gradle 插件相似的性能。然而,Gradle 插件应用了独立模块封装构建逻辑,无论是从开发开始应用来看,Gradle 插件的整体体验都更敌对。
- 逻辑复用: 将雷同的逻辑提供给多个类似我的项目复用,缩小反复保护相似逻辑开销。当然 .gradle 文件也能做到逻辑复用,但 Gradle 插件的封装性更好;
- 组件公布: 能够将插件公布到 Maven 仓库进行治理,其余我的项目能够应用插件 ID 依赖。当然 .gradle 文件也能够放到一个近程门路被其余我的项目援用;
- 构建配置: Gradle 插件能够申明插件扩大来裸露可配置的属性,提供定制化能力。当然 .gradle 文件也能够做到,但实现会麻烦些。
1.3 插件的两种实现模式
Gradle 插件的外围类是 Plugin<T>,个别应用 Project 作为泛型实参。当应用方引入插件后,其实就是调用了 Plugin#apply()
办法,咱们能够把 apply() 办法了解为插件的执行入口。例如:
MyCustomGradlePlugin.groovy
public class MyCustomGradlePlugin implements Plugin<Project> {
@Override
void apply(Project project) {println "Hello."}
}
如果依据实现模式分类(MyCustomGradlePlugin 的代码地位),能够把 Gradle 插件分为 2 类:
- 1、脚本插件: 脚本插件就是一个一般的脚本文件,它能够被导入都其余构建脚本中。有的敌人说脚本插件也须要应用 Plugin 接口才算脚本插件,例如:
build.gradle
apply plugin: MyCustomGradlePlugin
class MyCustomGradlePlugin implements Plugin<Project> {...}
- 2、二进制插件 / 对象插件: 在一个独自的插件模块中定义,其余模块通过 Plugin ID 利用插件。因为这种形式公布和复用更加敌对,咱们个别接触到的 Gradle 插件都是指二进制插件的模式。
1.4 利用插件的步骤
咱们总结下应用二进制插件的步骤:
- 1、将插件增加到 classpath: 将插件增加到构建脚本的 classpath 中,咱们的 Gradle 构建脚本能力利用插件。这里辨别本地依赖和近程依赖两种状况。
本地依赖: 指间接依赖本地插件源码,个别在调试插件的阶段是应用本地依赖的形式。例如:
我的项目 build.gradle
buildscript {
...
dependencies {
// For Debug
classpath project(":easyupload")
}
}
近程依赖: 指依赖已公布到 Maven 仓库的插件,个别咱们都是用这种形式依赖官网或第三方实现的 Gradle 插件。例如:
我的项目 build.gradle
buildscript {
repositories {google()
jcenter()}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
// 也能够应用另一种等价语法:classpath group: 'com.android.tools.build', name: 'gradle', version: '3.5.3'
}
...
}
- 2、应用 apply 利用插件: 在须要应用插件的 .gradle 脚本中应用
apply
利用插件,这将创立一个新的 Plugin 实例,并执行 Plugin#apply() 办法。例如:
apply plugin: 'com.android.application'
// 或者
plugins {// id «plugin id» [version «plugin version»] [apply «false»]
id 'com.android.application'
}
留神: 不反对在一个 build.gradle 中同时应用这两种语法。
1.5 非凡的 buildSrc 模块
插件模块的名称是任意的,除非应用了一个非凡的名称“buildSrc”,buildSrc 模块是 Gradle 默认的插件模块。buildSrc 模块实质上和一般的插件模块是一样的,有一些小区别:
- 1、buildSrc 模块会被自动识别为参加构建的模块,因而不须要在 settings.gradle 中应用 include 引入,就算引入了也会编译出错:
Build OutPut:'buildSrc' cannot be used as a project name as it is a reserved name
- 2、buildSrc 模块会主动被增加到构建脚本的 classpath 中,不须要手动增加:
buildscript {
...
dependencies {
// 不须要手动增加
// classpath project(":buildSrc")
}
}
- 3、buildSrc 模块的 build.gradle 执行机会早于其余 Project:
Executing tasks: [test]
settings.gradle:This is executed during the initialization phase.
> Configure project :buildSrc
build.gradle:buildSrc.
> Task :buildSrc:compileJava NO-SOURCE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources NO-SOURCE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :buildSrc:assemble UP-TO-DATE
> Task :buildSrc:pluginUnderTestMetadata UP-TO-DATE
> Task :buildSrc:compileTestJava NO-SOURCE
> Task :buildSrc:compileTestGroovy NO-SOURCE
> Task :buildSrc:processTestResources NO-SOURCE
> Task :buildSrc:testClasses UP-TO-DATE
> Task :buildSrc:test NO-SOURCE
> Task :buildSrc:validatePlugins UP-TO-DATE
> Task :buildSrc:check UP-TO-DATE
> Task :buildSrc:build UP-TO-DATE
...
> Configure project :
...
> Task :test
...
BUILD SUCCESSFUL in 19s
2. 自定义 Gradle 插件的步骤
这一节咱们来讲实现 Gradle 插件的具体步骤,根本步骤分为 5 步:
- 1、初始化插件目录构造
- 2、创立插件实现类
- 3、配置插件实现类
- 4、公布插件
- 5、应用插件
2.1 初始化插件目录构造
首先,咱们在 Android Studio 新建一个 Java or Kotlin Library
模块,这里以非 buildSrc 模块的状况为例:
而后,将模块 build.gradle 文件替换为以下内容:
模块 build.gradle
plugins {
id 'groovy' // Groovy Language
id 'org.jetbrains.kotlin.jvm' // Kotlin
id 'java-gradle-plugin' // Java Gradle Plugin
}
- groovy 插件: 应用 Groovy 语言开发必备;
- org.jetbrains.kotlin.jvm 插件: 应用 Kotlin 语言开发必备;
- java-gradle-plugin 插件: 用于帮忙开发 Gradle 插件,会主动利用 Java Library 插件,并在 dependencies 中增加
implementation gradleApi()
。
最初,依据你须要的开发语言补充对应的源码文件夹,不同语言有默认的源码文件夹,你也能够在 build.gradle 文件中从新指定:
模块 build.gradle
plugins {
id 'groovy' // Groovy Language
id 'org.jetbrains.kotlin.jvm' // Kotlin
id 'java-gradle-plugin' // Java Gradle Plugin
}
sourceSets {
main {
groovy {srcDir 'src/main/groovy'}
java {srcDir 'src/main/java'}
resources {srcDir 'src/main/resources'}
}
}
插件目录构造:
2.2 创立插件实现类
新建一个 Plugin<T> 实现类,并重写 apply 办法中增加构建逻辑,例如:
com.pengxr.easyupload.EasyUpload.groovy
class EasyUpload implements Plugin<Project> {
@Override
void apply(Project project) {
// 构建逻辑
println "Hello."
}
}
2.3 配置插件实现类
在模块 build.gradle 文件中减少以下配置,gradlePlugin 定义了插件 ID 和插件实现类的映射关系:
gradlePlugin {
plugins {
modularPlugin {
// Plugin id.
id = 'com.pengxr.easyupload'
// Plugin implementation.
implementationClass = 'com.pengxr.easyupload.EasyUpload'
}
}
}
这其实是 Java Gradle Plugin 提供的一个简化 API,其背地会主动帮咱们创立一个 [插件 ID].properties 配置文件,Gradle 就是通过这个文件类进行匹配的。如果你不应用 gradlePlugin API,间接手动创立 [插件 ID].properties 文件,作用是齐全一样的。
要点:
- 1、[插件 ID].properties 文件名是插件 ID,用于利用插件
- 2、[插件 ID].properties 文件内容配置了插件实现类的映射,须要应用
implementation-class
来指定插件实习类的全限定类名
implementation-class=com.pengxr.easyupload.EasyUpload
2.4 公布插件
咱们应用 maven 插件 来公布仓库,在模块 build.gradle 文件中减少配置:
模块 build.gradle
plugins {
id 'groovy' // Groovy Language
id 'org.jetbrains.kotlin.jvm' // Kotlin
id 'java-gradle-plugin' // Java Gradle Plugin
}
gradlePlugin {
plugins {
modularPlugin {
// Plugin id.
id = 'com.pengxr.easyupload'
// Plugin implementation.
implementationClass = 'com.pengxr.easyupload.EasyUpload'
}
}
}
uploadArchives {
repositories {
mavenDeployer {repository(url: uri('../localMavenRepository/snapshot'))
pom.groupId = 'com.pengxr'
pom.artifactId = 'easyupload'
pom.version = '1.0.0'
}
}
}
执行 uploadArchives
工作,会公布插件到我的项目根目录中的 localMavenRepository 文件夹,理论我的项目中通常是公布到 Nexus 私库或 Github 公共库等。不相熟组件公布的话能够回顾:Android 工程化实际:组件化公布,此处不开展。
2.5 应用插件
在我的项目级 build.gradle 文件中将插件增加到 classpath:
我的项目 build.gradle
buildscript {
repositories {google()
jcenter()
maven {url "$rootDir/localMavenRepository/snapshot"}
maven {url "$rootDir/localMavenRepository/release"}
}
dependencies {
// For debug
// classpath project(":easyupload")
classpath "com.pengxr:easyupload:1.0.0"
}
...
}
在模块级 build.gradle 文件中 apply 插件:
模块 build.gradle
// '我的项目 build.gradle' 是在 gradlePlugin 中定义的插件 ID
apply plugin: 'com.pengxr.easyupload'
实现以上步骤并同步我的项目,从 Build Output 能够看到咱们的插件失效了:
Build Output:
Hello.
到这里,自定义 Gradle 插件最根本的步骤就实现了,接下来就能够在 Plugin#apply 办法中开始你的表演。
3. 插件扩大机制
Extension 扩大是插件为内部构建脚本提供的配置项,用于反对内部自定义插件的工作形式,其实就是一个对外开放的 Java Bean 或 Groovy Bean。例如,咱们相熟的 android{}
就是 Android Gradle Plugin 提供的扩大。
当你利用一个插件时,插件定义的扩大会以 扩展名 - 扩大对象
键值对的模式保留在 Project 中的 ExtensionContainer 容器中。插件内外部也是通过 ExtensionContainer 拜访扩大对象的。
注意事项:
- 扩展名: 不反对在同一个 Project 上增加反复的扩展名;
- 映射关系: 增加扩大后,不反对从新设置扩大对象;
- DSL: 反对用
扩展名 {}
DSL 的模式拜访扩大对象。
3.1 根本步骤
这一节咱们来讲实现 Extension 扩大的具体步骤,根本步骤分为 5 步:
- 1、定义扩大类: 定义一个扩大配置类:
Upload.groovy
class Upload {String name}
提醒: 依据”约定优先于配置“准则,尽量为配置提供默认值,或者保障配置缺省时也能失常执行。
- 2、创立并增加扩大对象: 在 Plugin#apply() 中,将扩大对象增加到 Project 的 ExtensionContainer 容器中:
EasyUpload.groovy
class EasyUpload implements Plugin<Project> {
// 扩展名
public static final String UPLOAD_EXTENSION_NAME = "upload"
@Override
void apply(Project project) {
// 增加扩大
applyExtension(project)
// 增加 Maven 公布能力
applyMavenFeature(project)
}
private void applyExtension(Project project) {
// 创立扩大,并增加到 ExtensionContainer
project.extensions.create(UPLOAD_EXTENSION_NAME, Upload)
}
private void applyMavenFeature(Project project) {// 构建逻辑}
}
- 3、配置扩大: 应用方利用插件后,应用
扩展名 {}
DSL 定制插件行为:
build.gradle
apply plugin: 'com.pengxr.easyupload'
upload {name = "Peng"}
- 4、应用扩大: 在 Plugin#apply() 中,通过 Project 的 ExtensionContainer 容器获取扩大对象,获取的代码倡议封装在扩大对象外部。例如:
class EasyUpload implements Plugin<Project> {
// 扩展名
public static final String UPLOAD_EXTENSION_NAME = "upload"
@Override
void apply(Project project) {
// 增加扩大
applyExtension(project)
// 增加 Maven 公布能力
applyMavenFeature(project)
}
private void applyExtension(Project project) {
// 创立扩大,并增加到 ExtensionContainer 容器
project.extensions.create(UPLOAD_EXTENSION_NAME, Upload)
}
private void applyMavenFeature(Project project) {
project.afterEvaluate {
// 1. Upload extension
Upload rootConfig = Upload.getConfig(project.rootProject)
// 构建逻辑 ...
}
}
}
Upload.groovy
class Upload {
String name
// 将获取扩大对象的代码封装为静态方法
static Upload getConfig(Project project) {
// 从 ExtensionContainer 容器获取扩大对象
Upload extension = project.getExtensions().findByType(Upload.class)
// 配置缺省的时候,赋予默认值
if (null == extension) {extension = new Upload()
}
return extension
}
/**
* 查看扩大配置是否无效
*
* @return true:valid
*/
boolean checkParams() {return true}
}
提醒: ExtensionContainer#create() 反对变长参数,反对调用扩大类带参数的构造函数,例如:
project.extensions.create(UPLOAD_EXTENSION_NAME, Upload,"Name")
将调用构造函数Upload(String str)
。
- 5、构建逻辑: 到这里,实现插件扩大最根本的步骤就实现了,接下来就能够在 Plugin#apply 办法中持续实现你的表演。
3.2 project.afterEvaluate 的作用
应用插件扩大肯定会用到 project.afterEvaluate()
生命周期监听,这里解释一下:因为扩大配置代码的执行机会晚于 Plugin#apply() 的执行机会,所以如果不应用 project.afterEvaluate(),则在插件外部将无奈正确获取配置值。
project.afterEvaluate() 会在以后 Project 配置实现后回调,这个机会扩大配置代码曾经执行,在插件外部就能够正确获取配置值。
apply plugin: 'com.pengxr.easyupload'
// 执行机会晚于 apply
upload {name = "Peng"}
3.3 嵌套扩大
在扩大类中组合另一个配置类的状况,咱们称为嵌套扩大,例如咱们相熟的 defaultConfig{}
就是一个嵌套扩大:
android {
compileSdkVersion 30
buildToolsVersion "30.0.0"
defaultConfig {
minSdkVersion 21
...
}
...
}
默认下嵌套扩大是不反对应用闭包配置,咱们须要在内部扩大类中定义闭包函数。例如:
Upload.groovy
class Upload {
// 嵌套扩大
Maven maven
// 嵌套扩大
Pom pom
// 嵌套扩大闭包函数,办法名为 maven(办法名不肯定须要与属性名统一)void maven(Action<Maven> action) {action.execute(maven)
}
// 嵌套扩大闭包函数,办法名为 maven
void maven(Closure closure) {ConfigureUtil.configure(closure, maven)
}
// 嵌套扩大闭包函数,办法名为 pom
void pom(Action<Pom> action) {action.execute(pom)
}
// 嵌套扩大闭包函数,办法名为 pom
void pom(Closure closure) {ConfigureUtil.configure(closure, pom)
}
}
应用时:
build.gradle
apply plugin: 'com.pengxr.easyupload'
upload {
maven {...}
}
3.4 NamedDomainObjectContainer 命名 DSL
在 Android 工程中,你肯定在 build.gradle 文件中见过以下配置:
build.gradle
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {...}
// 反对任意命名
preview {...}
}
}
除了内置的 release 和 debug,咱们能够在 buildType 中定义任意多个且任意名称的类型,这个是如果实现的呢?—— 这背地是因为 buildTypes 是 NamedDomainObjectContainer 类型,源码体现:
com.android.build.api.dsl.CommonExtension.kt
val buildTypes: NamedDomainObjectContainer<BuildType>
NamedDomainObjectContainer 的作用:
NamedDomainObjectContainer<T> 直译是命名畛域对象容器,是一个反对配置不固定数量配置的容器。次要性能分为 3 点:
- Set 容器: 反对增加多个 T 类型对象,并且不容许命名反复;
- 命名 DSL: 反对以 DSL 的形式配置 T 类型对象,这也要求 T 类型必须带有 String name 属性,且必须带有以 String name 为参数的 public 构造函数;
- SortSet 容器: 容器将保障元素以 name 天然程序排序。
那么,以上配置相当于以下伪代码:
build.gradle
val buildTypes : Collections<BuildType>
BuildType release = BuildType("release")
release.minifyEnabled = false
release.proguardFiles = getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
BuildType debug = BuildType("debug")
...
BuildType preview = BuildType("preview")
...
buildTypes.add(release)
buildTypes.add(debug)
buildType.add(preview)
NamedDomainObjectContainer 的用法:
这里介绍一下具体用法,咱们仅以你相熟的 BuildType 为例,但不等于以下为源码。
- 1、定义类型 T: 在类型 T 中必须带有以 String name 为参数的 public 构造函数。例如:
BuildType.groovy
class BuildType {
// 必须有 String name 属性,且不容许结构后批改
@Nonnull
public final String name
// 业务参数
boolean minifyEnabled
BuildType(String name) {this.name = name}
}
- 2、定义 NamedDomainObjectContainer 属性: 在扩大类中定义一个 NamedDomainObjectContainer<BuildType> 类型属性。例如:
CommonExtension.grooyv
class CommonExtension {
NamedDomainObjectContainer<BuildType> buildTypes
CommonExtension(Project project) {// 通过 project.container(...) 办法创立 NamedDomainObjectContainer
NamedDomainObjectContainer<BuildType> buildTypeObjs = project.container(BuildType)
buildTypes = buildTypeObjs
}
// 嵌套扩大闭包函数,办法名为 buildTypes
void buildTypes(Action<NamedDomainObjectContainer<BuildType>> action) {action.execute(buildTypes)
}
void buildTypes(Closure closure) {ConfigureUtil.configure(closure, buildTypes)
}
}
- 3、创立 Extension: 依照 4.1 节介绍的步骤创立扩大。例如:
project.extensions.create("android", CommonExtension)
到这里,就能够依照 buildTypes {} 的形式配置 BuildType 列表了。然而,你会发现每个配置项必须应用 =
进行赋值。这就有点膈应人了,有懂的大佬领导一下。
android {
buildTypes {
release {
// 怎样才能省略 = 号呢?minifyEnabled = false
proguardFiles = getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
4. 插件调试
4.1 两个调试办法
在开发插件的过程肯定须要调试,除了通过日志调试,咱们也有断点调试的需要。这里总结两个办法:办法 1 尽管只反对调试简略执行工作,但曾经能满足大部分需要,而且绝对简略。而办法 2 反对命令行增加参数。
办法 1(简略): 间接提供 Android Studio 中 Gradle 面板的调试性能,即可调试插件。如下图,咱们抉择与插件性能相干的 Task,并右键抉择 Debug 执行。
办法 2: 通过配置 IDE Configuration 以反对调试命令行工作,具体步骤:
- 1、创立 Remote 类型 Configuration:
- 2、执行命令:
./gradlew Task -Dorg.gradle.debug=true --no-daemon
(开启 Debug & 不应用守护过程),执行后命令行会进入期待状态:
- 3、Attach Debug: 点击调试按钮,即可开始断点调试。
4.2 调试技巧
一些调试技巧:
- 援用插件源码: 在开发阶段能够间接本地依赖插件源码,而不须要将插件公布到 Maven 仓库,只须要在 build.gradle 文件中批改配置:
我的项目 build.gradle
buildscript {
repositories {google()
jcenter()}
dependencies {
// For Debug
classpath project(":easyupload")
// classpath "com.pengxr:easyupload:1.0.0"
}
...
}
- 插件代码开关: 因为 Plugin#apply 中的代码在配置阶段执行,如果其中的代码有问题就会呈现 Sync 报错。又因为编译插件代码须要先 Sync,只能先将工程中所有应用插件的代码正文掉,从新编译插件模块,再将正文批改回来。真麻烦!咱们还是加一个开关吧,例如:
gradle.properties
ENABLED=true
模块 build.gradle
if (ENABLED.toBoolean()) {
apply plugin: 'com.pengxr.easyupload'
upload {name = "123"}
}
5. 插件开发技巧总结
- 判断是否以后是 App 模块还是 Library 模块: 当咱们开发 Android 我的项目相干插件时,常常须要依据插件的应用环境辨别不同逻辑。例如插件利用在 App 模块和 Library 模块会采纳不同逻辑。此时,咱们能够用在 Plugin#apply() 中采纳以下判断:
project.afterEvaluate {
// 1. Check if apply the‘com.android.application’plugin
if (!project.getPluginManager().hasPlugin("com.android.application")) {return}
}
-
插件开发语言: 最后,Groovy 是 Gradle 的首要语言,但随着 Java 和 Kotlin 语言的演进,这一现状有所扭转。当初的趋势是:Gradle 脚本应用 Groovy 或 Kotlin 开发,而 Gradle 插件应用 Kotlin 开发。例如,咱们能够发现 AGP 当初曾经用 Kotlin 开发了。尽管趋势是往 Kotlin 靠,但目前存量的 Gradle 脚本 / 插件还是以 Groovy 为主。
- Groovy 劣势:社区积淀、动静语言
- Kotlin 劣势:IDE 反对、趋势
原文: In general, a plugin implemented using Java or Kotlin, which are statically typed, will perform better than the same plugin implemented using Groovy.
6. 总结
到这里,Gradle 插件的局部就讲完了,须要 Demo 的同学能够看下咱们之前实现过的小插件:EasyPrivacy。在本系列后续的文章中,也会有新的插件 Demo。关注我,带你理解更多,咱们下次见。
参考资料
- 《实战 Gradle》—— [美] Benjamin Muschko 著,李建 朱本威 杨柳 译
- 《Gradle for Android》—— [美] Kevin Pelgrims 著,余小乐 译
- Groovy 参考文档 —— Groovy 官网文档
- Gradle 阐明文档 —— Gradle 官网文档
- Gradle DSL 参考文档 —— Gradle 官网文档
- Developing Custom Gradle Plugins —— Gradle 官网文档
- Using Gradle Plugins —— Gradle 官网文档
- 深刻摸索 Gradle 自动化构建技术(系列)—— jsonchao 著
你的点赞对我意义重大!微信搜寻公众号 [彭旭锐],心愿大家能够一起探讨技术,找到气味相投的敌人,咱们下次见!