可使用任何您喜歡的語言來實現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
複製代碼