前言
在Android应用开发中,性能监控一直是开发者关注的重点。方法级别的耗时统计能帮助我们快速定位性能瓶颈,优化应用响应速度。
传统的性能监控方案往往需要手动埋点或使用第三方SDK,存在代码侵入性强、维护成本高等问题。
本文将介绍一个基于ASM字节码插桩技术的Android方法耗时监控插件 ------ MethodTimeMonitor,它能够在编译时自动为应用方法添加耗时统计代码,实现零侵入的性能监控。
项目概述
MethodTimeMonitor是一个现代化的Android Gradle插件,专为AGP 8.0+版本设计。项目采用模块化架构,主要包含两个核心组件:
- method-timer-plugin: Gradle插件模块,负责字节码插桩
- method-timer-runtime: 运行时库,提供统计功能和API
核心特性
- ✅ 零代码侵入: 基于ASM字节码插桩,无需修改业务代码
- ✅ 高精度计时: 使用System.nanoTime()提供纳秒级精度
- ✅ 智能过滤: 自动过滤系统类和框架类,减少性能影响
- ✅ 线程安全: 使用ConcurrentHashMap和AtomicLong保证多线程安全
- ✅ 丰富统计: 支持调用次数、平均耗时、最大/最小耗时等多维度统计
- ✅ 自动化报告: 支持自动打印和按需查看统计结果
技术架构深度解析
1. 字节码插桩架构
插件基于AGP 8.0+的新版Transform API,使用AsmClassVisitorFactory进行字节码操作:
csharp
// MethodTimerPlugin.kt:16-31
androidComponents.onVariants { variant ->
if (extension.enabled.get()) {
variant.instrumentation.transformClassesWith(
MethodTimerClassVisitorFactory::class.java,
InstrumentationScope.ALL
) { params ->
params.enabled.set(extension.enabled)
params.mainThreadOnly.set(extension.mainThreadOnly)
params.minDuration.set(extension.minDuration)
}
variant.instrumentation.setAsmFramesComputationMode(
FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
)
}
}
2. 智能过滤机制
为避免对系统性能造成影响,插件实现了智能过滤机制:
kotlin
// MethodTimerClassVisitorFactory.kt:35-44
override fun isInstrumentable(classData: ClassData): Boolean {
return !classData.className.startsWith("android.") &&
!classData.className.startsWith("androidx.") &&
!classData.className.startsWith("java.") &&
!classData.className.startsWith("kotlin.") &&
!classData.className.contains("R$") &&
!classData.className.endsWith("R")
}
3. 方法插桩实现
使用ASM的AdviceAdapter在方法入口和出口插入计时代码:
方法入口插桩:
kotlin
// MethodTimerClassVisitor.kt:52-72
override fun onMethodEnter() {
super.onMethodEnter()
recordStartTime()
}
private fun recordStartTime() {
// 调用 System.nanoTime() 获取开始时间
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false)
// 将开始时间存储到局部变量中
startTimeVar = newLocal(Type.LONG_TYPE)
mv.visitVarInsn(LSTORE, startTimeVar)
}
方法退出插桩:
// MethodTimerClassVisitor.kt:74-116
override fun onMethodExit(opcode: Int) {
if (startTimeVar != -1) {
// 计算耗时并调用记录方法
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false)
mv.visitVarInsn(LLOAD, startTimeVar)
mv.visitInsn(LSUB) // 结束时间 - 开始时间
mv.visitLdcInsn(1000000L)
mv.visitInsn(LDIV) // 转换为毫秒
// 调用MethodTimer.recordMethod
mv.visitMethodInsn(INVOKESTATIC,
"com/hql/methodtimer/runtime/MethodTimer",
"recordMethod", "(Ljava/lang/String;JJZ)V", false)
}
super.onMethodExit(opcode)
}
4. 运行时统计系统
线程安全的统计实现
使用ConcurrentHashMap和原子类保证多线程环境下的数据一致性:
java
// MethodTimer.java:20-21
private static final ConcurrentHashMap<String, MethodStats> methodStatsMap = new ConcurrentHashMap<>();
// MethodStats内部使用原子类
public static class MethodStats {
private final AtomicLong totalDuration = new AtomicLong(0);
private final AtomicLong callCount = new AtomicLong(0);
private volatile long maxDuration = 0;
private volatile long minDuration = Long.MAX_VALUE;
}
主线程检测
通过Looper判断当前线程类型,支持仅监控主线程方法:
typescript
// MethodTimer.java:88-90
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
自动打印机制
支持按方法数量阈值自动打印统计结果:
scss
// MethodTimer.java:64-72
private static void checkAutoPrint() {
if (autoPrintEnabled && methodStatsMap.size() >= autoPrintThreshold) {
if (methodStatsMap.size() == autoPrintThreshold) {
Log.d(TAG, "=== 自动打印统计信息 (方法数量达到" + autoPrintThreshold + ") ===");
printAllStats();
}
}
}
使用方法和最佳实践
快速集成
- 添加插件依赖:
scss
buildscript {
dependencies {
classpath("io.github.hequanli:method-timer-plugin:1.0.0")
}
}
- 应用插件并配置:
c
plugins {
id("com.methodtimer.plugin")
}
dependencies {
implementation("io.github.hequanli:method-timer-runtime:1.0.0")
}
methodTimer {
enabled.set(true) // 启用插件
mainThreadOnly.set(true) // 只监控主线程
minDuration.set(10L) // 只记录超过10ms的方法
}
统计结果示例
插件会自动输出详细的方法耗时统计:
性能建议
- 版本控制: 建议只在Debug版本中启用,Release版本关闭
- 阈值设置: 通过minDuration过滤短时间方法,减少日志噪音