可使用任何您喜欢的语言来实现Gradle插件,前提是该实现源码最终被编译为JVM字节码html
class GreetingPlugin implements Plugin<Project>{
@Override
void apply(Project target) {
target.task("hello"){
doLast {
println("Hello from the GreetingPlugin")
}
}
}
}
apply plugin: GreetingPlugin
复制代码
gradle -q hello
输出 Hello from the GreetingPlugin
复制代码
//build.gradle 自定义插件
class GreetingPlugin2 implements Plugin<Project> {
void apply(Project project) {
//获取配置
def extension = project.extensions.create('greeting', GreetingPluginExtension)
project.task('hello2') { //名字为 hello 的task
doLast {
//获取 extension 配置信息
println "${extension.message} from ${extension.greeter}"
}
}
}
}
//引入插件
apply plugin: GreetingPlugin2
// 配置 extension
greeting{
greeter = 'Gradle'
message = "Hi"
}
复制代码
gradle -q hello2
输出 > Task :app:hello2
Hi from Gradle
复制代码
注意:记得删除 settings.gradle buildSrc配置,不然会报'buildSrc' cannot be used as a project name as it is a reserved name 错误java
import org.gradle.api.Plugin
import org.gradle.api.Project
class TestPlugin implements Plugin<Project>{
@Override
void apply(Project project) {
println("====== buildSrc TestPlugin Plugin加载===========")
//执行自定义的 task
project.task("TestPlugin"){
doLast {
println("buildSrc TestPlugin task 任务执行")
}
}
}
}
复制代码
apply plugin: TestPlugin
复制代码
## 须要引入的插件
apply plugin: 'groovy'
apply plugin: 'maven'
//gradle 开发 sdk 依赖
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation gradleApi()
implementation localGroovy()
implementation 'com.android.tools.build:gradle:4.0.0'
}
//设置插件 group 和版本号 在项目中使用的时候会用到
group='com.maoasm.plugin'
version='1.0.0'
uploadArchives {
repositories {
mavenDeployer {
//本地的Maven地址设置
repository(url: uri('../asm_test_repo'))
}
}
}
复制代码
package com.maoasm.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class MainPlugin implements Plugin<Project>{
@Override
void apply(Project project) {
println("======自定义MainPlugin加载===========")
//执行自定义的 task
project.task("TestPluginTask"){
doLast {
println("自定义插件task 任务执行")
}
}
}
}
复制代码
## 本文件名称就是插件 apply 名称
implementation-class=com.maoasm.plugin.MainPlugin
复制代码
注意,上图中的目录层级须要一一对应建立目录,保证父子目录,不然 apply 插件会出现如下错误android
Plugin with id 'XXXXX' not found
复制代码
//引入自定义插件
apply plugin: 'com.mao.asmtest'
buildscript {
repositories {
google()
jcenter()
//自定义插件maven地址,这里以本地目录做为仓库地址目录
maven { url '../asm_test_repo' }
}
dependencies {
//加载自定义插件 group + module + version
classpath 'com.maoasm.plugin:asm_test_plugin:1.0.0'
}
}
复制代码
apply plugin: 'groovy'
apply plugin: 'maven'
apply plugin: 'kotlin'
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation gradleApi()
implementation localGroovy()
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.android.tools.build:gradle:4.0.0'
}
group='com.mao.asm'
version='1.0.1'
uploadArchives {
repositories {
mavenDeployer {
//本地的Maven地址设置
repository(url: uri('../asm_test_repo'))
}
}
}
复制代码
前面咱们写的代码在编译的时候可使用 project.task 来指定编译过程要作什么操做,要完成自动插桩,首先就要找到项目中对应的.class文件修改,编译过程当中 compileJava 这个task 将Java文件变成 .class ,若是编写一个 Transform 注册后 gradle 会将其看作是一个Task,并在 compileJava task 以后执行,Transform 接收这些 class 文件在执行插桩这就是这个自定义插件实现思路。git
详情请看个人另外一篇文章深刻了解 Gradlegithub
/**
* @Description:
* @author maoqitian
* @date 2020/11/13 0013 17:01
*/
class MainPlugin :Plugin<Project> {
override fun apply(project: Project) {
println("======自定义MainPlugin加载===========")
}
}
复制代码
/**
* @Description: Transform 能够被看做是 Gradle 在编译项目时的一个 task
* @author maoqitian
* @date 2020/11/13 0013 17:03
*/
class ASMLifecycleTransform :Transform() {
/**
* 设置咱们自定义的 Transform 对应的 Task 名称。Gradle 在编译的时候,会将这个名称显示在控制台上
* @return String
*/
override fun getName(): String = "ASMLifecycleTransform111"
/**
* 在项目中会有各类各样格式的文件,该方法能够设置 Transform 接收的文件类型
* 具体取值范围
* CONTENT_CLASS .class 文件
* CONTENT_JARS jar 包
* CONTENT_RESOURCES 资源 包含 java 文件
* CONTENT_NATIVE_LIBS native lib
* CONTENT_DEX dex 文件
* CONTENT_DEX_WITH_RESOURCES dex 文件
* @return
*/
override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> = TransformManager.CONTENT_CLASS
/**
* 定义 Transform 检索的范围
* PROJECT 只检索项目内容
* SUB_PROJECTS 只检索子项目内容
* EXTERNAL_LIBRARIES 只有外部库
* TESTED_CODE 由当前变量测试的代码,包括依赖项
* PROVIDED_ONLY 仅提供的本地或远程依赖项
* @return
*/
//只检索项目内容
override fun getScopes(): MutableSet<in QualifiedContent.Scope> = TransformManager.PROJECT_ONLY
/**
* 表示当前 Transform 是否支持增量编译 返回 true 标识支持 目前测试插件不须要
* @return Boolean
*/
override fun isIncremental(): Boolean = false
//对项目 class 检索操做
override fun transform(transformInvocation: TransformInvocation) {
println("transform 方法调用")
//获取全部 输入 文件集合
val transformInputs = transformInvocation.inputs
val transformOutputProvider = transformInvocation.outputProvider
transformOutputProvider?.deleteAll()
transformInputs.forEach { transformInput ->
// Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.appcompat.R$drawable" on path 问题
// gradle 3.6.0以上R类不会转为.class文件而会转成jar,所以在Transform实现中须要单独拷贝,TransformInvocation.inputs.jarInputs
// jar 文件处理
transformInput.jarInputs.forEach { jarInput ->
val file = jarInput.file
println("find jar input:$file.name")
val dest = transformOutputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
FileUtils.copyFile(file, dest)
}
//源码文件处理
//directoryInputs表明着以源码方式参与项目编译的全部目录结构及其目录下的源码文件
transformInput.directoryInputs.forEach { directoryInput ->
//遍历全部文件和文件夹 找到 class 结尾文件
directoryInput.file.walkTopDown()
.filter { it.isFile }
.filter { it.extension == "class" }
.forEach { file ->
println("find class file:${file.name}")
val classReader = ClassReader(file.readBytes())
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
//字节码插桩处理
//2.class 读取传入 ASM visitor
val asmLifecycleClassVisitor = ASMLifecycleClassVisitor(classWriter)
//3.经过ClassVisitor api 处理
classReader.accept(asmLifecycleClassVisitor,ClassReader.EXPAND_FRAMES)
//4.处理修改为功的字节码
val bytes = classWriter.toByteArray()
//写回文件中
val fos = FileOutputStream(file.path)
fos.write(bytes)
fos.close()
}
//复制到对应目录
val dest = transformOutputProvider.getContentLocation(directoryInput.name,directoryInput.contentTypes,directoryInput.scopes, Format.DIRECTORY)
FileUtils.copyDirectory(directoryInput.file,dest)
}
}
}
}
复制代码
字节码操做已有现成的框架ASM,官方文档后端
在插件 build.gradle 中依赖引入api
implementation 'org.ow2.asm:asm:9.0'
implementation 'org.ow2.asm:asm-commons:9.0'
复制代码
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
复制代码
ClassVisitor:用来解析字节码文件结构的,当解析到某些特定结构时(好比类、方法、变量、),则调用内部相应的 FieldVisitor 或者 MethodVisitor 的方法,进一步解析或者能够修改 字节码文件的内容帮助完成字节码插桩功能数组
更多字节码结果组成内容可看个人另外一篇文章 从新认识Java字节码markdown
/**
* @Description: class Visitor
* @author maoqitian
* @date 2020/11/13 0013 11:47
*/
class ASMLifecycleClassVisitor(classVisitor: ClassVisitor?) : ClassVisitor(Opcodes.ASM5, classVisitor) {
private var className:String? = null
private var superName:String? = null
override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String>?) {
super.visit(version, access, name, signature, superName, interfaces)
this.className = name
this.superName = superName
}
override fun visitMethod(access: Int, name: String, descriptor: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor {
val methodVisitor = cv.visitMethod(access,name,descriptor,signature,exceptions)
//找到 androidX 包下的 Activity 类
if (superName == "androidx/appcompat/app/AppCompatActivity"){
//对 onCreate 方法处理 加入日志打印
if (name.startsWith("onCreate")){
println("do ASM ClassVisitor visitMethod onCreate")
return ASMLifecycleMethodVisitor(methodVisitor, className!!, name)
}
if (name.startsWith("onStart")){
println("do ASM ClassVisitor visitMethod onStart")
return ASMLifecycleMethodVisitor(methodVisitor, className!!, name)
}
if (name.startsWith("onResume")){
println("do ASM ClassVisitor visitMethod onResume")
return ASMLifecycleMethodVisitor(methodVisitor, className!!, name)
}
if (name.startsWith("onRestart")){
println("do ASM ClassVisitor visitMethod onRestart")
return ASMLifecycleMethodVisitor(methodVisitor, className!!, name)
}
if (name.startsWith("onPause")){
println("do ASM ClassVisitor visitMethod onPause")
return ASMLifecycleMethodVisitor(methodVisitor, className!!, name)
}
if (name.startsWith("onStop")){
println("do ASM ClassVisitor visitMethod onStop")
return ASMLifecycleMethodVisitor(methodVisitor, className!!, name)
}
if (name.startsWith("onDestroy")){
println("do ASM ClassVisitor visitMethod onDestroy")
return ASMLifecycleMethodVisitor(methodVisitor, className!!, name)
}
}
return methodVisitor
}
override fun visitEnd() {
super.visitEnd()
}
}
复制代码
/**
* @Description: 方法 Method Visitor 为每一个方法加入日志打印
* @author maoqitian
* @date 2020/11/13 0013 11:47
*/
class ASMLifecycleMethodVisitor(private val methodVisitor:MethodVisitor, private val className:String,private val methodName:String) : MethodVisitor(Opcodes.ASM5, methodVisitor) {
//在方法执行前插入日志字节码
override fun visitCode() {
super.visitCode()
println("do ASMLifecycleMethodVisitor visitCode method......")
methodVisitor.visitLdcInsn("毛麒添")
methodVisitor.visitLdcInsn("$className -> $methodName")
//字节码 插入方法 日志
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false)
methodVisitor.visitInsn(Opcodes.POP)
}
override fun visitEnd() {
super.visitEnd()
}
}
复制代码
gradlew assembleDebug -Dorg.gradle.daemon=false -Dorg.gradle.debug=true
复制代码