ASM,Transfer,自定义插件,要先对Gradle知识,它的生命周期有所了解才行!
可以先看我之前的Arounter自定义插件,还有微信Matrix源码插桩
5. Android 组件化五(演变升级) 从蜗牛编译到秒级构建:组件化如何让工程效率飙升300%你是否还在为修改一行 - 掘金
10. Android <卡顿十>高度封装Matrix卡顿, 修改Matrix源码和发布自己的插件在集成Matrix的时 - 掘金
1.微信Matrix插桩原理,源码分析
1.1 插件的结构

在源码中找到matrix-gradle-plugin这个模块,找到插件的入口。 resources/META-INF/gradle-plugins/com.tencent.matrix-plugin.properties
ini
implementation-class=com.tencent.matrix.plugin.MatrixPlugin
Matrix-android 里面的matrix-gradle-plugin matrix-gradle-plugin
的源码进行一个深入的分析。这个插件是 Matrix-Android 实现"编译期插桩"的核心,它负责在 Android 应用的编译过程中修改字节码。
插件的核心内容在于TraceMethodAdapter中的三项操作:
- 在符合条件的方法入口插入AppMethodBeat.i()
- 在符合条件的方法出口插入AppMethodBeat.o()
- 在Activity的onWindowFocusChanged方法中插入AppMethodBeat.at()
在编译期间,除被排除掉的方法外,大量方法的入口和出口处被插入了AppMethodBeat的方法,意在能通过这两个方法计算出方法执行的耗时,于是,每一个方法执行的耗时情况就清晰的展现在我们开发者眼前,借助这些数据才能更好的发现卡顿问题的原因。
1.1.1 插件应用 (MatrixPlugin.apply
)
当你在项目的 build.gradle
中写下 apply plugin: 'com.tencent.matrix-plugin'
时,MatrixPlugin.apply(project)
方法被调用。
主要工作:
- 检查环境:确认当前插件是否被应用在 Android Application 或 Library 项目中。
- 创建配置扩展 :通过
project.extensions.create('matrix', MatrixExtension)
,这使得你可以在build.gradle
中使用matrix { ... }
来配置插件。 - 注册自定义 Transform :这是最关键的一步。通过
project.android.registerTransform(...)
方法,将MatrixTraceTransform
注册到 Android 构建流程中。此时,插件就成功"挂载"到了构建流程上。 - 注册其他任务:可能会注册一些用于处理资源、清单文件等的辅助任务。
1.1.2 配置扩展 (MatrixExtension
)
这个类定义了你熟悉的配置结构:
ini
matrix {
trace {
enable = true
baseMethodMapFile = "${project.buildDir}/matrix_output/Debug.methodmap"
blackListFile = "${project.projectDir}/matrixtrace/blackMethodList.txt"
// ... 其他配置
}
removeUnusedResources {
enable = true
// ... 其他配置
}
// ... 其他功能配置
}
MatrixTraceTransform
在执行时会读取这些配置来决定插桩的行为。
1.1.3 字节码转换核心 (MatrixTraceTransform
)
MatrixTraceTransform
继承自 com.android.build.api.transform.Transform
,它重写了几个关键方法来实现字节码处理。
transform(transformInvocation: TransformInvocation)
方法流程:
这是 Transform 的核心入口。它的处理逻辑可以概括为以下几步:
-
检查是否启用/是否需要执行 :读取
MatrixExtension
中的配置,检查插桩功能是否被启用。如果未启用,则直接return
,跳过所有处理,避免不必要的性能开销。 -
收集输入文件:
- 遍历所有输入 :
transformInvocation.inputs
包含了所有的输入内容,分为 目录输入 (DirectoryInput
) 和 JAR 输入 (JarInput
)。 - 目录输入 :通常是项目的源码编译后的
.class
文件目录。 - JAR 输入 :项目所依赖的本地或远程库(
.jar
或.aar
中的.class
文件)。
- 遍历所有输入 :
-
处理增量编译:
- 检查
transformInvocation.isIncremental()
。如果是增量编译,则只处理发生变化(Status.ADDED
,Status.CHANGED
)的文件,而跳过状态为Status.REMOVED
或Status.NOTCHANGED
的文件。这是优化构建速度的关键。
- 检查
-
遍历和处理每个 Class 文件:
-
这是一个双重循环:外层循环遍历每个
Input
,内层循环遍历每个Input
中的每个.class
文件。 -
关键决策 :对于遇到的每一个
.class
文件,需要根据配置决定是否要处理它。- 是否在包名白名单内? (如
com.sample.app
) - 是否在黑名单内? (如
android.support.*
,com.tencent.matrix.*
) - 是否要忽略的类? (如 R文件、BuildConfig文件、Lambda表达式等)
- 是否在包名白名单内? (如
-
这个过滤逻辑通常封装在
MatrixTrace
之类的类中。
-
-
字节码修改 (ASM 操作) :
-
对于需要插桩的
.class
文件,使用FileInputStream
读取其字节数据。 -
创建一个
ClassReader
来解析字节码。 -
创建一个
ClassWriter
(通常使用ClassWriter.COMPUTE_FRAMES
模式,让 ASM 自动计算栈帧,更安全)。 -
创建一个自定义的
ClassVisitor
(例如TraceClassAdapter
),并包裹ClassWriter
。这个自定义的ClassVisitor
是 ASM 操作的核心。 -
调用
ClassReader.accept(ClassVisitor, parsingOptions)
,开始访问解析类的所有部分(注解、字段、方法等)。 -
方法级访问 :当访问到方法时,自定义的
ClassVisitor
会创建一个自定义的MethodVisitor
(例如TraceMethodAdapter
)。在这个MethodVisitor
中:- 在
visitCode()
方法中(代表方法体开始),插入记录方法开始时间的代码(如调用AppMethodBeat.i(methodId)
)。 - 在
visitInsn(opcode)
方法中,当遇到RETURN
,IRETURN
,ATHROW
等方法返回指令时,插入记录方法结束时间的代码(如调用AppMethodBeat.o(methodId)
)。 - 这些"桩代码"是通过 ASM 的
visitMethodInsn()
,visitLdcInsn()
等指令一步步"编织"进去的。
- 在
-
访问结束后,
ClassWriter.toByteArray()
会返回修改后的新字节码。
-
-
输出文件:
- 将修改后的新字节码(
byte[]
)写入到transformInvocation.outputProvider
指定的目标位置。这个位置后的后续编译步骤(如 dex 打包)会使用这些已经被修改过的文件。
- 将修改后的新字节码(
1.1.4. 关键技术与设计模式
-
Transform API :Android Gradle Plugin 提供的标准扩展点,允许第三方插件在
.class -> .dex
的过程中修改字节码。 -
ASM :一个轻量级但功能强大的 Java 字节码操作和分析框架。它提供了基于
Visitor
模式的 API (ClassVisitor
,MethodVisitor
),使得遍历和修改字节码变得非常清晰。 -
增量编译:为了性能考量,插件必须很好地支持增量编译,避免每次构建都处理所有文件。
-
职责分离:
MatrixPlugin
负责插件生命周期和注册。MatrixExtension
负责配置管理。MatrixTraceTransform
负责构建流程集成和文件IO。MatrixTrace
(或其他类似类) 负责核心的插桩逻辑和 ASM 操作。

1.2 Matrix 获取方法堆栈的完整流程
整个过程分为编译期 和运行期两个阶段,完美衔接:
阶段一:编译期 (Preparation)
- 插桩 :
matrix-gradle-plugin
使用 ASM 在所有方法入口插入AppMethodBeat.i(methodId)
,在所有方法出口(包括正常 return 和异常 throw)插入AppMethodBeat.o(methodId)
。 - 生成映射表 :在插桩的同时,插件会收集所有被插桩方法的签名信息 (如
com/tencent/sample/MyActivity->onCreate(Landroid/os/Bundle;)V
)并为它们分配一个唯一的数字 ID (如10001
)。 - 输出文件 :插件将这个
方法ID -> 方法签名
的映射关系生成一个 JSON 文件(如methodmap.json
),并将其放在 Assets 目录或固定路径下,随 APK 一起发布。
Method ID | Method Signature |
---|---|
10001 | com/tencent/sample/MyActivity->onCreate(Landroid/os/Bundle;)V |
10002 | com/tencent/sample/MyActivity->onClick(Landroid/view/View;)V |
... | ... |
阶段二:运行期 (Execution & Analysis)
-
数据采集:
- 当应用运行时,被插桩的方法开始执行。
- 方法开始时,执行
AppMethodBeat.i(10001)
,记录:[时间戳t1, 事件: Enter, ID:10001]
- 方法结束时,执行
AppMethodBeat.o(10001)
,记录:[时间戳t2, 事件: Exit, ID:10001]
AppMethodBeat
内部使用一个巨大的环形数组 (long[]
)来按顺序存储这些记录。每条记录用一个long
值表示,其中高位代表时间戳,低位代表方法ID和事件类型(进入/退出)。这种设计极其高效,避免了对象创建和GC。
-
检测卡顿/慢方法:
- Matrix 的 监控主线程的loop,然后超过700ms,去检查
AppMethodBeat
中记录的数据。 - 它通过分析连续的事件,可以计算出每个方法的执行耗时(
t2 - t1
)。 - 如果发现某个方法的执行时间超过了预设的阈值(如 500ms 用于卡顿,100ms 用于慢方法),就会被判定为有问题的方法。
- Matrix 的 监控主线程的loop,然后超过700ms,去检查
-
还原堆栈(关键步骤!) :
-
当发现一个耗时方法时,Monitor 线程只知道它的
methodId
(例如10001
)和开始/结束时间 (t1, t2
)。 -
为了得到可读的堆栈,它需要:
a. 查找映射表 :根据
methodId=10001
,去内存中加载的methodmap.json
数据里查找,得到完整的方法签名com/tencent/sample/MyActivity->onCreate(...)
。b. 还原调用链 :光有一个方法名还不够,我们需要知道是谁调用了它。
AppMethodBeat
的环形数组里按时间顺序存储了所有方法的进出记录。分析器可以根据t1
和t2
这个时间范围,回溯 出在这段时间内所有"进入但尚未退出"的方法。-
这其实就是当时完整的调用栈。
-
例如,在
t1
时刻,如果栈内还有[10000, 10002]
,那么最终的调用栈就是:scss10002 (调用者B) 10000 (调用者A) 10001 (当前耗时方法) <- 我们找到的这个
c. 翻译堆栈 :最后,将回溯得到的这一系列
methodId
(如[10002, 10000, 10001]
) 全部通过映射表 翻译成对应的方法签名,就得到了一个完全可读的、包含调用关系的方法堆栈。 -
-
less
// 最终上报的堆栈信息可能是这样的:
at com.tencent.sample.CallerA.doSomething(CallerA.java:100) // methodId 10000
at com.tencent.sample.CallerB.invokeTask(CallerB.java:200) // methodId 10002
at com.tencent.sample.MyActivity.onCreate(MyActivity.java:50) // methodId 10001 (耗时700ms)
AppMethodBeat
的角色
在正确的流程中,AppMethodBeat
的角色依然不变:
- 它仍然是一个被动的、高性能的数据记录器。它默默地、持续地记录所有被插桩方法的执行痕迹,形成一个"方法执行时间线"。
- 它不负责主动判断卡顿 。判断卡顿是
LooperMonitor
的职责。 - 它是一个"黑匣子" 。当"事故"(卡顿)发生时,调查员(
LooperMonitor
的回调分析逻辑)会根据"事故发生时间"去读取黑匣子里的数据,还原事故现场(调用栈)。
EvilMethodTracer

插桩相关的:
traceExtension和removeUnusedResourcesExtension对应的正是build.gradle中的配置
2.要显示的效果
每个方法前面和后面添加时间搓,结束的时候计算时间差值
需要插入的内容:主要就是2行
long startTime = System.currentTimeMillis()// --------> AppMethodBeat.i(methodId)
long endTime = System.currentTimeMillis();// -------->AppMethodBeat.o(methodId)
csharp
/**
* 执行方法并统计执行时间
* @param methodName 方法名称(用于标识和输出)
* @param methodToExecute 要执行的方法(使用Lambda表达式或方法引用)
*/
public static void executeWithTiming(String methodName, Runnable methodToExecute) {
// 记录开始时间
long startTime = System.currentTimeMillis(); // 需要插入的方法
System.out.println("[" + methodName + "] 开始执行,时间戳: " + startTime);// 需要插入的方法
try {
// 执行目标方法
methodToExecute.run();
} finally {
// 记录结束时间并计算耗时
long endTime = System.currentTimeMillis();// 需要插入的方法
long duration = endTime - startTime;// 需要插入的方法
System.out.println("[" + methodName + "] 结束执行,时间戳: " + endTime);// 需要插入的方法
System.out.println("[" + methodName + "] 执行耗时: " + duration + "ms");
}
}
3.手写Matrix插件
scss
┌─────────────────────────────────────────────────────────────────────────────┐
│ TimePlugin ASM 架构图 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ 注册 ┌─────────────────────────────────────┐ │
│ │ TimePlugin │ ────────────► │ TimeTransform │ │
│ │ (Gradle Plugin) │ │ (Android Transform) │ │
│ └─────────────────┘ └─────────────────────────────────────┘ │
│ │ │ │
│ │应用插件 │处理Class文件 │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────────────────────────┐ │
│ │ Android Project│ │ TimeClassVisitor │ │
│ │ (使用插件项目) │ │ (ASM Class Visitor) │ │
│ └─────────────────┘ └─────────────────────────────────────┘ │
│ │ │
│ │访问方法 │
│ ▼ │
│ ┌─────────────────────────────────────┐│
│ │ TimeMethodVisitor ││
│ │ (ASM Method Visitor) ││
│ └─────────────────────────────────────┘│
│ │ │
│ │插入字节码 │
│ ▼ │
│ ┌─────────────────────────────────────┐│
│ │ MethodTimer ││
│ │ (运行时时间统计工具类) ││
│ └─────────────────────────────────────┘│
│ │ │
│ │输出日志 │
│ ▼ │
│ ┌─────────────────────────────────────┐│
│ │ Android Log ││
│ │ (系统日志输出) ││
│ └─────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────────────────┘

Android ASM 插桩插件开发与调试指南
针对您的问题,我整理了完整的解决方案,包括插件开发、调试技巧和常见错误处理。
3.1. 创建 ASM 插件模块
插件 build.gradle 配置
groovy
plugins {
id 'java-library'
id 'maven-publish'
id 'java-gradle-plugin' // 必须添加这个插件
}
group = 'com.example.asmplugin' // 合法的 groupId
version = '1.0.0'
repositories {
google()
mavenCentral()
}
dependencies {
implementation gradleApi()
implementation 'org.ow2.asm:asm:9.4' // 使用最新稳定版 ASM
implementation 'org.ow2.asm:asm-commons:9.4'
implementation 'com.android.tools.build:gradle:7.4.2' // 与项目 Gradle 版本一致
}
java {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
gradlePlugin {
plugins {
timeMethodPlugin {
id = 'com.example.time-plugin'
implementationClass = 'com.example.moduleasm.TimePlugin'
}
}
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'com.example.asmplugin'
artifactId = 'asm-plugin'
version = '1.0.0'
from components.java
}
}
repositories {
mavenLocal()
}
}
3.2. 插件核心代码实现(特别要注意不同的版本,API都 不一样)
Plugin
3.2.1 TimePlugin.java
-
职责: 插件入口,注册 Transform 到 Android 构建流程
-
关键功能:
- 应用插件时初始化
- 创建排除列表(过滤系统类和不需插桩的类)
- 注册 TimeTransform
- 配置 MultiDex 支持
java
package com.example.moduleasm;
import com.android.build.gradle.AppExtension;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class TimePlugin implements Plugin<Project> {
private final Logger logger = Logging.getLogger(TimePlugin.class);
@Override
public void apply(Project project) {
logger.lifecycle("⏳ [TimePlugin] 初始化开始");
// 确保是 Android 项目
if (!project.getPlugins().hasPlugin("com.android.application")) {
throw new IllegalStateException("必须应用 'com.android.application' 插件");
}
// 创建排除列表
Set<String> excludeList = createExcludeList();
logger.lifecycle("排除列表: " + excludeList);
// 注册 Transform
AppExtension android = project.getExtensions().getByType(AppExtension.class);
android.registerTransform(new TimeTransform(project, excludeList));
logger.lifecycle("✅ [TimePlugin] 初始化完成");
}
private Set<String> createExcludeList() {
return new HashSet<>(Arrays.asList(
"android/", "androidx/", "kotlin/", "com/google/",
"javax/", "org/", "com/example/BuildConfig",
"com/example/R", "androidx/multidex/", "java/"
));
}
}
3.2.2 TimeTransform.java
Transform
-
职责: 处理编译过程中的 Class 文件转换
-
关键功能:
- 实现 Android Transform API
- 遍历所有输入的 Class 和 Jar 文件
- 调用 ASM Visitor 处理每个 Class 文件
- 处理增量编译(如果支持)
java
package com.example.moduleasm;
import com.android.build.api.transform.*;
import com.android.build.gradle.internal.pipeline.TransformManager;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
public class TimeTransform extends Transform {
private final Project project;
private final Set<String> excludeList;
private final Logger logger = Logging.getLogger(TimeTransform.class);
public TimeTransform(Project project, Set<String> excludeList) {
this.project = project;
this.excludeList = excludeList;
}
@Override
public String getName() {
return "TimeTransform";
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;
}
@Override
public boolean isIncremental() {
return false;
}
@Override
public void transform(TransformInvocation invocation) throws IOException {
logger.lifecycle("🛠️ [TimeTransform] 开始处理...");
for (TransformInput input : invocation.getInputs()) {
for (DirectoryInput dirInput : input.getDirectoryInputs()) {
processDirectory(dirInput.getFile(), invocation.getOutputProvider());
}
for (JarInput jarInput : input.getJarInputs()) {
processJar(jarInput.getFile(), invocation.getOutputProvider());
}
}
logger.lifecycle("✅ [TimeTransform] 处理完成");
}
private void processDirectory(File inputDir, TransformOutputProvider outputProvider) throws IOException {
File outputDir = outputProvider.getContentLocation(
inputDir.getName(), getInputTypes(), getScopes(), Format.DIRECTORY
);
FileUtils.copyDirectory(inputDir, outputDir);
Collection<File> classFiles = FileUtils.listFiles(outputDir, new String[]{"class"}, true);
logger.lifecycle("处理目录: {}, 找到 {} 个 class 文件", inputDir, classFiles.size());
for (File file : classFiles) {
if (shouldSkip(file)) {
logger.debug("跳过排除文件: {}", file.getAbsolutePath());
continue;
}
try {
byte[] bytes = FileUtils.readFileToByteArray(file);
byte[] modified = modifyClass(bytes);
FileUtils.writeByteArrayToFile(file, modified);
logger.debug("已处理: {}", file.getName());
} catch (Exception e) {
logger.error("处理文件失败: " + file.getAbsolutePath(), e);
}
}
}
private void processJar(File inputJar, TransformOutputProvider outputProvider) throws IOException {
File outputJar = outputProvider.getContentLocation(
inputJar.getName(), getInputTypes(), getScopes(), Format.JAR
);
FileUtils.copyFile(inputJar, outputJar);
}
private boolean shouldSkip(File classFile) {
String path = classFile.getAbsolutePath().replace(File.separatorChar, '/');
for (String exclude : excludeList) {
if (path.contains(exclude)) {
return true;
}
}
return path.endsWith("/R.class") ||
path.endsWith("/BuildConfig.class") ||
path.contains("androidx/multidex/");
}
private byte[] modifyClass(byte[] original) {
try {
ClassReader reader = new ClassReader(original);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
TimeClassVisitor visitor = new TimeClassVisitor(writer);
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
return writer.toByteArray();
} catch (Exception e) {
logger.error("修改类失败", e);
return original;
}
}
}
3.2.3 TimeClassVisitor.java
-
职责: 访问类结构,筛选需要插桩的方法
-
关键功能:
- 实现 ASM ClassVisitor
- 过滤不需要插桩的类(系统类、特定包等)
- 识别并跳过特定方法(构造函数、静态块等)
- 创建 TimeMethodVisitor 处理每个方法
java
package com.example.moduleasm;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class TimeClassVisitor extends ClassVisitor {
private String className;
private final Logger logger = Logging.getLogger(TimeClassVisitor.class);
// 需要完全跳过的类名单
private static final Set<String> FULLY_EXCLUDED_CLASSES = new HashSet<>(Arrays.asList(
"android/", "androidx/", "java/", "javax/", "kotlin/",
"org/", "com/google/", "com/example/BuildConfig"
));
public TimeClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM9, classVisitor);
}
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
this.className = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (shouldFullySkipClass(className) || shouldSkipMethod(access, name, descriptor)) {
return mv;
}
logger.lifecycle("处理方法: {}.{}", className, name);
return new TimeMethodVisitor(mv, className, name, descriptor, access);
}
private boolean shouldFullySkipClass(String className) {
for (String excluded : FULLY_EXCLUDED_CLASSES) {
if (className.startsWith(excluded)) {
return true;
}
}
return false;
}
private boolean shouldSkipMethod(int access, String methodName, String descriptor) {
// 跳过构造方法和静态初始化块
if (methodName.equals("<init>") || methodName.equals("<clinit>")) {
return true;
}
// 跳过 native/abstract/synthetic 方法
if ((access & Opcodes.ACC_NATIVE) != 0 ||
(access & Opcodes.ACC_ABSTRACT) != 0 ||
(access & Opcodes.ACC_SYNTHETIC) != 0) {
return true;
}
return false;
}
}
3.2.4 TimeMethodVisitor.java
-
职责: 在方法前后插入时间统计代码
-
关键功能:
- 继承自 AdviceAdapter
- 在方法入口处插入开始时间记录
- 在方法出口处插入结束时间记录和耗时计算
- 处理局部变量,避免冲突
- 生成调用 MethodTimer 的字节码
java
package com.example.moduleasm;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
public class TimeMethodVisitor extends AdviceAdapter {
private final String methodName;
private final String className;
private int startTimeVarIndex;
private final Logger logger = Logging.getLogger(TimeMethodVisitor.class);
public TimeMethodVisitor(MethodVisitor mv, String className,
String methodName, String descriptor, int access) {
super(Opcodes.ASM9, mv, access, methodName, descriptor);
this.className = className;
this.methodName = methodName;
}
@Override
protected void onMethodEnter() {
// 记录方法开始时间
startTimeVarIndex = newLocal(Type.LONG_TYPE);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, startTimeVarIndex);
}
@Override
protected void onMethodExit(int opcode) {
if (opcode != ATHROW) {
// 记录方法结束时间
int endTimeVarIndex = newLocal(Type.LONG_TYPE);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, endTimeVarIndex);
// 计算耗时
int durationVarIndex = newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LLOAD, endTimeVarIndex);
mv.visitVarInsn(LLOAD, startTimeVarIndex);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, durationVarIndex);
// 输出日志
mv.visitLdcInsn("TimeTrace");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn("Method [" + className.replace('/', '.') + "." + methodName + "] took ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, durationVarIndex);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn("ms");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false);
mv.visitInsn(POP);
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(Math.max(maxStack, 6), maxLocals);
}
}
3.3. 发布插件到本地
在插件模块目录下运行:
bash
./gradlew publishToMavenLocal
3.4. 在项目中使用插件
项目根目录 build.gradle
groovy
buildscript {
repositories {
google()
mavenCentral()
mavenLocal() // 添加本地 Maven 仓库
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.example.asmplugin:asm-plugin:1.0.0' // 使用本地插件
}
}
App 模块 build.gradle
groovy
plugins {
id 'com.android.application'
id 'com.example.time-plugin' // 应用插件
}
android {
// 正常 Android 配置
compileSdk 33
defaultConfig {
applicationId "com.example.myapplication"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
// 启用 MultiDex
multiDexEnabled true
}
buildTypes {
debug {
// 启用调试
debuggable true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
3.5. 调试技巧
主要3个办法: 添加日志,gradle的生命周期方法,反编译看字节码的插桩效果
3.5. 1. 实时调试(无需每次发布)
在项目的 settings.gradle
中添加:
groovy
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
// 包含插件模块进行实时开发
includeBuild('../moduleAsm') // 修改为你的插件模块路径
}
这样修改插件代码后,无需重新发布,直接运行项目即可生效。
3.5.2. 查看插桩后的字节码,反编译(重点)
在transfer(),的里面有插桩的内容!在javac\debug\classes\xxxxx.class!
日志打印: 一个是在class里面的答应
一个是运行时候,在控制台,gradlew的日志打印
在 app/build/intermediates/transforms/TimeTransform/debug/
目录下可以找到插桩后的 class 文件。
使用 JD-GUI 或其他反编译工具查看插桩效果。
3.5.3. 启用详细日志
运行构建时添加 --info
或 --debug
参数:
bash
./gradlew assembleDebug --info
3.5.4. 在插件中添加调试日志
java
// 在 transform 方法中添加详细日志
@Override
public void transform(TransformInvocation invocation) throws IOException {
logger.lifecycle("=== Transform开始执行 ===");
logger.lifecycle("输入文件数量: " + invocation.getInputs().size());
logger.lifecycle("输出路径: " + invocation.getOutputProvider());
// 更多详细日志...
}
3.5.5. gradle生命周期排查(重点)
arduino
gradle.taskGraph.beforeTask { Task task ->
if (task.name.contains("Transform")) {
println "Transform任务 detected: ${task.name}"
}
}
3.5.6. 常见问题解决
问题 1: Invalid publication 'maven'
错误信息:
csharp
Invalid publication 'maven': groupId (My Application) is not a valid Maven identifier
解决方案: 使用合法的 Maven groupId,不能包含空格:
groovy
group = 'com.example.asmplugin' // 正确的格式
问题 2: ClassNotFoundException
错误信息:
css
Unable to instantiate application: java.lang.ClassNotFoundException
解决方案: 确保正确排除系统类和关键类:
java
private Set<String> createExcludeList() {
return new HashSet<>(Arrays.asList(
"android/", "androidx/", "kotlin/", "com/google/",
"javax/", "org/", "com/example/BuildConfig",
"com/example/R", "androidx/multidex/", "java/"
));
}
问题 3: ASM 版本兼容性问题
错误信息:
rust
Cannot constrain type: INT for value: v9(i) by constraint: LONG
解决方案: 使用兼容的 ASM 版本,并确保所有 ASM 依赖版本一致:
groovy
implementation 'org.ow2.asm:asm:9.4'
implementation 'org.ow2.asm:asm-commons:9.4'
3.5.7. 完整构建流程日志示例
正常构建输出应类似:
kotlin
> Task :app:transformClassesWithTimeTransformForDebug
Transform任务 detected: transformClassesWithTimeTransformForDebug
🛠️ [TimeTransform] 开始处理...
=== Transform开始执行 ===
输入文件数量: 6
输出路径: com.android.build.gradle.internal.pipeline.TransformOutputProviderImpl@5086b32d
📁 Processing directory: F:\project\app\build\intermediates\javac\debug\classes
🔍 Found 13 class files to process
⚙️ Processing: MainActivity.class
✅ [TimeTransform] 处理完成
通过以上步骤和解决方案,您应该能够成功开发、调试和部署 ASM 插桩插件。
4 Android ASM 插桩插件开发总结
4.1 自定义插件的流程
开发流程概述
- 创建插件模块:新建 Android Library 模块,配置 build.gradle 添加必要依赖
- 实现 Plugin 接口:创建 TimePlugin 类实现 Gradle Plugin 接口
- 注册 Transform:在 Plugin 中注册自定义的 Transform 类
- 实现 Transform:创建 TimeTransform 类处理字节码转换
- 实现 ASM Visitors:创建 ClassVisitor 和 MethodVisitor 进行字节码操作
- 发布插件:配置发布到 Maven Local 或远程仓库
- 应用插件:在目标项目中应用插件
详细步骤
gradle
// 1. 插件模块 build.gradle 配置
plugins {
id 'java-library'
id 'maven-publish'
id 'java-gradle-plugin'
}
dependencies {
implementation gradleApi()
implementation 'org.ow2.asm:asm:9.4'
implementation 'org.ow2.asm:asm-commons:9.4'
implementation 'com.android.tools.build:gradle:7.4.2'
}
// 2. 发布配置
publishing {
publications {
maven(MavenPublication) {
groupId = 'com.example.asmplugin'
artifactId = 'asm-plugin'
version = '1.0.0'
from components.java
}
}
}
4.2 插入的方法内容与 ASM 工具使用
ASM 字节码操作原理
ASM 通过 Visitor 模式操作字节码,需要了解 Java 字节码结构和 JVM 指令集:
java
// 方法插桩示例 - 在方法前后插入时间统计代码
@Override
protected void onMethodEnter() {
// 插入: long startTime = System.currentTimeMillis();
mv.visitMethodInsn(
INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false
);
startTimeVarIndex = newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, startTimeVarIndex);
}
@Override
protected void onMethodExit(int opcode) {
// 插入耗时计算和日志输出代码
if (opcode != ATHROW) {
// 计算 duration = endTime - startTime
// 使用 StringBuilder 构建日志信息
// 调用 Log.d 输出日志
}
}
ASM 工具使用建议
- 使用 ASM Bytecode Viewer:IntelliJ IDEA 插件,可查看字节码
- 编写测试用例:先对简单类进行插桩测试
- 逐步调试:从简单方法开始,逐步增加复杂度
- 参考官方文档:ASM 官网提供详细 API 文档和示例
4.3 Transform API 版本差异
不同 AGP 版本的 Transform API
AGP 版本 | Transform API 状态 | 推荐替代方案 |
---|---|---|
< 7.0 | 主要 API | 无 |
7.0+ | 已弃用 | AsmClassVisitorFactory |
8.0+ | 完全移除 | Instrumentation API |
兼容性处理策略
java
// 条件编译处理不同版本
dependencies {
// 根据 AGP 版本选择不同的实现方式
if (agpVersion.startsWith("7.") || agpVersion.startsWith("8.")) {
implementation "com.android.tools.build:gradle:$agpVersion"
// 使用新的 API
} else {
implementation "com.android.tools.build:gradle:$agpVersion"
// 使用传统 Transform API
}
}
新版 API 示例 (AsmClassVisitorFactory)
java
// AGP 7.0+ 推荐方式
abstract class TimeAsmFactory implements AsmClassVisitorFactory<InstrumentationParameters.None> {
@Override
public ClassVisitor createClassVisitor(
ClassContext classContext, ClassVisitor classVisitor) {
return new TimeClassVisitor(classVisitor);
}
@Override
public boolean isInstrumentableClass(ClassData classData) {
// 过滤需要插桩的类
return !classData.getClassName().startsWith("androidx/");
}
}
4.4 插桩调试与问题排查
调试策略
- 启用详细日志 :运行构建时添加
--info
或--debug
参数 - 添加调试输出:在关键位置添加日志输出
java
public void transform(TransformInvocation invocation) {
logger.lifecycle("=== Transform开始执行 ===");
logger.lifecycle("输入文件数量: " + invocation.getInputs().size());
// 更多日志...
}
- 查看字节码:使用 JD-GUI 或 javap 查看插桩后的类文件
bash
# 查看插桩后的字节码
javap -c build/intermediates/transforms/TimeTransform/debug/0/com/example/MainActivity.class
- Gradle 生命周期调试:添加任务监听器了解执行流程
gradle
// 在插件 build.gradle 中添加
gradle.taskGraph.beforeTask { Task task ->
if (task.name.contains("Transform")) {
println "Transform任务 detected: ${task.name}"
}
}
常见问题与解决方案
- ClassNotFoundException:检查排除列表,确保系统类被正确排除
- 字节码验证错误:确保 ASM 版本与 AGP 版本兼容
- 性能问题:优化排除列表,减少不必要的插桩
- 增量编译问题:正确实现 isIncremental() 方法
反编译查看插桩结果
- 找到插桩后的类文件:
app/build/intermediates/transforms/TimeTransform/
- 使用 JD-GUI 或其他反编译工具查看
- 确认插桩代码正确插入:
java
// 插桩后的方法示例
public void myMethod() {
long startTime = System.currentTimeMillis();
try {
// 原始方法逻辑
} finally {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
Log.d("TimeTrace", "Method [com.example.MyClass.myMethod] took " + duration + "ms");
}
}
总结
开发 Android ASM 插桩插件需要掌握以下关键点:
- Gradle 插件开发:了解插件结构和发布流程
- Transform API:掌握不同版本的区别和兼容性处理
- ASM 字节码操作:熟悉 Visitor 模式和字节码指令
- 调试技巧:使用日志、反编译和 Gradle 任务监控
- 兼容性考虑:正确处理系统类和第三方库的排除
通过遵循这些最佳实践,可以开发出稳定、高效的 ASM 插桩插件,为 Android 应用性能监控和优化提供有力工具。
5. 思考:如何关闭插桩的流程?
5.1. 通过 Gradle 配置开关控制
在插件中添加配置选项
typescript
// TimePlugin.java 中添加配置扩展
public class TimePlugin implements Plugin<Project> {
private TimeExtension timeExtension;
@Override
public void apply(Project project) {
// 创建扩展
timeExtension = project.getExtensions().create("timePlugin", TimeExtension.class);
project.afterEvaluate(p -> {
// 检查是否启用插件
if (timeExtension.isEnabled()) {
logger.lifecycle("⏳ [TimePlugin] 插件已启用");
// 注册 Transform
AppExtension android = project.getExtensions().getByType(AppExtension.class);
android.registerTransform(new TimeTransform(project, createExcludeList()));
} else {
logger.lifecycle("⏳ [TimePlugin] 插件已禁用");
}
});
}
}
// 配置扩展类
public class TimeExtension {
private boolean enabled = true; // 默认启用
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
在项目 build.gradle 中配置
ini
// 在 app 模块的 build.gradle 中配置
timePlugin {
enabled = false // 关闭插桩
}
// 或者根据构建类型配置
android {
buildTypes {
debug {
// 调试版本启用
timePlugin.enabled = true
}
release {
// 发布版本禁用
timePlugin.enabled = false
}
}
}
// 或者根据产品风味配置
android {
productFlavors {
dev {
// 开发版本启用
timePlugin.enabled = true
}
prod {
// 生产版本禁用
timePlugin.enabled = false
}
}
}
5. 2. 通过构建变体控制
使用变体特定的配置
arduino
// 在构建变体中禁用插件
android {
buildTypes {
release {
// 方法1: 通过扩展属性
timePlugin.enabled = false
// 方法2: 使用变体特定的插件应用
if (project.plugins.hasPlugin('com.example.time-plugin')) {
// 可以在这里移除插件,但更推荐使用配置开关
}
}
}
flavorDimensions "environment"
productFlavors {
dev {
dimension "environment"
timePlugin.enabled = true
}
prod {
dimension "environment"
timePlugin.enabled = false
}
}
}
项目案例:
自定义的插件地址: github.com/pengcaihua1...
参考博客: 官方的说明: android Matrix插桩原理 - 搜索 - 掘金
腾讯性能监控框架Matrix源码分析(十二)插桩 之MatrixTrace背景 对于APM项目,诸如像 Debug 日 - 掘金
浅谈Android Matrix使用原理前言 看了一下关于对Android性能监控框架Matrix的介绍九个模块的内容, - 掘金