你的压力来源于,无法自律只是假装很努力,现状跟不上欲望,所以你焦虑又恐慌,诸君共勉!
问题
同样我们还是要抛出几个问题:
- 什么是Gradle插件?
- 我们引入插件有几种方式呢?
- 插件该如何去写呢?
真实场景
其实Gradle是帮我们自动化构建的一套工具,Gradle插件我用一直在开发中使用,比如com.android.application,com.android.library,前者标识这是一个APP运行程序,后者标识当前module是一个依赖库,这个就是插件。插件可以给我们提供很多有用的功能,目前我们用插件,主要用来帮助我们进行自动化,比如字节码插桩,生成代码等,接下来我们就看看什么插件是怎么书写的,怎么运行的,我们不搞形式主义,就是告诉你怎么去书写自己的插件。
插件实现方式
-
build.gradle
我们可以在build.gradle书写自己的函数(亦或者是一个Task),完成一个简单的功能,但是这个函数的可见性只有在这个文件可见,如果其他的gradle文件想用,只能引这个文件,比如通过apply方式。
-
单独的module
这个就比较方便了,这个module可以单独发布,其他任何仓库直接引入即可。
-
工程buildSrc
只有在当前工程可见可用
其中在build.gradle文件的方式,这里不展开讲解,很简单,估计大家也或多或少见过或者写过,接下来就详细讲解一下另外两种方式。
buildSrc方式构建插件
构建工程配置
首先创建名为”buildSrc“的文件夹(注意名字不能改变,只能是这个),然后可以本地Sync一下,然后创建一个resources的目录(和java目录同一个级别),然后在resources目录下创建META-INF目录,随后在META-INF目录下创建gradle-plugins目录,最后创建配置文件xxx.properties.(注意这个配置文件的名称也是有说法的,比如这里创建的com.judahao.buildSrc,这个名称是在apply插件的时候用到的,字符串名称是绑定的)
介绍build.gradle文件
// 你需要什么插件就引什么即可
apply plugin: 'java'
apply plugin: 'kotlin'
buildscript {
ext.kotlin_version = '1.3.61'
repositories {
jcenter()
mavenCentral()
}
dependencies {
// 这个就是你用来的三方插件
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
jcenter()
google()
}
dependencies {
// 注意,gradle插件开发需要引下面两个依赖,一个gradle开发的api,一个就是gradle的包
implementation gradleApi()
implementation 'com.android.tools.build:gradle:4.1.0'
// ASM 相关,这个后面讲解ASM字节码插桩再说
implementation 'org.ow2.asm:asm:9.2'
implementation 'org.ow2.asm:asm-util:9.1'
implementation 'org.ow2.asm:asm-commons:9.2'
implementation 'androidx.room:room-compiler:2.3.0'
}
很简单,需要什么就依赖什么,注意依赖gradle的开发api和工具包即可。
创建Plugin
public class BuildSrcPlugin implements Plugin<Project> {
public static final String BUILD_SRC_EXTENSION_NAME = "buildSrc";
public static final String ANDROID_EXTENSION_NAME = "android";
@Override
public void apply(@Nonnull Project project) {
// 注册transform,这里可以先不关注,后面讲ASM插桩会讲到
Objects.requireNonNull(project.getExtensions().findByType(AppExtension.class))
.registerTransform(new LogTransform());
// 获取扩展的配置属性
BuildSrcExtension buildSrcExtension =
project.getExtensions().create(BUILD_SRC_EXTENSION_NAME, BuildSrcExtension.class);
// 这个时候没有值,因为还没有读取完毕配置文件
System.out.println("--- BuildSrc Extension versionCode: " + buildSrcExtension.versionCode);
System.out.println("--- BuildSrc Extension versionName: " + buildSrcExtension.versionName);
System.out.println(buildSrcExtension.versionCode);
// gradle读取配置完毕,注册一个监听
project.afterEvaluate(evaluatedProject -> {
// 获取android打包的扩展配置信息
AppExtension appExtension =
(AppExtension) project.getExtensions().findByName(ANDROID_EXTENSION_NAME);
if (appExtension != null) {
// 获取变体(简单理解就是打出一种类型的包)
DomainObjectSet<ApplicationVariant> applicationVariants =
appExtension.getApplicationVariants();
for (ApplicationVariant applicationVariant : applicationVariants) {
// 如果当前是debug的包,给Debug添加Task
if (applicationVariant.getBuildType().getName().equalsIgnoreCase("debug")) {
// 创建一个自定义的任务
BuildSrcTask packageTask =project.getTasks().create("BuildSrcTask" + applicationVariant.getName(), BuildSrcTask.class);
// 初始化必要属性
packageTask.initPackageTask(applicationVariant, project);
// 投建依赖关系,让自定的Task依赖debug打包的Task,debug打包的Task依赖clean,
// 保证每次运行都clean一下
Task debugTask = applicationVariant.getAssembleProvider().get();
debugTask.dependsOn(project.getTasks().findByName("clean"));
packageTask.dependsOn(debugTask);
}
}
}
});
}
}
TIP:什么是variant变体,其实打包有两种行为,buildType+flavorType,buildType就是我们常说的构建类型,比如debug,release(这两个是默认提供的,当然你是可以修改的,一般来说是不会动的),后者的flavor是干嘛的呢?比如说,我们做中台SDK,提供一个卖货的功能,我需要给ABCD四个APP使用,四个APP需要的卖货的功能基本一致,只有些微的查边,他们的UI主样式也是不一样的(比如有的主色点白的,有的蓝色,有的绿色),这个时候我们就可以用flavor了,四个flavor主要是有区别的代码,我们可以放在四个文件夹,打包的时候我们只需要配置好,flavorType,打包的时候会自己去找对应的目录去打包(和免费版付费版差不多,都可以用flavor)。所以,打包的类型一共有buildType*flavorType种,比如debugA,releaseD。
PS:不要问我为啥不用if-else,要用flavor,不同的颜色,我可以通过if-else(switch)区分啊,之前面试管就这么问我,我懒得掰扯,google提供这么好的工具你不用,你去用if-else。
分析一下代码(Transform先不用管,也是一种Task其实):
- 首先获取扩展,啥是扩展呢?简单,就是属性,Task运行的时候可能会需要参数,这个扩展就是用来写参数的。
- 注册监听,在gradle文件配置读取完毕,gradle之间的依赖关系弄清楚之后,回调,我们在回调中处理自己的逻辑。
- 获取所有的变体,就是打包的类型,我们获取debug类型的打包Task,让这个任务依赖”clean“的task,保证每次打该debug包,都会执行clean,同时我们创建了自己的Task,让自定义的Task依赖debug的打包Task。这样我们每次运行自己的Task,都可以保证先执行clean然后执行打debug最后执行我们的自定义Task。
具体的Task
public class BuildSrcTask extends DefaultTask {
private BaseVariant mVariant;
private Project mProject;
public void initPackageTask(@Nonnull BaseVariant variant, @Nonnull Project project) {
setGroup("pluginStudy");
this.mProject = project;
this.mVariant = variant;
}
@TaskAction
public void runPackageCompleteTask() {
// 获取自定义的扩展
AppExtension appExtension =
(AppExtension) mProject.getExtensions().findByName(BuildSrcPlugin.ANDROID_EXTENSION_NAME);
// 获取输出
for (BaseVariantOutput output : mVariant.getOutputs()) {
File outputFile = output.getOutputFile();
if (outputFile != null) {
// 打印APP信息
System.out.println("# applicationId = " + mVariant.getMergedFlavor().getApplicationId());
System.out.println("# fileName = " + outputFile.getAbsoluteFile());
assert appExtension != null;
System.out.println("# versionName = " + appExtension.getDefaultConfig().getVersionName());
System.out.println("# versionCode = " + appExtension.getDefaultConfig().getVersionCode());
}
}
}
}
TIP:setGroup方法,是可以让你的Task归类吧,方便寻找,如下方截图所示。
- 同样,我们先获取自己的扩展,自己的配置属性对象Model。
- 然后获取变体的输出,并打印,很简单,我们这个时候其实是可以拿到产物包了,比如你想上传,然后通过机器人告诉你包打好了,都行,随意老铁。
自定义扩展
public class BuildSrcExtension {
public int versionCode;
public String versionName;
// public也不行,要get set方法
public void setVersionCode(int versionCode) {
this.versionCode = versionCode;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
}
}
就是一个model对象,比如可以在里面配置versionCode和versionName,很灵活,这里就是做一个演示,你当然可以扩展里面放对象对吧,有兴趣可以自己了解下,主要就是让我们存放参数的。注意注意,一定要有set方法,我发现在Java里面,就算是public的也会运行报错,扩展在运行的时候会读取你在gradle文件的配置,给对象复制,只会调用set方法。
自定义插件使用
首先再次说明一下,buildSrc是工程可见的,所以不需要再根目录的build.gradle配置依赖。
我们只需要在想要使用的module引入即可,首先依赖plugin(这里是plugins,以前是通过apply的,gradle升级会有所变化,不用纠结),然后我们就可以使用扩展了,这个名称也是自己定义的,属性也是我们定义的,当然扩展可以包括一个扩展,映射到Extension,就是对象里面包括两另外一个对象,有兴趣自己了解下,很简单。很简单,这样buildSrc的插件就完成了。
单独module构建插件
构建工程配置
首先setting.gradle需要把这个module引进来(对了我看到网上说buildSrc也需要引入,但是我本地发现引入报错,不用引入,不知道是不是网上的是不是很久之前的了),其余的配置是和BuildSrc一致的。
介绍build.gradle文件
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'maven'
apply plugin: 'maven-publish'
buildscript {
ext.kotlin_version = '1.3.61'
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
jcenter()
google()
}
dependencies {
implementation gradleApi()
implementation 'com.android.tools.build:gradle:4.1.0'
}
// Maven,这个脚本用来发布插件的,这里模拟的是本地发布
// 如果你一来了本地module的插件,要测试,注意程序运行起来之前,要先发布本地插件不然跑不过,因为找不到
uploadArchives {
repositories {
mavenDeployer {
//提交到远程服务器:
//repository(url: "http://www.xxx.com/repos") {
//authentication(userName: "admin", password: "admin")
//}
//本地的Maven地址设置为项目下的/localMaven的文件夹
repository(url: uri('/Users/judahao/Downloads/'))
}
}
}
//uploadArchives {
// repositories {
// mavenDeployer {
// pom.groupId = 'your.package.name'
// pom.artifactId = "your_plugin_name"
// pom.version = '1.0.0'
// repository(url: uri('../repo'))
// }
// }
//}
// Maven-publish
publishing {
publications {
mavenJava(MavenPublication) {
groupId 'com.judahao.module'
artifactId 'plugin'
version '1.0.0'
from components.java
}
}
repositories {
maven {
url uri('/Users/judahao/Downloads/')
}
}
}
很简单,需要什么就依赖什么,注意依赖gradle的开发api和工具包即可,但是注意一下,本地是要测试module的插件,本地依赖添加完毕之后,记得发布一下,不然你依赖有了,因为这个插件不像BuildSrc是工程可见的,插件远端找不到,不就会运行失败了吗。
创建Plugin
public class ModelPlugin implements Plugin<Project> {
public static final String BUILD_SRC_EXTENSION_NAME = "module";
public static final String ANDROID_EXTENSION_NAME = "android";
@Override
public void apply(@Nonnull Project project) {
ModuleExtension buildSrcExtension =
project.getExtensions().create(BUILD_SRC_EXTENSION_NAME, ModuleExtension.class);
// 这个时候没有值
System.out.println("--- Module Extension versionCode: " + buildSrcExtension.versionCode);
System.out.println("--- Module Extension versionName: " + buildSrcExtension.versionName);
System.out.println(buildSrcExtension.versionCode);
// gradle读取配置完毕
project.afterEvaluate(evaluatedProject -> {
AppExtension appExtension =
(AppExtension) project.getExtensions().findByName(ANDROID_EXTENSION_NAME);
if (appExtension != null) {
// appExtension.getApplicationVariants().all(applicationVariant -> {
// applicationVariant.getOutputs().all(baseVariantOutput -> {
// System.out.println(baseVariantOutput.getOutputFile());
// });
// });
DomainObjectSet<ApplicationVariant> applicationVariants =
appExtension.getApplicationVariants();
for (ApplicationVariant applicationVariant : applicationVariants) {
// 给Debug添加Task
if (applicationVariant.getBuildType().getName().equalsIgnoreCase("debug")) {
// 创建任务
ModuleTask packageTask =
project.getTasks()
.create("ModuleTask" + applicationVariant.getName(), ModuleTask.class);
packageTask.initPackageTask(applicationVariant, project);
// 投建依赖关系
Task debugTask = applicationVariant.getAssembleProvider().get();
debugTask.dependsOn(project.getTasks().findByName("clean"));
packageTask.dependsOn(debugTask);
}
}
}
});
}
}
不过多介绍,和BuildSrc的是一致的,前面有讲过。
具体的Task
public class ModuleTask extends DefaultTask {
private BaseVariant mVariant;
private Project mProject;
public void initPackageTask(@Nonnull BaseVariant variant, @Nonnull Project project) {
setGroup("pluginStudy");
this.mProject = project;
this.mVariant = variant;
}
@TaskAction
public void runPackageCompleteTask() {
AppExtension appExtension =
(AppExtension) mProject.getExtensions().findByName(ModelPlugin.ANDROID_EXTENSION_NAME);
// 获取输出
for (BaseVariantOutput output : mVariant.getOutputs()) {
File outputFile = output.getOutputFile();
if (outputFile != null) {
// 打印APP信息
System.out.println("# applicationId = " + mVariant.getMergedFlavor().getApplicationId());
System.out.println("# fileName = " + outputFile.getAbsoluteFile());
assert appExtension != null;
System.out.println("# versionName = " + appExtension.getDefaultConfig().getVersionName());
System.out.println("# versionCode = " + appExtension.getDefaultConfig().getVersionCode());
}
}
}
}
和buildSrc同理,一样的。
自定义扩展
public class ModuleExtension {
public int versionCode;
public String versionName;
// public也不行,要get set方法
public void setVersionCode(int versionCode) {
this.versionCode = versionCode;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
}
和buildSrc同理。
自定义插件使用
首先发布本地插件,保证程序运行远端可以找到。
添加插件的依赖,虽然是本地发布,但是模拟的是网络发布,没有什么区别。上面的插件,就是项目默认帮我们默认生成的插件依赖。
你需要使用的module直接配置插件依赖即可,齐活。
再次说一下,buildSrc是项目可见的,所以不需要再项目根目录的classPath配置东西,但是module是网上发布的,所以需要配置路径依赖。
小结
总体来说,插件的常用的两种方式已经叙述完毕,如果是项目用,我们在buildSrc写就可以了,但是追求复用,当时是单独的module去书写插件,语言无所谓,java,kotlin,groovy都是可以的看你习惯。
上面的问题估计也都解决了,大家需要知道插件的书写规范,怎么配置就可以了。
- BuildSrc方式
- 根目录创建BuildSrc目录
- 创建main/resources/META-INT/gradle-plugins/xxx.properties文件,这个xxx配置文件名称是和apply ’xxx‘,名字保持一致的注意。
- 开发自己的插件,实现自己的功能。
- 最后在配置文件xxx.properties中书写’implementation-class=包名.Plugin‘,就是你目标插件的路径,把插件路径写进去。
- 单独Module方式
- 创建一个单独的Module,并在setting.gradle文件中引入
- 创建main/resources/META-INT/gradle-plugins/xxx.properties文件,这个xxx配置文件名称是和apply ’xxx‘,名字保持一致的注意。
- 开发自己的插件,实现自己的功能。
- 配置文件xxx.properties中书写’implementation-class=包名.Plugin‘,就是你目标插件的路径,把插件路径写进去。
- 完毕之后,发布你的目标插件到Maven中,你也可以到后台看看,你是不是成功发布。(账号密码是阿德细节,就别问我了bro)
- 到项目的根目录的build.gradle,查找classPath,配置好你的发布插件依赖,包括groupId,artifactId以及版本号。
- 然后在你需要的使用的module依赖,apply’xxx‘即可。