手写agp8自定义插件,用ASM实现路由跳转

module lib_router:

主要看三个类:

ConstRouterPath

复制代码
object ConstRouterPath {
    const val APP_MAIN = "/app/main"
    const val LIB1_1 = "/mylibrary/activity1"
    const val LIB1_2 = "/mylibrary/activity2"

    const val LIB2_1 = "/mylibrary2/activity1"
    const val LIB2_2 = "/mylibrary2/activity2"
}

RouterPath

复制代码
package com.example.router

@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class RouterPath(
    val path: String = "" //路由表中的path
)

RouterApi

复制代码
package com.example.router

import android.content.Context
import android.content.Intent
import android.util.Log

object RouterApi {
    /**
     * 存储跳转路由表
     */
    private val routerPathMap = mutableMapOf<String, String>()

    /**
     * 注册跳转路由
     */
    private fun addRouterPath(key: String?, path: String?) {
        if (key != null && path != null) {
            routerPathMap[key] = path
        }
    }

    /**
     * 页面跳转
     */
    fun routerPath(context: Context, routerTargetPath: String) {
        routerPathMap[routerTargetPath]?.takeIf {
            it.isNotEmpty()
        }?.run {
            context.startActivity(Intent(context, Class.forName(this)))
        }
    }

    /**
     * 打印routerPathMap
     */
    fun printRouterPathMap() {
        routerPathMap.forEach { entry ->
            Log.e("RouterApi", "routerPathMap entry -> $entry")
        }
    }
}

module custom_plugin:

这个里面实现了asm插件代码逻辑

复制代码
build.gradle.kts
复制代码
plugins {
    id("java-library")
    alias(libs.plugins.jetbrains.kotlin.jvm)

    id("java-gradle-plugin") //会自动引入java-library、gradleApi()
    //id("org.jetbrains.kotlin.jvm") //支持kotlin编写插件

    id("maven-publish") //发布到maven
}
java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
    compilerOptions {
        jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
    }
}
gradlePlugin {
    plugins{
        create("test2Plugin"){
            group = "com.example.custom"
            version = "0.0.1"
            id = "com.example.custom.test2"
            implementationClass = "com.example.plugin2.PluginTest2"
        }
    }
}
publishing{
    repositories{
        maven {
            url = uri("../custom_plugin_repo")
        }
    }
}
dependencies {
    implementation("com.android.tools.build:gradle:8.1.2")
    implementation("org.ow2.asm:asm:9.2")
    implementation("org.ow2.asm:asm-commons:9.2")
}

RouterMVisitor

复制代码
package com.example.plugin2

import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.commons.AdviceAdapter

class RouterMVisitor(
    api: Int,
    methodVisitor: MethodVisitor?,
    access: Int,
    name: String?,
    descriptor: String?,
    private val annotationPathMap: HashMap<String?, String?>?,
) : AdviceAdapter(api, methodVisitor, access, name, descriptor) {

    override fun onMethodExit(opcode: Int) {
        super.onMethodExit(opcode)

        annotationPathMap?.forEach { entry ->
            mv.visitFieldInsn(
                GETSTATIC,
                "com/example/router/RouterApi",
                "INSTANCE",
                "Lcom/example/router/RouterApi;"
            )
            mv.visitLdcInsn(entry.key)
            mv.visitLdcInsn(entry.value)
            mv.visitMethodInsn(
                INVOKEVIRTUAL,
                "com/example/router/RouterApi",
                "addRouterPath",
                "(Ljava/lang/String;Ljava/lang/String;)V",
                false
            )
            println("PluginTest2 HuiRouterApi -> addRouterPath插入:$entry")
        }
    }
}

RouterCVisitor

复制代码
package com.example.plugin2

import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes

class RouterCVisitor(
    cv: ClassVisitor,
    private val annotationPathMap: HashMap<String?, String?>?,
) : ClassVisitor(Opcodes.ASM9, cv) {
    override fun visitMethod(
        access: Int,
        name: String?,
        desc: String?,
        signature: String?,
        exceptions: Array<out String?>?
    ): MethodVisitor? {
        val methodVisitor = super.visitMethod(access, name, desc, signature, exceptions)
        if (name == "<clinit>" && desc == "()V") {
            println("RouterApi的静态代码块执行了")
            return RouterMVisitor(
                Opcodes.ASM9,
                methodVisitor,
                access,
                name,
                desc,
                annotationPathMap
            )
        }
        return methodVisitor
    }
}

RouterTask

复制代码
package com.example.plugin2

import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.ClassNode
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty

abstract class RouterTask : DefaultTask(){
    /**
     * 所有的jar文件输入信息
     */
    @get:InputFiles
    abstract val allJars: ListProperty<RegularFile>

    /**
     * 所有的class文件输入信息
     */
    @get:InputFiles
    abstract val allDirectories: ListProperty<Directory>

    /**
     * 经过插桩修改后的输出信息
     */
    @get:OutputFile
    abstract val output: RegularFileProperty

    /**
     * 注册带有HuiRouterPath注解的类
     */
    private val annotationPathMap = HashMap<String?, String?>()

    /**
     * HuiRouterApi的class对应Jar包文件
     */
    private var routerApiJarFile: File? = null

    @TaskAction
    fun taskAction() {
        println("---PluginTest2 abc---taskAction")
        //输出到output的流
        val jarOutput = JarOutputStream(
            BufferedOutputStream(FileOutputStream(output.get().asFile))
        )

        //遍历扫描class
        allDirectories.get().forEach { directory ->
            println("---PluginTest2 abc---taskAction directory=${directory.asFile.absolutePath}")
            directory.asFile.walk().forEach { file ->
                if (file.isFile) {
                    println("---PluginTest2 abc---taskAction file=${file.absolutePath}")
                    if (file.absolutePath.endsWith(".class")) {
                        scanAnnotationClass(file.inputStream())
                    }
                    val relativePath = directory.asFile.toURI().relativize(file.toURI()).path
                    jarOutput.putNextEntry(
                        JarEntry(relativePath.replace(File.separatorChar, '/'))
                    )
                    file.inputStream().use { inputStream ->
                        inputStream.copyTo(jarOutput)
                    }
                    jarOutput.closeEntry()
                }
            }
        }

        //遍历扫描jar
        allJars.get().forEach { jarInputFile ->
            val jarFile = JarFile(jarInputFile.asFile)
            jarFile.entries().iterator().forEach { jarEntry ->
                //过滤掉非class文件,并去除重复无效的META-INF文件
                if (jarEntry.name.endsWith(".class") && !jarEntry.name.contains("META-INF")) {
                    if (jarEntry.name.equals("com/example/router/RouterApi.class")) {
                        println("---PluginTest2 abc---taskAction jar=RouterApi.class")
                        routerApiJarFile = jarInputFile.asFile
                    } else {
                        scanAnnotationClass(jarFile.getInputStream(jarEntry))
                        jarOutput.putNextEntry(JarEntry(jarEntry.name))
                        jarFile.getInputStream(jarEntry).use {
                            it.copyTo(jarOutput)
                        }
                        jarOutput.closeEntry()
                    }
                }
            }
            jarFile.close()
        }

        //对HuiRouterApi进行插桩修改,添加收集到的路由信息
        routerApiJarFile?.let { transformJar(it, jarOutput) }

        //关闭输出流
        jarOutput.close()
    }

    /**
     * 扫描有目标注解的类
     *
     * @param inputStream
     */
    private fun scanAnnotationClass(inputStream: InputStream) {
        val classReader = ClassReader(inputStream)
        val classNode = ClassNode()
        classReader.accept(classNode, ClassReader.EXPAND_FRAMES)
        val annotations = classNode.invisibleAnnotations //获取声明的所有注解
        //println("---PluginTest2 abc---taskAction scanAnnotationClass annotations=${annotations}")
        if (annotations != null && annotations.isNotEmpty()) {
            annotations.forEach { aNode ->

                //println("---PluginTest2 abc---taskAction scanAnnotationClass aNode.desc=${aNode.desc}---${aNode}")
                if ("Lcom/example/router/RouterPath;" == aNode.desc) {
                    println("---PluginTest2 abc---taskAction scanAnnotationClass classNode.name=${classNode.name}---${aNode}")
                    var pathKey = classNode.name
                    if (aNode.values != null && aNode.values.size > 1) {
                        pathKey = aNode.values[1] as? String
                        println("---PluginTest2 abc---taskAction scanAnnotationClass aNode.values[1]=${aNode.values[1]}")
                    }
                    annotationPathMap[pathKey] = classNode.name.replace("/", ".")
                }
            }
        }
        inputStream.close()
    }

    /**
     * 遍历并修改目标class
     */
    private fun transformJar(inputJar: File, jarOutput: JarOutputStream) {
        val jarFile = JarFile(inputJar)
        jarFile.entries().iterator().forEach { jarEntry ->
            if (jarEntry.name.equals("com/example/router/RouterApi.class")) {
                jarOutput.putNextEntry(JarEntry(jarEntry.name))
                asmTransform(jarFile.getInputStream(jarEntry)).inputStream().use {
                    it.copyTo(jarOutput)
                }
                jarOutput.closeEntry()
            }
        }
        jarFile.close()
    }

    /**
     * 对目标class进行修改
     */
    private fun asmTransform(inputStream: InputStream): ByteArray {
        val cr = ClassReader(inputStream)
        val cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
        val classVisitor = RouterCVisitor(cw, annotationPathMap)
        cr.accept(classVisitor, ClassReader.EXPAND_FRAMES)
        return cw.toByteArray()
    }
}

PluginTest2

复制代码
package com.example.plugin2

import com.android.build.api.artifact.ScopedArtifact
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ScopedArtifacts
import org.gradle.api.Plugin
import org.gradle.api.Project

class PluginTest2  : Plugin<Project> {
    override fun apply(target: Project) {
        println("---PluginTest2 abc---")

        val androidComponents = target.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.onVariants { variant ->

            println("---PluginTest2 abc---variant")
            val taskProvider = target.tasks.register(//注册HuiRouterTask任务
                "${variant.name}RouterTask", RouterTask::class.java
            )

            variant.artifacts.forScope(ScopedArtifacts.Scope.ALL) //扫描所有class
                .use(taskProvider)
                .toTransform(
                    type = ScopedArtifact.CLASSES,
                    inputJars = RouterTask::allJars,
                    inputDirectories = RouterTask::allDirectories,
                    into = RouterTask::output
                )
        }
    }
}

项目根目录下:

复制代码
settings.gradle.kts
复制代码
pluginManagement {
    repositories {
        maven { url=uri("https://maven.aliyun.com/repository/google") }
        maven { url=uri("https://maven.aliyun.com/repository/releases") }
        maven { url=uri("https://maven.aliyun.com/repository/central") }
        maven { url=uri("https://maven.aliyun.com/repository/public") }
        maven { url=uri("https://maven.aliyun.com/repository/gradle-plugin") }
        maven { url=uri("https://maven.aliyun.com/repository/apache-snapshots") }
        maven { url=uri("https://maven.aliyun.com/nexus/content/groups/public/")}

        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()

        maven {
            url = uri("./custom_plugin_repo")
        }
    }
}

........................
复制代码
build.gradle.kts
复制代码
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
    alias(libs.plugins.kotlin.compose) apply false
    alias(libs.plugins.android.library) apply false
    alias(libs.plugins.jetbrains.kotlin.jvm) apply false


    id("com.example.custom.test2") version "0.0.1" apply false
}

module mylibrary:

复制代码
build.gradle.kts
复制代码
........

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)

    implementation(project(path = ":lib_router"))
}

ActivityLib1

复制代码
package com.example.mylibrary

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.router.ConstRouterPath
import com.example.router.RouterPath

@RouterPath(ConstRouterPath.LIB1_1)
class ActivityLib1 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_lib1)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }

    fun click(view: View) {
        Toast.makeText(this,"lib1", Toast.LENGTH_SHORT).show()
    }
}

ActivityLib2

复制代码
package com.example.mylibrary

import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.router.ConstRouterPath
import com.example.router.RouterApi
import com.example.router.RouterPath

@RouterPath(ConstRouterPath.LIB1_2)
class ActivityLib2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_lib2)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }

    fun click(view: View) {
        RouterApi.routerPath(this, ConstRouterPath.APP_MAIN)
    }
}

module mylibrary2:

复制代码
build.gradle.kts
复制代码
......

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)

    implementation(project(path = ":lib_router"))
}

Lib2Activity1

复制代码
package com.example.mylibrary2

import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.router.ConstRouterPath
import com.example.router.RouterPath

@RouterPath(ConstRouterPath.LIB2_1)
class Lib2Activity1 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_lib21)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }

    fun click(view: View) {
        finish()
    }
}

Lib2Activity2

复制代码
package com.example.mylibrary2

import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.router.ConstRouterPath
import com.example.router.RouterPath

@RouterPath(ConstRouterPath.LIB2_2)
class Lib2Activity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_lib22)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }

    fun click(view: View) {finish()}
}

module app:

复制代码
build.gradle.kts
复制代码
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)

    id("com.example.custom.test2")
}

android {
    namespace = "com.example.app2"
    compileSdk {
        version = release(36)
    }

    defaultConfig {
        applicationId = "com.example.app2"
        minSdk = 24
        targetSdk = 36
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
}

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)

    implementation(project(path = ":lib_router"))

    implementation(project(path = ":mylibrary"))
    implementation(project(path = ":mylibrary2"))

}

MainActivity

复制代码
package com.example.app2

import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import com.example.router.ConstRouterPath
import com.example.router.RouterApi
import com.example.router.RouterPath
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@RouterPath(ConstRouterPath.APP_MAIN)//"/app/main"
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        val start = System.currentTimeMillis()
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        log("MainActivity---onCreate---耗时${System.currentTimeMillis() - start}")
    }

    fun test(view: View) {

        RouterApi.routerPath(this, ConstRouterPath.LIB1_1)
    }

    fun test2(view: View) {

        RouterApi.routerPath(this, ConstRouterPath.LIB1_2)

    }

    fun lib2Act1(view: View) {RouterApi.routerPath(this, ConstRouterPath.LIB2_1)}
    fun lib2Act2(view: View) {RouterApi.routerPath(this, ConstRouterPath.LIB2_2)}

    fun log(msg: String){
        Log.i("test",msg)
    }

    fun CoroutineScope.lanuchAutoCancel(block: suspend CoroutineScope.() -> Unit) {
        val job = this.launch (block= { block() })
        job.invokeOnCompletion {
            log("---invokeOnCompletion---AutoCancel")
            job.cancel()
            log("---invokeOnCompletion---end---AutoCancel")
        }
    }


}

在此记录一下,做个笔记

相关推荐
liangdabiao5 小时前
开源AI拼豆大升级 - 一键部署cloudflare page - 全免费 web和小程序
前端·人工智能·小程序
indexsunny5 小时前
互联网大厂Java面试实战:核心技术与微服务架构在电商场景中的应用
java·spring boot·redis·kafka·maven·spring security·microservices
摇滚侠5 小时前
Java 多线程基础 Java Multithreading Basics
java
今天你TLE了吗5 小时前
LLM到Agent&RAG——AI概念概述 第一章:大模型
java·人工智能·语言模型·大模型
你的牧游哥5 小时前
Java 核心概念详解
java·开发语言
深邃-5 小时前
【数据结构与算法】-顺序表链表经典算法
java·c语言·数据结构·c++·算法·链表·html5
JAVA学习通5 小时前
励志从零打造LeetCode平台之C端竞赛列表
java·vscode·leetcode·docker·状态模式
海兰5 小时前
【第3篇-续】多模型多模态项目实现示例(增加OpenAI通用适配)附源代码
java·人工智能·spring boot·alibaba·spring ai
澄澈青空~5 小时前
有一个叫R2C,也有一个叫G2C
java·数据库·人工智能·c#
SuperHeroWu75 小时前
【鸿蒙基础入门】概念理解和学习方法论说明
前端·学习·华为·开源·harmonyos·鸿蒙·移动端