3. Android 组件化三ARouter路由优化:AGP插件动态注入路由表实践

1.AGP的工作原理?

那么Google是如何制定Apk构建流程的呢? AndroidStudio中只有Gradle还不够,还得告诉它如何构建。我们知道Apk打包流程包括很多环节:源码文件经过JavaCompiler转为class文件、class经过dex操作变为dex文件、dex和资源等打包成apk、签名 等等一系列步骤,这就需要Google使用Gradle把这些操作都串联起来。

transform方法被调用的时机就是在class被打进dex文件之前

1.1 问题那能不能用APT,写java文件?

APT是写整个java文件,而AGP: 是修改文件,class

APT是生成模块类型的代码,而AGP,修改代码!

2.AGP在Arounter的重要作用

加载路由------在编译时进行扫描并动态在LogisticsCenter#loadRouterMap()中插入了代码

2.1 具体的插入的代码如下:

通过反编译得到的

clase文件是不能直接看的,是字节码,

上图是class文件反编译后的样子,我们可以看懂。 但如果使用文本软件直接打开class文件,基本就是看不懂的十六进制内容:

在Transform的中拿到所有class文件后,如何识别帮助类和LogisticsCenter类呢?,识别后如何在LogisticsCenter类中插入代码呢? 如下图这样:

2.2 架构图

核心的2个问题:

2.2.1 怎么知道帮助类的!

2.2.2 .怎么插入LogisticsCenter的内容,手动写demo

字节码: 如何识别某个方法?

ASM

问题:要在某个方法里面插入,应该怎么做?

先看编译前的,然后把要编译后的

最终:用asm框架搞定,插桩

具体要插入的内容

2.3 下面是简化版本的插件

csharp 复制代码
/**
 * ARouter框架的简化版自动注册插件
 */

public class PluginLaunch implements Plugin<Project> {

   @Override
   public void apply(Project project) {
       // 检查当前模块是否是Android应用模块(而不是库模块)
        def isApp = project.plugins.hasPlugin(AppPlugin)

       // 只有应用模块才需要这个自动注册插件
       if (isApp) {
           // 初始化日志系统
           Logger.make(project)
           Logger.i('启用ARouter自动注册插件')

           // 获取Android扩展配置
            def android = project.extensions.getByType(AppExtension)
           // 创建自定义Transform实现
            def transformImpl = new RegisterTransform(project)


           // 初始化ARouter自动注册的扫描设置
           // 指定需要扫描的三种ARouter接口类型
           ArrayList<ScanSetting> list = new ArrayList<>(3)

           
           // 1. 路由根节点接口(存储所有路由路径)
            list.add(new ScanSetting('IRouteRoot'))
           // 2. 拦截器分组接口
            list.add(new ScanSetting('IInterceptorGroup'))
           // 3. 服务提供者分组接口
            list.add(new ScanSetting('IProviderGroup'))

          
           // 将扫描设置传递给Transform
           RegisterTransform.registerList = list
           // 向Android构建流程注册自定义Transform
           // 这将在.class文件转换为.dex文件前介入编译过程
            android.registerTransform(transformImpl)
       }
   }
}

注册自定义 Transform

ini 复制代码
def android = project.extensions.getByType(AppExtension)
def transformImpl = new RegisterTransform(project)
android.registerTransform(transformImpl)

获取 Android 构建扩展 (AppExtension)。

创建自定义的 RegisterTransform(继承自 Transform,用于在字节码层面修改类文件)。

将 Transform 注册到 Android 构建流程中

配置扫描规则

csharp 复制代码
ArrayList<ScanSetting> list = new ArrayList<>(3)
list.add(new ScanSetting('IRouteRoot'))
list.add(new ScanSetting('IInterceptorGroup'))
list.add(new ScanSetting('IProviderGroup'))
RegisterTransform.registerList = list

定义需要扫描的 三个 ARouter 接口:

IRouteRoot: 路由根节点(存储所有路由路径)。

IInterceptorGroup: 拦截器分组。

IProviderGroup: 服务提供者分组。

将这些规则传递给 RegisterTransform,指导 Transform 在编译时扫描实现这些接口的类。

工作原理(运行时)

Transform 处理字节码

在编译的 .class 文件转 .dex 文件 前,RegisterTransform 会扫描所有类。

检测哪些类实现了 IRouteRoot、IInterceptorGroup、IProviderGroup 接口。

自动生成注册代码

将扫描到的类名收集起来。

动态生成注册代码(通常是在一个固定类中插入注册逻辑),例如:

typescript 复制代码
// 伪代码示例
public class ARouter$$Root$$app {
  public static void loadInto(Map<String, Class<?>> routes) {
    routes.put("/module/path", TargetRoute.class);
  }
}

ARouter 初始化时调用

应用启动时,ARouter 框架会查找生成的注册类。

自动加载所有路由、拦截器、服务提供者信息,无需手动注册。

RegisterTransform

java 复制代码
/**
 * ARouter 自动注册的核心 Transform 实现
 * 功能:
 *  1. 扫描所有类文件,查找实现指定接口的类
 *  2. 将注册代码生成到目标类文件中 {@link ScanSetting#GENERATE_TO_CLASS_FILE_NAME
 */

class RegisterTransform extends Transform {
   Project project
   // 静态扫描配置列表,来自PluginLaunch的配置
   static ArrayList<ScanSetting> registerList
   // 包含初始化代码的目标类文件
   static File fileContainsInitClass;
 
   RegisterTransform(Project project) {
       this.project = project
   }
   /**
    * 获取Transform名称(用于调试和日志)
    * @return Transform的唯一标识名
    */
   @Override
   String getName() {
       return ScanSetting.PLUGIN_NAME // 返回插件名称(如"arouter-register")
   }

   /**
    * 指定输入内容类型(只处理class文件)
    * @return 返回CLASS类型集合
    */

   @Override
   Set<QualifiedContent.ContentType> getInputTypes() {
       return TransformManager.CONTENT_CLASS
   }

   /**
    * 指定处理范围(整个项目)
    * @return 返回全项目范围
    */
   @Override
   Set<QualifiedContent.Scope> getScopes() {
       return TransformManager.SCOPE_FULL_PROJECT
   }

   /**
    * 是否支持增量编译
    * @return 当前实现不支持增量编译
    */
   @Override
   boolean isIncremental() {
       return false
   }
  
   /**
    * 核心转换方法,在编译过程中被调用
    * 1. 扫描所有JAR和目录中的class文件
    * 2. 识别实现特定接口的类
    * 3. 生成并注入注册代码
    */
   @Override
   void transform(Context context, Collection<TransformInput> inputs,
                   Collection<TransformInput> referencedInputs,
                   TransformOutputProvider outputProvider,
                   boolean isIncremental) throws IOException, TransformException, InterruptedException {
       Logger.i('开始扫描JAR文件中的注册信息')
       long startTime = System.currentTimeMillis()
       boolean leftSlash = File.separator == '/' // 处理不同操作系统的路径分隔符
  
       // 遍历所有输入文件(JAR和目录)
        inputs.each { TransformInput input ->

           // 处理JAR文件输入
            input.jarInputs.each { JarInput jarInput ->
               // 生成输出JAR文件名(避免冲突)
               String destName = jarInput.name
                def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath)
               if (destName.endsWith(".jar")) {
                    destName = destName.substring(0, destName.length() - 4)
               }

              
               // 获取输入/输出文件路径
               File src = jarInput.file
               File dest = outputProvider.getContentLocation(destName + "_" + hexName, 
                    jarInput.contentTypes, jarInput.scopes, Format.JAR
  
               // 扫描JAR文件中的类
               if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) {
                   ScanUtil.scanJar(src, dest) // 核心扫描方法
               }

               // 复制原始JAR到输出位置
               FileUtils.copyFile(src, dest)

           }

           // 处理目录中的class文件
            input.directoryInputs.each { DirectoryInput directoryInput ->
               File dest = outputProvider.getContentLocation(directoryInput.name, 
                    directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)

              
               String root = directoryInput.file.absolutePath
               if (!root.endsWith(File.separator)) root += File.separator
               
               // 递归扫描目录中的每个class文件
                directoryInput.file.eachFileRecurse { File file ->
                    def path = file.absolutePath.replace(root, '')
                   // 统一路径格式(Windows反斜杠转正斜杠)
                   if (!leftSlash) {
                        path = path.replaceAll("\\\\", "/")
                   }

                   // 过滤并处理符合条件的class文件
                   if(file.isFile() && ScanUtil.shouldProcessClass(path)){
                       ScanUtil.scanClass(file) // 核心扫描方法
                   }
               }

               // 复制目录到输出位置
               FileUtils.copyDirectory(directoryInput.file, dest)
           }
       }

       Logger.i('扫描完成,耗时 ' + (System.currentTimeMillis() - startTime) + "ms")

     
       // 扫描完成后生成注册代码
       if (fileContainsInitClass) {
            registerList.each { ScanSetting ext ->
               Logger.i('向文件插入注册代码: ' + fileContainsInitClass.absolutePath)
              
               // 检查是否找到实现类
               if (ext.classList.isEmpty()) {
                   Logger.e("未找到实现接口的类: " + ext.interfaceName)
               } else {
                   // 打印找到的类(调试用)
                    ext.classList.each { Logger.i(it) }
                   // 核心代码生成方法
                   RegisterCodeGenerator.insertInitCodeTo(ext)
               }
           }
       }
       Logger.i("代码生成完成,总耗时: " + (System.currentTimeMillis() - startTime) + "ms")
   }
}

核心处理类,继承自Transform

在编译过程中扫描所有.class文件

查找实现了指定接口(IRouteRoot等)的类

动态生成路由注册代码

2.3.1.核心功能详解:

Transform 生命周期方法

getName(): 返回 Transform 名称(用于日志标识)

getInputTypes(): 指定处理 CLASS 文件类型

getScopes(): 声明处理全项目范围

isIncremental(): 禁用增量编译(简化实现)

2.3.2.核心转换流程 (transform 方法):
scss 复制代码
// 处理JAR文件

input.jarInputs.each { ... 
  ScanUtil.scanJar(src, dest) // 扫描JAR中的类
  FileUtils.copyFile(src, dest) // 复制到输出目录
}

// 处理目录中的class文件
input.directoryInputs.each { ...
  directoryInput.file.eachFileRecurse { ...
    if(ScanUtil.shouldProcessClass(path)) {
      ScanUtil.scanClass(file) // 扫描单个类文件
    }
  }
  FileUtils.copyDirectory(...) // 复制目录
}

3.扫描后处理:
if (fileContainsInitClass) {
  registerList.each { ext ->
    if (!ext.classList.isEmpty()) {
      // 向目标类插入注册代码
      RegisterCodeGenerator.insertInitCodeTo(ext)
    }
  }
}
2.3.4 工作流程:

1).扫描阶段:

遍历所有 JAR 和 class 文件

识别实现指定接口的类

收集类名到 ScanSetting.classList

2).代码生成阶段:

确认目标文件存在(fileContainsInitClass)

遍历所有扫描配置(registerList)

对每个配置生成注册代码

注入到目标类文件中

3).输出处理:

保持原始文件结构

只修改目标注册类

其他文件原样复制

scss 复制代码
/**
 * ARouter 自动注册代码生成器
 * 功能:将扫描到的路由组件注册代码注入到 LogisticsCenter 类中
 * @author billy.qi email: qiyilike@163.com
 */

class RegisterCodeGenerator {
   ScanSetting extension  // 扫描配置信息(包含要注册的类列表)


   private RegisterCodeGenerator(ScanSetting extension) {
       this.extension = extension
   }

   /**
    * 入口方法:向目标类插入注册代码
    * @param registerSetting 扫描配置(包含需要注册的类列表)
    */

   static void insertInitCodeTo(ScanSetting registerSetting) {
       if (registerSetting != null && !registerSetting.classList.isEmpty()) {
           RegisterCodeGenerator processor = new RegisterCodeGenerator(registerSetting)
           File file = RegisterTransform.fileContainsInitClass
           // 只处理包含目标类的JAR文件
           if (file.getName().endsWith('.jar'))
                processor.insertInitCodeIntoJarFile(file)
       }
   }

 
   /**
    * 核心方法:向JAR文件中的目标类注入注册代码
    * @param jarFile 包含 LogisticsCenter.class 的JAR文件
    * @return 处理后的JAR文件
    */

   private File insertInitCodeIntoJarFile(File jarFile) {
       if (jarFile) {
           // 创建临时优化文件
            def optJar = new File(jarFile.getParent(), jarFile.name + ".opt")
            optJar.delete()  // 清理旧文
           
            def file = new JarFile(jarFile)
           JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))

 

            file.entries().each { JarEntry jarEntry ->
               String entryName = jarEntry.name
                jarOutputStream.putNextEntry(new ZipEntry(entryName))

               InputStream inputStream = file.getInputStream(jarEntry)

               // 找到目标类文进行代码注入
               if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
                   Logger.i('向类文件注入初始化代码 >> ' + entryName)
                   // 使用ASM修改字节码
                    jarOutputStream.write(referHackWhenInit(inputStream))
               } else {
                   // 其他文件直接复制
                    jarOutputStream.write(IOUtils.toByteArray(inputStream))
               }
                inputStream.close()
                jarOutputStream.closeEntry()

           }
            jarOutputStream.close()
            file.close()

 
           // 替换原始JAR文件
            jarFile.delete()
            optJar.renameTo(jarFile)
       }
       return jarFile
   }

   /**
    * 使用ASM框架修改字节码
    * @param inputStream 类文件输入流
    * @return 修后的字节码
    */

   private byte[] referHackWhenInit(InputStream inputStream) {
       ClassReader cr = new ClassReader(inputStream)  // 读取原始字节码
       ClassWriter cw = new ClassWriter(cr, 0)       // 创建字节码写入器
       // 使用自定义访问器修改字节码
       ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)
        cr.accept(cv, ClassReader.EXPAND_FRAMES)
       return cw.toByteArray()  // 返回修改后的字节码
   }

 
   /**
    * 自定义类访问器(ASM)
    * 功能:定位目标方法进行代码注入
    */

   class MyClassVisitor extends ClassVisitor {
       MyClassVisitor(int api, ClassVisitor cv) {
           super(api, cv)
       }

       @Override

       MethodVisitor visitMethod(int access, String name, String desc,
                                 String signature, String[] exceptions) {
           MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
           // 定位目标初始化方法(通常是 loadRouterMap)
           if (name == ScanSetting.GENERATE_TO_METHOD_NAME) {
               // 返回自定义方法访问器进行代码注入
                mv = new RouteMethodVisitor(Opcodes.ASM5, mv)
           }
           return mv
       }
   }

   /**
    * 自定义方法访问器(ASM)
    * 功能:在方法返回前插入注册代码
    */

   class RouteMethodVisitor extends MethodVisitor {
       RouteMethodVisitor(int api, MethodVisitor mv) {
           super(api, 

       @Override
       void visitInsn(it opcode) {
           // 在返回指令前插入代码(IRETURN~RETURN 是所有返回指令)
           if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
               // 遍历所有需要注册的类
                extension.classList.each { className ->
                    className = className.replaceAll("/", ".")  // 转换格式:com/example -> com.example

                   // 字节码操作指令:
                    mv.visitLdcInsn(className)  // 1. 将类名压入操作数栈
                   
                   // 2. 调用注册方法:LogisticsCenter.register(className)
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                           ScanSetting.GENERATE_TO_CLASS_NAME,   // 目标类:com/alibaba/android/arouter/core/LogisticsCenter
                           ScanSetting.REGISTER_METHOD_NAME,   // 方法名:register
                           "(Ljava/lang/String;)V",             // 方法描述符:参数String,返回void
                           false)
               }
           }
           super.visitInsn(opcode)  // 执行原始返回指令
       }

       /**
        * 调整栈大小(确保有足够空间执行插入的代码)
        */
       @Override
       void visitMaxs(int maxStack, int maxLocals) {
           // 增加栈空间(+4 确保有足够空间处理新指令)
           super.visitMaxs(maxStack + 4, maxLocals)
       }
   }
}
  
字节码修改技术(ASM):

ClassReader:读取原始类字节码

ClassWriter:生成修改后的字节码

ClassVisitor:访问类结构

MethodVisitor:修改方法体

关键注入点:

rust 复制代码
// 在方法返回前插入代码
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
  // 对每个类生成注册代码
  extension.classList.each { className ->
    mv.visitLdcInsn(className)  // 加载类名字符串
    mv.visitMethodInsn(INVOKESTATIC, "com/alibaba/android/arouter/core/LogisticsCenter", 
                      "register", "(Ljava/lang/String;)V", false)
  }
}

生成的代码等价于

c 复制代码
public class LogisticsCenter {
  static void loadRouterMap() {
    // 原始代码...

    // 注入的代码
    register("com.example.Route1");
    register("com.example.Route2");
    register("com.example.Interceptor1");
  
    return;
  }
}

配置常量说明(ScanSetting):

常量名 典型值 作用

GENERATE_TO_CLASS_FILE_NAME "com/alibaba/android/arouter/core/LogisticsCenter.class" 目标类文件路径

GENERATE_TO_METHOD_NAME "loadRouterMap" 要注入代码的方法名

GENERATE_TO_CLASS_NAME "com/alibaba/android/arouter/core/LogisticsCenter" 目标类全限定名

REGISTER_METHOD_NAME "register" 要调用的注册方法名

loadRouterMap(),方法里面是怎么插入代码的! 源代码是哪个

scss 复制代码
   private byte[] referHackWhenInit(InputStream inputStream) {
       ClassReader cr = new ClassReader(inputStream)
       ClassWriter cw = new ClassWriter(cr, 0)
       ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)
        cr.accept(cv, ClassReader.EXPAND_FRAMES)
       return cw.toByteArray()
   }

这个方法里面的详细内容:

extension.classList,里面的classList指的是啥

扫描阶段的list,extension是传过来的

ScanUtil 负责扫描 JAR 和 Class 文件,检测实现目标接口的类,并填充 classList。

// 扫描匹配的类:

arduino 复制代码
private static class ScanClassVisitor extends ClassVisitor {
   private final File file;
   private final File destFile;

   ScanClassVisitor(File file, File destFile) {
       super(Opcodes.ASM5)
       this.file = file
       this.destFile = destFile
   }

  
   @Override
   public void visit(int version, int access, String name, 
                     String signature, String superName, String[] interfaces) {
       super.visit(version, access, name, signature, superName, interfaces)


       // 核心赋值逻辑
       RegisterTransform.registerList.each { ext ->
           // 检查是否实现目标接口
           if (interfaces != null && Arrays.asList(interfaces).contains(ext.interfaceName)) {
               // 添加到 classList
                ext.classList.add(name) // ⭐⭐⭐ 赋值点 ⭐⭐⭐

               // 记录包含初始化类的文件(首次发现时)
               if (!RegisterTransform.fileContainsInitClass) {
                   RegisterTransform.fileContainsInitClass = destFile ?: file
               }
           }
       }
   }
}

classList在extension, gradle? ScanSetting extension,是不gradle,是定义了一个类 !

PluginLaunch 是怎么公开的, implementation-class=com.alibaba.android.arouter.register.launch.PluginLaunch

APT需要公布,AGP也需要注册公布

Gradle插件"半自动化"方案: 使用java-gradle-plugin库

3.手写Aounter插件,ASM的demo:

需求: LogisticsCenter类里面

  • loadRouterMap() 方法开头插入:register("pengchengshibashao_start");
  • 在方法返回前插入:register("pengchengshibashao_end");

3.1 需要在创建的gradle插件中注册自定义的Transform

ini 复制代码
implementation-class=com.example.router.plugin.RouterCodeInjectPlugin

3.2 具体的plugin

java 复制代码
package com.example.router.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project
import com.android.build.api.transform.*
import com.android.build.gradle.AppExtension
import org.objectweb.asm.*
import org.apache.commons.io.FileUtils

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream

/**
 * ARouter字节码注入插件
 * 功能:在ARouter的LogisticsCenter.loadRouterMap()方法首尾插入自定义代码
 */
class RouterCodeInjectPlugin implements Plugin<Project> {

    // 目标类和方法信息
    static final String TARGET_CLASS = "com/alibaba/android/arouter/core/LogisticsCenter"
    static final String TARGET_METHOD = "loadRouterMap"
    static final String TARGET_METHOD_DESC = "()V" // 方法描述符(无参void)
    static final String REGISTER_METHOD = "register" // 要注入的方法名
    static final String REGISTER_METHOD_DESC = "(Ljava/lang/String;)V" // 方法描述符(String参数void返回)

    @Override
    void apply(Project project) {
        // 获取Android扩展并注册Transform
        def android = project.extensions.getByType(AppExtension)
        android.registerTransform(new RouterCodeTransform())
    }

    /**
     * 自定义Transform实现类
     * 负责扫描并修改字节码
     */
    class RouterCodeTransform extends Transform {

        @Override
        String getName() {
            return "RouterCodeInject"
        }

        @Override
        Set<QualifiedContent.ContentType> getInputTypes() {
            // 处理.class文件
            return [QualifiedContent.DefaultContentType.CLASSES]
        }

        @Override
        Set<QualifiedContent.Scope> getScopes() {
            // 作用范围:主工程+子模块+第三方库
            return [QualifiedContent.Scope.PROJECT,
                    QualifiedContent.Scope.SUB_PROJECTS,
                    QualifiedContent.Scope.EXTERNAL_LIBRARIES]
        }

        @Override
        boolean isIncremental() {
            // 禁用增量编译(简化处理逻辑)
            return false
        }

        @Override
        void transform(Context context, Collection<TransformInput> inputs,
                       Collection<TransformInput> referencedInputs,
                       TransformOutputProvider outputProvider,
                       boolean isIncremental) throws IOException, TransformException, InterruptedException {
            
            // 遍历所有输入
            inputs.each { TransformInput input ->
                // 处理目录中的class文件(如:app/build/intermediates/classes)
                input.directoryInputs.each { DirectoryInput dirInput ->
                    File dest = outputProvider.getContentLocation(
                            dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY)
                    
                    // 递归扫描.class文件
                    dirInput.file.eachFileRecurse { File file ->
                        if (file.isFile() && file.name.endsWith(".class")) {
                            // 定位目标类并修改字节码
                            handleClassFile(file)
                        }
                    }
                    
                    // 将处理后的目录复制到输出位置
                    FileUtils.copyDirectory(dirInput.file, dest)
                }

                // 处理JAR文件(如:aar依赖)
                input.jarInputs.each { JarInput jarInput ->
                    String destName = jarInput.name
                    File src = jarInput.file
                    File dest = outputProvider.getContentLocation(
                            destName, jarInput.contentTypes, jarInput.scopes, Format.JAR)
                    
                    if (src.exists()) {
                        // 解压JAR,扫描目标类并重新打包
                        handleJarFile(src, dest)
                    }
                }
            }
        }

        /**
         * 处理单个.class文件
         * @param classFile 目标类文件
         */
        private void handleClassFile(File classFile) {
            // 只处理LogisticsCenter.class
            if (classFile.name == 'LogisticsCenter.class') {
                Logger.info("处理LogisticsCenter类文件: ${classFile.absolutePath}")
                
                // 读取原始字节码
                def bytes = FileUtils.readFileToByteArray(classFile)
                def reader = new ClassReader(bytes)
                def writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS)
                def visitor = new RouterClassVisitor(Opcodes.ASM5, writer)
                
                // 使用ASM修改字节码
                reader.accept(visitor, ClassReader.EXPAND_FRAMES)
                
                // 写回修改后的字节码
                def modifiedBytes = writer.toByteArray()
                FileUtils.writeByteArrayToFile(classFile, modifiedBytes)
            }
        }

        /**
         * 处理JAR中的目标类
         * @param srcJar 输入JAR
         * @param destJar 输出JAR
         */
        private void handleJarFile(File srcJar, File destJar) {
            def jarFile = new JarFile(srcJar)
            def outputJar = new JarOutputStream(new FileOutputStream(destJar))
            
            jarFile.entries().each { jarEntry ->
                def inputStream = jarFile.getInputStream(jarEntry)
                outputJar.putNextEntry(new JarEntry(jarEntry.name))
                
                // 只处理目标类
                if (jarEntry.name.endsWith(".class") &&
                        jarEntry.name.contains("LogisticsCenter")) {
                    
                    Logger.info("处理JAR中的LogisticsCenter类: ${jarEntry.name}")
                    
                    // ASM字节码操作流程
                    def reader = new ClassReader(inputStream)
                    def writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS)
                    def visitor = new RouterClassVisitor(Opcodes.ASM5, writer)
                    reader.accept(visitor, ClassReader.EXPAND_FRAMES)
                    
                    // 写入修改后的字节码
                    outputJar.write(writer.toByteArray())
                } else {
                    // 非目标文件直接复制
                    def buffer = new byte[1024]
                    def length
                    while ((length = inputStream.read(buffer)) > 0) {
                        outputJar.write(buffer, 0, length)
                    }
                }
                
                outputJar.closeEntry()
                inputStream.close()
            }
            
            outputJar.close()
            jarFile.close()
        }
    }

    /**
     * ASM类访问器:定位目标方法
     */
    static class RouterClassVisitor extends ClassVisitor {

        RouterClassVisitor(int api, ClassVisitor cv) {
            super(api, cv)
        }

        @Override
        MethodVisitor visitMethod(int access, String name, String desc,
                                  String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
            
            // 只处理loadRouterMap方法
            if (name == TARGET_METHOD && desc == TARGET_METHOD_DESC) {
                Logger.info("找到目标方法: ${TARGET_METHOD}")
                return new RouterMethodVisitor(api, mv) // 交给方法访问器处理
            }
            return mv
        }
    }

    /**
     * ASM方法访问器:插入字节码
     */
    static class RouterMethodVisitor extends MethodVisitor {

        RouterMethodVisitor(int api, MethodVisitor mv) {
            super(api, mv)
        }

        @Override
        void visitCode() {
            super.visitCode()
            // 在方法开头插入代码:register("pengchengshibashao_start");
            Logger.info("在方法开头插入注册代码")
            mv.visitLdcInsn("pengchengshibashao_start") // 加载字符串常量
            mv.visitMethodInsn(Opcodes.INVOKESTATIC,     // 调用静态方法
                    TARGET_CLASS, REGISTER_METHOD, REGISTER_METHOD_DESC, false)
        }

        @Override
        void visitInsn(int opcode) {
            // 在返回指令前插入代码:register("pengchengshibashao_end");
            if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
                Logger.info("在方法结尾插入注册代码")
                mv.visitLdcInsn("pengchengshibashao_end")
                mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                        TARGET_CLASS, REGISTER_METHOD, REGISTER_METHOD_DESC, false)
            }
            super.visitInsn(opcode)
        }

        @Override
        void visitMaxs(int maxStack, int maxLocals) {
            // 确保栈深度足够(至少2个slot:字符串+方法调用)
            super.visitMaxs(Math.max(maxStack, 2), maxLocals)
        }
    }
}

// 简单日志工具
class Logger {
    static void info(String msg) {
        println "[RouterPlugin] $msg"
    }
}

studio种如何创建groovy文件

和java文件的目录也是不一样的!

id 'groovy' // 添加 Groovy 支持

gradle: 的生命周期,任务的作用!

3.3 完整的流程图

3.4 排查及调试 :插件是否生效! 是否执行!

/Users/your_username/.m2/repository/com/example/router/plugin/router-plugin/1.0.0/router-plugin-1.0.0.jar

问题: 生产了3个文件

// 关键修复:安全地禁用默认发布

ini 复制代码
afterEvaluate {
    // 安全地禁用默认的 pluginMaven 发布
    def pluginMavenTasks = [
            'generateMetadataFileForPluginMavenPublication',
            'generatePomFileForPluginMavenPublication',
            'publishPluginMavenPublicationToMavenLocal'
    ]

 
    pluginMavenTasks.each { taskName ->
        def task = tasks.findByName(taskName)
        if (task) {
            task.enabled = false
            println "已禁用任务: $taskName"
        }
    }
}

// 添加诊断任务

arduino 复制代码
task debugPublications {
    doLast {
        println "=== 有效发布任务 ==="
        tasks.withType(PublishToMavenRepository).each { task ->
            if (task.enabled) {
                println "- ${task.name}"
                println "  出版物: ${task.publication.groupId}:${task.publication.artifactId}:${task.publication.version}"
            }
        }
        println "=================="
    }
}

3.4 Plugin Transfer ASM ASM核心方法

Plugin、Transform 与 ASM 三者的关系详解

3.4.1. Gradle Plugin(插件)

  • 角色定位 :整个流程的启动器协调者

  • 核心职责

    • apply() 方法中注册自定义 Transform
    • 配置构建环境参数
    • 提供用户可配置的扩展接口
  • 关键代码

    groovy

    java 复制代码
    class RouterCodeInjectPlugin implements Plugin<Project> {
        void apply(Project project) {
            def android = project.extensions.getByType(AppExtension)
            android.registerTransform(new RouterCodeTransform())
        }
    }

3.4.2. Transform(转换器)

  • 角色定位 :字节码处理的管道执行引擎

  • 核心职责

    • 扫描所有输入源(.class 文件和 .jar 文件)
    • 提供处理目录和 JAR 文件的回调接口
    • 管理输入输出流
  • 关键特性

    • transform() 方法中处理所有输入
    • 支持增量编译(本示例中禁用)
    • 可指定作用范围(Project/Sub-Projects/External Libraries)

3.4.3. ASM(字节码操作框架)

  • 角色定位 :字节码的手术刀编辑器

  • 核心职责

    • 解析.class文件结构
    • 提供访问类/方法/字段的API
    • 支持字节码的增删改查
  • 核心组件

    组件 作用 示例
    ClassReader 读取字节码 解析.class文件
    ClassWriter 生成字节码 输出修改后的字节码
    ClassVisitor 访问类结构 扫描类和方法
    MethodVisitor 修改方法体 插入/删除指令

3.4.4 关键 ASM 组件

组件 作用 使用场景
ClassReader 读取字节码 解析.class文件
ClassWriter 生成字节码 输出修改后的字节码
ClassVisitor 访问类结构 扫描类和方法
MethodVisitor 访问方法体 修改方法指令
FieldVisitor 访问字段 修改字段信息
AnnotationVisitor 访问注解 处理注解信息

3.4.5 Plugin、Transform 与 ASM 三者的关系详解

三者的不可替代性

组件 不可替代的原因 替代方案
Gradle Plugin 唯一合法的构建入口点 无,必须通过插件机制
Transform Android 官方提供的字节码处理管道 可替换为 Transform API 的封装库(如 Booster)
ASM JVM 字节码操作的事实标准 可替换为 Javassist 或 Byte Buddy,但 ASM 性能最优

在 Android 组件化开发中,Plugin、Transform 和 ASM 共同构成了强大的字节码操作工具链,它们之间的关系可以用以下结构表示:

手写的Arouter的plugin的项目地址:github.com/pengcaihua1...

相关推荐
拾光拾趣录9 分钟前
WebSocket:断线、心跳与重连
前端·websocket
阿眠33 分钟前
vue3实现web端和小程序端个人签名
前端·小程序·apache
哎呦薇1 小时前
从开发到发布:手把手教你将Vue组件上传npm
前端·vue.js
Z7676_1 小时前
静态路由技术
服务器·前端·javascript
慧一居士1 小时前
npm 和 npx 区别对比
前端
用户3802258598241 小时前
vue3源码解析:生命周期
前端·vue.js·源码阅读
遂心_1 小时前
前端路由进化论:从传统页面到React Router的SPA革命
前端·javascript
前端菜鸟杂货铺1 小时前
前端首屏优化及可实现方法
前端
遂心_1 小时前
React Fragment与DocumentFragment:提升性能的双剑合璧
前端·javascript·react.js
ze_juejin1 小时前
ionic、flutter、uniapp对比
前端