请点赞加关注,你的支持对我非常重要,满足下我的虚荣心。
?? Hi,我是小彭。本文已收录到 GitHub · Android-NoteBook 中。这里有 Android 进阶成长知识体系,有志同道合的朋友,欢迎跟着我一起成长。(联系方式在 GitHub)
Gradle 本质上是高度模块化的构建逻辑,便于重用并与他人分享。例如,我们熟悉的 Android 构建流程就是由 Android Gradle Plugin 引入的构建逻辑。在这篇文章里,我将带你探讨 Gradle 插件的使用方法、开发步骤和技巧总结。
这篇文章是全面掌握 Gradle 构建系统系列的第 2 篇:
Gradle 和 Gradle 插件是两个完全不同的概念,Gradle 提供的是一套核心的构建机制,而 Gradle 插件则是运行在这套机制上的一些具体构建逻辑,本质上和 .gradle 文件是相同。例如,我们熟悉的编译 Java 代码的能力,都是由插件提供的。
虽然 Gradle 插件与 .gradle 文件本质上没有区别,.gradle 文件也能实现 Gradle 插件类似的功能。但是,Gradle 插件使用了独立模块封装构建逻辑,无论是从开发开始使用来看,Gradle 插件的整体体验都更友好。
Gradle 插件的核心类是 PluginPlugin#apply() 方法,我们可以把 apply() 方法理解为插件的执行入口。例如:
MyCustomGradlePlugin.groovy
public class MyCustomGradlePlugin implements Plugin<Project> { @Override void apply(Project project) { println "Hello." }}如果根据实现形式分类(MyCustomGradlePlugin 的代码位置),可以把 Gradle 插件分为 2 类:
build.gradle
apply plugin: MyCustomGradlePluginclass MyCustomGradlePlugin implements Plugin<Project> { ...}我们总结下使用二进制插件的步骤:
本地依赖: 指直接依赖本地插件源码,一般在调试插件的阶段是使用本地依赖的方式。例如:
项目 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' } ...}apply 应用插件,这将创建一个新的 Plugin 实例,并执行 Plugin#apply() 方法。例如:apply plugin: 'com.android.application'// 或者plugins { // id ?plugin id? [version ?plugin version?] [apply ?false?] id 'com.android.application'}注意: 不支持在一个 build.gradle 中同时使用这两种语法。
插件模块的名称是任意的,除非使用了一个特殊的名称 “buildSrc”,buildSrc 模块是 Gradle 默认的插件模块。buildSrc 模块本质上和普通的插件模块是一样的,有一些小区别:
Build OutPut:'buildSrc' cannot be used as a project name as it is a reserved namebuildscript { ... dependencies { // 不需要手动添加 // classpath project(":buildSrc") }}Executing tasks: [test] settings.gradle:This is executed during the initialization phase.> Configure project :buildSrcbuild.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
这一节我们来讲实现 Gradle 插件的具体步骤,基本步骤分为 5 步:
首先,我们在 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}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' } }}插件目录结构:

新建一个 Plugin
com.pengxr.easyupload.EasyUpload.groovy
class EasyUpload implements Plugin<Project> { @Override void apply(Project project) { // 构建逻辑 println "Hello." }}在模块 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 文件,作用是完全一样的。

要点:
implementation-class来指定插件实习类的全限定类名implementation-class=com.pengxr.easyupload.EasyUpload我们使用 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工程化实践:组件化发布,此处不展开。
在项目级 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 中定义的插件 IDapply plugin: 'com.pengxr.easyupload'完成以上步骤并同步项目,从 Build Output 可以看到我们的插件生效了:
Build Output:Hello.到这里,自定义 Gradle 插件最基本的步骤就完成了,接下来就可以在 Plugin#apply 方法中开始你的表演。
Extension 扩展是插件为外部构建脚本提供的配置项,用于支持外部自定义插件的工作方式,其实就是一个对外开放的 Java Bean 或 Groovy Bean。例如,我们熟悉的 android{} 就是 Android Gradle Plugin 提供的扩展。
当你应用一个插件时,插件定义的扩展会以 扩展名-扩展对象 键值对的形式保存在 Project 中的 ExtensionContainer 容器中。插件内外部也是通过 ExtensionContainer 访问扩展对象的。
注意事项:
扩展名 {} DSL 的形式访问扩展对象。这一节我们来讲实现 Extension 扩展的具体步骤,基本步骤分为 5 步:
Upload.groovy
class Upload { String name}提示: 根据 ”约定优先于配置“ 原则,尽量为配置提供默认值,或者保证配置缺省时也能正常执行。
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) { // 构建逻辑 }}扩展名 {} DSL定制插件行为:build.gradle
apply plugin: 'com.pengxr.easyupload'upload { name = "Peng"}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)。
使用插件扩展一定会用到 project.afterEvaluate() 生命周期监听,这里解释一下:因为扩展配置代码的执行时机晚于 Plugin#apply() 的执行时机,所以如果不使用 project.afterEvaluate(),则在插件内部将无法正确获取配置值。
project.afterEvaluate() 会在当前 Project 配置完成后回调,这个时机扩展配置代码已经执行,在插件内部就可以正确获取配置值。
apply plugin: 'com.pengxr.easyupload'// 执行时机晚于 applyupload { name = "Peng"}在扩展类中组合另一个配置类的情况,我们称为嵌套扩展,例如我们熟悉的 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 { ... }}在 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
那么,以上配置相当于以下伪代码:
build.gradle
val buildTypes : Collections<BuildType>BuildType release = BuildType("release")release.minifyEnabled = falserelease.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 为例,但不等于以下为源码。
BuildType.groovy
class BuildType { // 必须有 String name 属性,且不允许构造后修改 @Nonnull public final String name // 业务参数 boolean minifyEnabled BuildType(String name) { this.name = name }}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) }}project.extensions.create("android", CommonExtension)到这里,就可以按照 buildTypes {} 的方式配置 BuildType 列表了。然而,你会发现每个配置项必须使用 = 进行赋值。这就有点膈应人了,有懂的大佬指导一下。
android { buildTypes { release { // 怎样才能省略 = 号呢? minifyEnabled = false proguardFiles = getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }}在开发插件的过程一定需要调试,除了通过日志调试,我们也有断点调试的需求。这里总结两个方法:方法 1 虽然只支持调试简单执行任务,但已经能满足大部分需求,而且相对简单。而方法 2 支持命令行添加参数。
方法 1(简单): 直接提供 Android Studio 中 Gradle 面板的调试功能,即可调试插件。如下图,我们选择与插件功能相关的 Task,并右键选择 Debug 执行。

方法 2: 通过配置 IDE Configuration 以支持调试命令行任务,具体步骤:

./gradlew Task -Dorg.gradle.debug=true --no-daemon (开启 Debug & 不使用守护进程),执行后命令行会进入等待状态:

一些调试技巧:
项目 build.gradle
buildscript { repositories { google() jcenter() } dependencies { // For Debug classpath project(":easyupload") // classpath "com.pengxr:easyupload:1.0.0" } ...}gradle.properties
ENABLED=true模块 build.gradle
if (ENABLED.toBoolean()) { apply plugin: 'com.pengxr.easyupload' upload { name = "123" }}project.afterEvaluate { // 1. Check if apply the ‘com.android.application’ plugin if (!project.getPluginManager().hasPlugin("com.android.application")) { return }}原文: In general, a plugin implemented using Java or Kotlin, which are statically typed, will perform better than the same plugin implemented using Groovy.
到这里,Gradle 插件的部分就讲完了,需要 Demo 的同学可以看下我们之前实现过的小插件: EasyPrivacy。在本系列后续的文章中,也会有新的插件 Demo。关注我,带你了解更多,我们下次见。