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
javaclass 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...