Gradle Plugin
本质就是把独立的和业务代码无关的,各个项目、子项目通用的逻辑抽离出来进行封装。今天我们从零开始,从最简单的Demo逐渐深入,来演示下gradle的插件如何编写。
入门Demo:最简单的plugin之打印Hello World
首先我们写一个最简单的Android gradle plugin,就在我们APP module,主module的gradle文件里写一个打印helloWorld的插件,非常简单,如下所示:
class PluginDemo implements Plugin<Project> {
@Override
void apply(Project target) {
println "Hello plugin"
}
}
apply plugin: PluginDemo
实现了Plugin接口,实现apply方法即可,然后我们make下项目,就可以看到Hello plugin的打印了。在print里面我们是写死的字符串,改成变量的写法如下:
class PluginDemo implements Plugin<Project> {
@Override
void apply(Project target) {
def extension = target.extensions.create('buder', ExtensionDemo)
target.afterEvaluate {
println "watch ${extension.name}"
}
}
}
class ExtensionDemo {
def name = '庆余年'
}
apply plugin: PluginDemo
但是这么写是无法动态配置的,例如我们gradle配置的Android版本之类的信息是可以手动填写,可以自己动态配置的:
android {
compileSdkVersion 28
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.plugin"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
升级demo:可以像上面versionCode "1"、versionName "1.0"这样可配置的插件写法
步骤一:在项目中创建一个新的module,可以选择一个java module,这个module的名字必须是buildSrc;
步骤二:然后在main目录下面创建一个resources的文件夹,里面再创建一个META-INF的文件夹,里面再创建一个gradle-plugins的文件夹,最后创建一个xxx.properties的文件(xxx文件名可以随意取);再新建groovy目录用于编写插件逻辑,最终如下图:
上面module以及resources目录的创建就是我们编写插件的基础,其实做法很简单,就是需要创建一个符合Android studio可以为我们编译为插件的一个目录结构,只是这个目录结构需要我们自己创建罢了,要是哪天AS可以支持创建项目或者创建module那样一键创建的话,这两个步骤就不需要我们自己手动创建了。下面我们要做的事就是把刚刚那个入门demo的代码,拆分到不同文件中,而非直接写在主工程的build.gradle文件内。
步骤三:拆分到升级demo的PluginDemo.groovy 以及 ExtensionDemo.groovy中
1.在代码目录下com.example.plugindemo,新建一个PluginDemo.groovy的文件,
class PluginDemo implements Plugin<Project>{
@Override
void apply(Project project) {
def extension = project.extensions.create('plugindemo', ExtensionDemo)
project.afterEvaluate {
println "watch ${extension.name}"
}
}
}
这里注意:第一个参数是xxx.properties文件的文件名称
2.在代码目录下com.example.plugindemo,新建一个ExtensionDemo.groovy的文件,
class ExtensionDemo {
def name = "kkkk"
}
3.在resources/META-INF/gradle-plugins目录下,新建一个plugindemo.properties的文件,
implementation-class=com.example.plugindemo.PluginDemo
4.在主工程就是app工程的build.gradle文件中使用,
apply plugin: 'plugindemo'
plugindemo {
name '《新世界》'
}
这里注意:这里的plugindemo就是之前的xxx.properties的文件名,这里起的是PluginDemo,这个名字可以随意起,但是一定要和xxx.properties的文件名保持一致。
上面的1--3步就是将原来入门demo的那几行代码拆分到Android studio所能识别到的文件中去,步骤4就是使用,达到和我们使用versionName "1.0" 一样的效果,make项目后就可以打印出:watch 《新世界》。
上述过程的工程目录:
需要注意的几点:
1.plugindemo.properties里面的值就是PluginDemo.groovy的类的全限定名,
2.plugindemo.properties这个文件的文件名是可以随便起的,比如您起为abcd.properties
3.resources/META-INF/gradle-plugins/*.properties 中的 * 是插件的名称,例如*.properties 是 abcd.properties,最终在应用插件时的代码就是 apply plugin: 'abcd',在使用的时候,下面的就是您的这个properties的文件名必须保持一致
apply plugin: 'abcd'
abcd {
name '新世界'
}
而且注意,PluginDemo.groovy中的extension创建函数里的第一个参数也是abcd,
def extension = project.extensions.create('plugindemo', ExtensionDemo)
4.关于 buildSrc ⽬目录
• 这是 gradle 的⼀一个特殊⽬目录,这个⽬目录的 build.gradle 会⾃自动被执⾏行行,即使不不配配置进settings.gradle
• buildSrc 的执⾏行行早于任何⼀一个 project,也早于 settings.gradle。它是⼀一个独⽴立的存在
• buildSrc 所配置出来的 Plugin 会被⾃自动添加到编译过程中的每⼀一个 project 的 classpath,因此它们才可以直接使⽤用 apply plugin: 'xxx' 的⽅方式来便便捷应⽤用这些 plugin
• settings.gradle 中如果配置了了 ':buildSrc' ,buildSrc ⽬目录就会被当做是⼦子 Project ,因此会被执⾏行行两遍。所以在 settings.gradle ⾥里里⾯面应该删掉 ':buildSrc' 的配置
添加Transform的Demo:
transform是什么:Transform是由 Android 提供的,在项⽬目构建过程中把编译后的⽂文件(jar ⽂文件和 class ⽂文件)添加自定义的中间处理过程的工具
步骤一:因为需要编写gradle代码,所以在buildSrc项目的gradle文件中需要加入依赖,才能使用gradle相关的API
repositories {
google()
jcenter()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.tools.build:gradle:3.5.3'
}
步骤二:将之前的自定义的apply plugin放到后面,因为你添加transform,它的初始化太靠前了,否则会报错:
android {
compileSdkVersion 28
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.plugin"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
apply plugin: 'plugindemo'
plugindemo {
name '新世界'
}
dependencies {
步骤三:编写TransformDemo.groovy文件:
package com.example.plugindemo
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInvocation
import com.android.build.gradle.internal.pipeline.TransformManager;
public class TransformDemo extends Transform{
/**
* 所添加的task名称
* @return
*/
@Override
String getName() {
return "buderTransform"
}
/**
* 可以改class文件、jar包、资源文件等
* 这里我们要是需要修改字节码,就为 TransformManager.CONTENT_CLASS
* @return
*/
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
/**
* 修改的范围
* 这里为整个项目范围
* @return
*/
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
//这里就是我们的class以及jar文件的输入和输出,输出的话就是我们做了手脚后的class文件
def inputs = transformInvocation.inputs
def outputProvider = transformInvocation.outputProvider
inputs.each {
it.jarInputs.each {
File dest = outputProvider.getContentLocation(it.name, it.contentTypes, it.scopes, Format.JAR)
FileUtils.copyFile(it.file, dest)
}
it.directoryInputs.each {
File dest = outputProvider.getContentLocation(it.name, it.contentTypes, it.scopes, Format.DIRECTORY)
FileUtils.copyDirectory(it.file, dest)
}
}
}
}
步骤四:在PluginDemo.groovy中使用:
import com.android.build.gradle.BaseExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class PluginDemo implements Plugin<Project>{
@Override
void apply(Project project) {
def extension = project.extensions.create('plugindemo', ExtensionDemo)
project.afterEvaluate {
println "watch ${extension.name}"
}
def transform = new TransformDemo()
def baseExtension = project.extensions.getByType(BaseExtension)
// println "bootClassPath = ${baseExtension.bootClasspath} "
baseExtension.registerTransform(transform)
}
}
此项目地址:https://github.com/buder-cp/DesignPattern/tree/master/buder_coder_plugin