应用性能管理之 APM - Android 的可观测性工具
2023-12-31 11:12:24
随着项目中对 APM (Application Performance Management) 越来越关注,诸如像 Debug 日志,运行耗时监控等都会陆陆续续加入到源码中,随着功能的增多,这些监控日志代码在某种程度上会影响甚至是干扰业务代码的阅读,笔者于是查阅有没有一些可以自动完成注入日志代码的技术,最终发现了 ASM (Android Support Multidexing)。
ASM 可以将新代码和原先的代码进行合并,形成新的类文件,这样就实现了自动注入的需求,但是我们还需要实现业务代码和注入代码的兼容,也就是需要生成一个新的字段或方法,使得可以在原有代码中调用。
首先,编写一个我们自己的 Transform,要继承 Transform 类,然后复写 transform 方法,在 transform 方法中,可以遍历所有的 class 文件,获取每个 class 文件的字节码,然后使用 ASM 的操作来修改字节码,修改完成后,将修改后的字节码重新写入到新的 class 文件中。
transform 代码如下:
public class InjectTransform extends Transform {
@Override
public void transform(Context context, Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) {
try {
for (TransformInput input : inputs) {
for (JarInput jarInput : input.getJarInputs()) {
ClassReader cr = new ClassReader(jarInput.getBytes());
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
// 遍历类
cr.accept(new ClassVisitor(Opcodes.ASM7, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
// 查找需要注入的方法
if (name.equals("onCreate") && desc.equals("(Landroid/os/Bundle;)V")) {
// 注入代码
mv = new MethodVisitor(Opcodes.ASM7, mv) {
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "android/util/Log", "d", "Ljava/lang/String;");
mv.visitLdcInsn("TAG");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false);
mv.visitInsn(Opcodes.POP);
}
};
}
return mv;
}
}, ClassReader.EXPAND_FRAMES);
// 将修改后的字节码重新写入到新的 class 文件中
byte[] code = cw.toByteArray();
FileOutputStream fos = new FileOutputStream(outputProvider.getContentLocation(jarInput.getFile().getAbsolutePath(),
jarInput.getFile().getAbsolutePath(), TransformOutputProvider.SUBFOLDER_SECONDARY));
fos.write(code);
fos.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String getName() {
return "InjectTransform";
}
@Override
public Set<ContentType> getInputTypes() {
return Collections.singleton(ContentType.JAR);
}
@Override
public Set<? super ContentType> getOutputTypes() {
return Collections.singleton(ContentType.JAR);
}
@Override
public boolean isIncremental() {
return false;
}
@Override
public void setParameterInputs(List<File> parameterInputs) {
}
@Override
public Collection<File> getParameterInputs() {
return null;
}
}
编写一个用来生成新字段或方法的 ClassWriter,要继承 ClassWriter 类,然后复写 visitField 和 visitMethod 方法,在 visitField 和 visitMethod 方法中,可以分别生成新的字段或方法,然后将生成的字段或方法添加到 ClassWriter 中。
ClassWriter 代码如下:
public class InjectClassWriter extends ClassWriter {
public InjectClassWriter(ClassReader classReader, int flags) {
super(classReader, flags);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, String[] exceptions) {
FieldVisitor fv = super.visitField(access, name, desc, signature, exceptions);
// 生成新的字段
if (name.equals("sDebugTag")) {
fv = new FieldVisitor(Opcodes.ASM7, fv) {
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return new AnnotationVisitor(Opcodes.ASM7) {
@Override
public void visit(String name, Object value) {
if (name.equals("value")) {
// 生成字段值
value = "sDebugTagValue";
}
super.visit(name, value);
}
};
}
};
}
return fv;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
// 生成新的方法
if (name.equals("test") && desc.equals("()V")) {
mv = new MethodVisitor(Opcodes.ASM7, mv) {
@Override
public void visitCode() {
super.visitCode();
// 生成方法体
mv.visitFieldInsn(Opcodes.GETSTATIC, "com/example/test/MainActivity", "sDebugTag", "Ljava/lang/String;");
mv.visitLdcInsn("TAG");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false);
mv.visitInsn(Opcodes.POP);
mv.visitInsn(Opcodes.RETURN);
}
};
}
return mv;
}
}
将我们编写的 Transform 和 ClassWriter 集成到项目中,在 build.gradle 文件中添加如下代码:
android {
defaultConfig {
// ...
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath = true
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
// ...
implementation 'org.ow2.asm:asm:7.3.3'
annotationProcessor 'com.android.tools.build:gradle-apm-transforms:4.3.0'
}
运行项目,在日志中可以看到注入的日志输出:
D/TAG: MainActivity
以上就是使用 Transform 和 ASM 来实现 APM 的示例代码。希望对你有帮助。