前言
通过阅读,帮大家梳理Gradle插件的脉络,知道Gradle插件是什么,能帮我们做什么。
但凡对大家有一些用处,都是好的。
一、什么是Gradle
一种自动化构建工具,构建脚本使用Groovy/Koltin的DSL编写,而不是传统的XML
1.自动化构建
构建,即Build。
一说Build,Android同学就很熟悉了,Android打包流程就是一个Build过程。
下面是一段Android工程Gradle配置。
Sample
-- app
-- src
-- build.gradle
-- mylibrary
-- src
-- build.gradle
-- build.gradle
-- settings.gradle
Android打包流程示意
Gradle Build代码,是一段程序文件,运行在本机的JVM上。就如同Apk包是一段程序文件,运行在Art上。
但凡是程序,大抵是要有输入和输出的。
- apk程序的输入是用户操作应用程序,输出为响应操作。
- Gradle build程序的输入为项目文件(源码,资源等等),输出为Apk文件。
自动化
一次构建需要处理成千上万个原始文件,最终仅生成一个apk文件,中间有几十上百个流程。如果没有这个构建程序帮我们把流程串联起来,实现自动化,我无法想象光靠人力如何能解决。
这种自动化的能力,不是天生的,是先驱努力的结果。 大家也有机会成为后人的先驱,青史留名。
扩展了解:Jenkins构建
2.Groovy语言
初印象
一说到Gradle,大家第一印象可能是下面这串,不错,这些就是Groovy语言编写的。
// app/build.gradle
plugins {
id 'com.android.application'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.a.gradlepluginsample"
minSdk 24
targetSdk 31
versionCode 1
versionName "1.0"
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}
再看Groovy
我们来写一段Groovy代码
//Main.groovy
//跟java一模一样
static void main(String[] args) {
def person = new Person()
person.name = "zhang san"
println("Hello world! ${person.name}")
}
class Person {
def name
}
//运行结果: Hello world! zhang san
再看一段
//ClosureSample.groovy
static def testClosure(String a, int b, Closure closure) {
println("$a! $b")
closure()
}
static def testClosure2( Closure closure) {
closure()
}
static void main(String[] args) {
testClosure("hello world", 1) {
println "i am in closure 1"
}
//这个写法更像 buildGradle中的配置
testClosure2 {
println "i am in closure 2"
}
}
//运行结果:
hello world! 1
i am in closure 1
i am in closure 2
是java,又不是java的感觉。。。
Groovy 是 用于JVM的一种敏捷的动态语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。 基本特点:
- Groovy代码动态地编译成运行于Java虚拟机(JVM)上的Java字节码,并与其他Java代码和库进行互操作。
- 支持闭包,支持DSL(Domain Specific Languages领域特定语言)(build.gradle)
- 源文件可以当作脚本直接运行,区别于Java源文件
下面这张图,很好的表达了Groovy的部分特性
扩展:Main.Groovy编译后产物
//Main.class
public class Main extends Script {
public Main() {
CallSite[] var1 = $getCallSiteArray();
super();
}
public Main(Binding context) {
CallSite[] var2 = $getCallSiteArray();
super(context);
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].callStatic(InvokerHelper.class, Main.class, args);
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
Object person = var1[1].callConstructor(Person.class);
String var3 = "zhang san";
ScriptBytecodeAdapter.setProperty(var3, (Class)null, person, (String)"name");
return var1[2].callCurrent(this, new GStringImpl(new Object[]{var1[3].callGetProperty(person)}, new String[]{"Hello world! ", ""}));
}
}
3.基于Groovy的DSL脚本
领域特定语言(DSL)
专注于某个特性程序领域的计算机语言。例如Gradle脚本文件,Android布局xml文件,web的Html文件,正则表达式,SQL等等,离开特定宿主程序,无法使用。
Gradle主要是利用Groovy的闭包特性来编写DSL脚本,所以build.gradle看起来不像代码,更像是一堆配置。
小结
现在我们对Gradle有了初步的认识
- 构建工具
- Groovy开发
- DSL脚本
那么Gradle构建Android项目的过程是什么样的?
二、Gradle构建Android项目
下面是一段Android工程Gradle配置。(上面复制过来)
Sample
-- app
-- src
-- build.gradle
-- mylibrary
-- src
-- build.gradle
-- build.gradle
-- settings.gradle
Android打包流程示意
构建特点:
- 拆分不同的任务
- 任务有先后依赖关系
- 前一个任务输出是下一个任务的输入,例如class合成为dex
- 任务也可以并行,如果app和mylibrary没有依赖关系就可以并行提速。
如果是你,你会怎么设计这个构建框架?
先来看看Android Project中的实际情况
整个工程是一个Project。app,buildSrc,mylibrary都是它的Sub Project。 每个Sub Project都对应一组Task集合。
当我们运行app module,会先运行 tasks [:app:assembleDebug]生成apk文件
一次构建被分解为N个Task的执行过程。
Executing tasks: [:app:assembleDebug] in project ../../GradlePluginSample
//buildSrc中的这组是自定义插件内容,先跳过。
> Task ....
> Task :buildSrc:compileKotlin UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :buildSrc:build UP-TO-DATE
> Configure project :
> Configure project :app
//列举一些好玩的Task
> Task ....
//生成BuildConfig文件
> Task :app:generateDebugBuildConfig
// merge所有的manifest文件
> Task :app:processDebugManifest
//AAPT 生成R文件
> Task :app:processDebugResources
> Task ....
//javac 编译java文件
> Task :app:compileDebugJavaWithJavac
//转换class文件为dex文件
> Task :app:dexBuilderDebug
//打包成apk并签名
> Task :app:packageDebug
那么问题来了,我能写自己的task,嵌入到这些自带的task中么?
答案当然是可以。我们在build.gradle中追加如下代码,然后Gradle Sync后,出现了右侧的task,双击右侧 "Task a"
build 日志如下
Executing tasks: [a] in project /../GradlePluginSample/app
hi settings.gradle
> Task :buildSrc:compileKotlin UP-TO-DATE
> Configure project :
hi root build.gradle
> Configure project :app
hi app build.gradle
task a in Configure //相关输出
> Task :app:...
> Task :app:compileDebugJavaWithJavac UP-TO-DATE
> Task :app:dexBuilderDebug UP-TO-DATE
> Task :app:packageDebug UP-TO-DATE
> Task :app:assembleDebug UP-TO-DATE
> Task :app:a //相关task执行
last //相关task执行输出
到这里,一个简单的task就写出来了,并且还给它设置了依赖执行的task。
那接下来又会去想,如果我这个task比较复杂呢,而且xx.gradle更偏向于DSL脚本,在这里写逻辑是不合适的,那怎么办?
我们可以把核心逻辑写到单独的工程中,然后在当前工程的xx.gradle中引入核心逻辑。
- 这个单独的工程的产物,即为Gradle插件
- 当前工程引入Gradle插件,同时在xx.gradle中为Gradle插件配置参数
再看app/build.gradle,
plugins {
id 'com.android.application'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.a.gradlepluginsample"
minSdk 24
targetSdk 31
versionCode 1
versionName "1.0"
}
}
你甚至可以build.gradle中直接写插件,但插件还是推荐写在独立工程,方便上传仓库,供多个应用使用。
// app/build.gradle
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('ahello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
// Apply the plugin
apply plugin: GreetingPlugin
Gradle插件共有三种方式创建
- 直接脚本,如上
- buildSrc Module
- 独立工程
buildSrc和独立工程较为接近,区别在于,独立工程可以发布到外部仓库。buildSrc只能当前项目使用。
可以通过 ./gradlew init 创建插件工程,但手动也并不麻烦,核心就两个文件
手动在root目录下新建buildSrc文件夹,项目结构如下
Sample
-- buildSrc
-- src/main/kotlin/com/x/x/xxPlugin.kt
-- build.gradle
-- build.gradle
-- settings.gradle
这里我们就可以用Java/kotlin/Groovy编写Plugin逻辑了,其实kotlin也可以编写xx.gradle.
// buildSrc/build.gradle
plugins {
id 'java-gradle-plugin'
id 'org.jetbrains.kotlin.jvm' version '1.5.31'
}
repositories {
jcenter()
google()
mavenCentral()
}
dependencies {
implementation gradleApi() //gradle sdk
implementation 'com.android.tools.build:gradle:7.2.1'
}
gradlePlugin {
plugins {
//定义插件,原先是通过resource文件配置
firstPlugin {
id = 'com.a.first'
implementationClass = 'com.a.plugin.FirstPlugin'
}
}
}
// buildSrc/src/main/kotlin/com/a/plugin/FirstPlugin.kt
package com.a.plugin
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ApplicationVariant
import org.gradle.api.Plugin
import org.gradle.api.Project
class FirstPlugin : Plugin<Project> {
override fun apply(project: Project) {
println("hello first plugin")
println("project name ${project.name}")
//可以获取到在app/build.gradle中配置的一些属性
var appExtension = project.extensions.getByType(AndroidComponentsExtension::class.java)
appExtension.onVariants { variant ->
if (variant is ApplicationVariant) {
println("---buildType:${variant.buildType}---")
println("minSdkVersion:${variant.minSdkVersion.apiLevel}")
println("applicationId:${variant.applicationId.get()}")
}
}
}
}
Gradle Sync后,在app中引入插件
// app/build.gradle
plugins {
id 'com.android.application'
//使用插件
id 'com.a.first'
}
双击运行 task assembleDebug,build日志输出
10:10:39: Executing task 'assembleDebug'...
Executing tasks: [assembleDebug] in project /Users/x/y/z/GradlePluginSample/app
hi settings.gradle
> Task :buildSrc:compileKotlin UP-TO-DATE
> Task :...
> Task :buildSrc:jar UP-TO-DATE
> Task :buildSrc:validatePlugins UP-TO-DATE
> Task :buildSrc:build UP-TO-DATE
> Configure project :
hi root build.gradle
> Configure project :app
hello first plugin
project name app
hi app build.gradle
//可以获取到在app/build.gradle中配置的一些属性
---buildType:debug---
minSdkVersion:24
applicationId:com.a.gradlepluginsample
---buildType:release---
minSdkVersion:24
applicationId:com.a.gradlepluginsample
> Task :app:...
> Task :app:compileDebugJavaWithJavac UP-TO-DATE
> Task :app:dexBuilderDebug UP-TO-DATE
> Task :app:packageDebug UP-TO-DATE
> Task :app:assembleDebug UP-TO-DATE
上面的日志输出,你可能会注意到两点:
- 在执行Task之前,总是会先执行Configure,而且Configure先Root Project,然后Sub Project
- 我们可以获取到在app/build.gradle中配置的一些属性,例如applicationId,buildType等等,并且是可编程修改的。
小结:
Gradle是什么?引用开头的话:
Gradle Build代码,是一段程序文件,面向构建过程编程。
Gradle插件是什么?
Gradle提供编程框架理念,而具体场景逻辑,以插件的形式进行扩展
我们可以编写插件,嵌入到构建的各个阶段。
Gradle插件能做什么事情
先找找工程中用到的第三方插件:
//多渠道打包
apply plugin: 'com.tencent.vasdolly'
//组件化方案
apply plugin: 'com.alibaba.arouter'
//自动化数据埋点
apply plugin: 'com.x.plugin.statistics'
//上传到maven仓库
apply plugin: 'maven-publish'
还有一些其他用到的插件
//隐私访问api检测
apply plugin: 'privacycheck-plugin'
//符号表mapping.txt上传
apply plugin: 'com.x.plugin.symupload'
再回看Android打包流程示意
看看这些插件都发生在哪些阶段?
- com.alibaba.arouter,源文件阶段,为我们生成了一些java文件,形如
ARouter$$Group$$activity.java - com.x.plugin.statistics,class阶段,修改class文件,在某些method中注入代码
- privacycheck-plugin,同上,修改class文件
- com.tencent.vasdolly,apk阶段,多渠道打包,apk签名
- maven-publish,library工程构建后阶段,从class到jar,打包发布到外部仓库
- com.x.plugin.symupload,app工程构建后阶段,上传构建产物mapping.txt
你甚至可以通过插件直接往dex中写入class或者生成dex(某些热修复方案)
回到这段开头的问题
Gradle插件能做什么事情? 答案是无所不能
- 当你觉得模版代码文件太多,想少写一些
- 当你觉得一个个文件检查累死,想批量检查
- 当你觉得一个个文件改代码累死,想批量修改
- 当你觉得三方lib不能满足你需要,想魔改
- 当你觉得流程太多,操作繁琐,头皮发麻,想更加智能一些
- 当你觉得。。。。
想想你的痛点,从编写第一个插件开始
参考
Developing Custom Gradle Plugins