返回

应用性能管理之 APM - Android 的可观测性工具

Android

随着项目中对 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 的示例代码。希望对你有帮助。