ASM插桩--多线程运行监测

3,450 阅读5分钟

最近需要优化App启动的时间,现有代码存在以下问题:

  1. 线程未复用(使用new Thread\HandlerThread),创建线程数过多
  2. 使用HandlerThread,使用后未销毁(Looper一直等待),占用内存
  3. 提早start线程,却未使用
  4. 部分业务方过早初始化业务代码(虽然是异步),影响启动时间

由于存在上述问题,需要扫描App从冷启动开始到首页展示出来,中间执行的子线程和主线程执行的情况。

需要监测的数据如下:

  1. 创建的线程情况,包括数量和使用情况
  2. 执行的runnable.run、AsyncTask.doInBackground等函数的执行时间

对于创建的线程数,Android Studio的profiler->cpu->threads中可以看出,但是我手机只要一打开profiler,就卡的不行,根本没法用。

基于上述需求,使用ASM对线程代码进行插桩。

  1. 执行时间:计算run、doInBackground等的执行时间很简单,只需要在这些方法前面和最后面加入代码,然后就可以计算时间。
  2. 统计线程数:由于Thread是系统代码,无法在Thread.run方法中进行插桩,也无法在Thread.start后面插代码,因为像线程池这种情况,也都是系统代码。只能在业务代码中进行插桩。因此考虑在以下代码中插入代码:
  • Runnable.run
  • AsyncTask.doInBackground
  • Callable.call
  • Handler.handleMessage、Handler.Callback.handleMessage
  • Thread.run
  • TimerTask.run

在这些方法都是运行在线程中,在这些方法中可以通过Thread.currentThread()获取到当前线程数据。 这样已经能覆盖到大部分情况了,因为大部分都是有任务才会创建线程,使用线程和创建线程的时间是挨着的。但是有一种特殊情况就是HandlerThread,他可以先创建,然后Looper一直等待直到有任务才执行,因此如果该looper一直等不到任务,用刚刚的办法就统计不到,因此这种情况要特殊处理。我们要逐行扫描是否有new HandlerThread 的代码,如果扫描到的话,也要统计到线程创建里去。

到这里,基本上讲完我们线程插桩的思路了。那么如何插桩呢?就是使用ASM库,他的原理是在class文件打包到dex之前,使用Gradle中的Transform对class文件进行插桩(具体可以自行查询,这里不细讲)。 我们要利用gradle的插件对代码进行插桩,如何进行插件开发呢?有以下两种方式:

  1. 本工程中创建buildSrc模块(该模块名字专门用于插件开发)进行开发。
  2. 独立工程开发 具体可以查看该文章

本章采用第一种,在demo工程中创建了buildSrc。然后创建一个插件groovy文件:

class ThreadInjectPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        def android
        if (project.plugins.hasPlugin(AppPlugin)) {
            android = project.extensions.getByType(AppExtension)
        } else {
            android = project.extensions.getByType(LibraryExtension)
        }
        //处理runnable等方法
        android.registerTransform(new ThreadRunTransform())
        //处理new HandlerThread的
        android.registerTransform(new HandlerThreadTransform())
    }
}

重点是这两个Transform,第一个Transform就是扫描到runnable这些,在函数前(onMethodEnter)和函数结束(onMethodExit)插入代码。第二个Transform是专门处理new HandlerThread的。

先看ThreadRunTransform:

class ThreadRunTransform extends Transform {

    @Override
    String getName() {
        return "ThreadTransform"
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT)
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation)
        transformInvocation.inputs.each { TransformInput input ->
            input.directoryInputs.each { DirectoryInput directoryInput ->
                transformDirectory(directoryInput, transformInvocation.outputProvider)
            }
        }
    }

    private void transformDirectory(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
        if (directoryInput.file.isDirectory()) {
            directoryInput.file.eachFileRecurse { File file ->
                def className = file.name
                def path = file.path
                if (isAppClass(className, path)) {
                    try {
                        FileInputStream fileInputStream = new FileInputStream(file.getAbsolutePath())
                        //-------------重点代码,拿到class类代码,通过ClassVisitor来扫描,并插入代码
                        ClassReader classReader = new ClassReader(fileInputStream)
                        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                        ClassVisitor visitor = new ThreadRunVisitor(className, classWriter)
                        classReader.accept(visitor, ClassReader.EXPAND_FRAMES)
                        byte[] code = classWriter.toByteArray();
                        FileOutputStream fos = new FileOutputStream(file.parentFile.absolutePath + File.separator + className)
                        fos.write(code)
                        fos.close()
                        //------------重点代码结束------------------
                    } catch (Exception e) {

                    }
                }

            }
        }

        def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
        FileUtils.copyDirectory(directoryInput.file, dest)
    }

    private boolean isAppClass(String className, String path) {
        //检查该class是不是app工程下的包,不包括第三方的包
        return className.endsWith(".class") && !className.contains("R\$") && !"R.class".equals(className) && !"BuildConfig.class".equals(className);
    }
}
public class ThreadRunVisitor extends ClassVisitor {

    private String className;
    private boolean needInject;

    public ThreadRunVisitor(String className, ClassVisitor classVisitor) {
        super(Opcodes.ASM5, classVisitor);
        this.className = className;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        //判断是否需要给该类的方法注入
        this.needInject = isInjectClass(className, interfaces, superName);
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor methodVisitor = this.cv.visitMethod(access, name, descriptor, signature, exceptions);
        boolean isInject = this.needInject && isInjectMethod(name, descriptor);
        if (!isInject) {
            return methodVisitor;
        }
        return (MethodVisitor) new AdviceAdapter(groovyjarjarasm.asm.Opcodes.ASM5, methodVisitor, access, name, descriptor) {

            @Override
            protected void onMethodEnter() {
                super.onMethodEnter();
                //在方法前插入你想要插入的代码(这个代码在你app工程里)
                this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "runStart", "()V", false);
            }

            @Override
            protected void onMethodExit(int opcode) {
                //在方法结束时插入你想要插入的代码(这个代码在你app工程里)
                this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "runEnd", "()V", false);

            }
        };

    }

    public boolean isInjectClass(String className, String[] interfaces, String superName) {
        if (className == null)
            return false;
        //1、支持runnable和android.os.Handler.Callback
        if (interfaces != null) {
            for (String inter : interfaces) {
                if ("java/lang/Runnable".equals(inter)
                        || "android/os/Handler$Callback".equals(inter))
                    return true;
            }
        }
        //2、支持ExtendsAsyncTask
        if ("android/os/AsyncTask".equals(superName)) {
            return true;
        }
        //3、支持Handler.handleMessage
        if ("android/os/Handler".equals(superName)) {
            return true;
        }
        //4、支持Thread.run
        if ("java/lang/Thread".equals(superName)) {
            return true;
        }
        return false;
    }

    public boolean isInjectMethod(String methodName, String methodDesc) {
        if (methodName == null || methodDesc == null)
            return false;
        //1、runnable和thread的run方法
        if (methodName.equals("run") && methodDesc.equals("()V"))
            return true;
        //2、extendedAsyncTask.doInBackground方法
        if (methodName.equals("doInBackground"))
            return true;
        //3、handler和callback的handleMessage方法
        if (methodName.equals("handleMessage")) {
            return methodDesc.equals("(Landroid/os/Message;)V") || methodDesc.equals("(Landroid/os/Message;)Z");
        }
        return false;
    }
}

这里说明一下,在以下代码中,new Runnable这个会被编译为内部类,在代码扫描时,会创建两个类,分别是Test.class和Test$1.class,并且分别被扫描:

public class Test{
    void test(){
        new Thread(new Runnable(){
            @override
            public void run(){
                //xxxx
            }
        }).start();
    }
}

来看第二个Transform,由于不清楚是哪个方法中会存在new HandlerThread,无法像第一个Transform那样根据method的名字和desc来匹配,只能逐行扫描。因此这里需要用到ClassNode来扫描,获取到这个类的methods列表,然后再拿到每个method的instructions(被编译后执行的指令),从指令中去分析中有没有这个语句。

HandlerThreadTransform的代码和ThreadRunTransform的代码类似,看下重点代码部分:

private void transformDirectory(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
    if (directoryInput.file.isDirectory()) {
        directoryInput.file.eachFileRecurse { File file ->
            def className = file.name
            def path = file.path
            if (isAppClass(className, path)) {
                try {
                    FileInputStream fileInputStream = new FileInputStream(file.getAbsolutePath())
                    ClassReader classReader = new ClassReader(fileInputStream)
                    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                    ClassVisitor visitor = new HandlerThreadVisitor(classReader,classWriter)
                    classReader.accept(visitor, ClassReader.EXPAND_FRAMES)
                    byte[] code = classWriter.toByteArray();
                    FileOutputStream fos = new FileOutputStream(file.parentFile.absolutePath + File.separator + className)
                    fos.write(code)
                    fos.close()
                } catch (Exception e) {
                    e.printStackTrace()
                }
            }
        }
    }
    def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
    FileUtils.copyDirectory(directoryInput.file, dest)
}

首先在HandlerThreadVisitor的visit方法中,需要先扫描出来该类中需要被插桩的方法列表。 如何判断该方法是否需要被插桩呢?当通过ClassNode去解析一个class文件时,可以通过classNode.methods拿到该类的方法列表。遍历列表,拿到MethodNode,取出每个方法被编译后能被JVM执行的instructions(指令,AbstractInsnNode)。 每个AbstractInsnNode中,都会有一个int值:opcode(具体值在org.objectweb.asm.Opcodes中),这个指令能说明当前执行的内容。举例说明:

加载变量的opcode数值:
Opcodes.ILOAD(加载int类型的变量)=21
Opcodes.LLOAD(加载long类型的变量)=22
Opcodes.FLOAD(加载float类型的变量)=23
Opcodes.DLOAD(加载double类型的变量)=24
Opcodes.ALOAD(加载引用类型的变量)=25

给某类型的变量赋值
Opcodes.ISTORE = 54、LSTORE = 55、FSTORE = 56、DSTORE = 57、ASTORE = 58

调用某个类的方法:
Opcodes.INVOKESTATIC(调用static方法) = 184
Opcodes.INVOKEVIRTUAL(调用实例的方法,非static)=182
Opcodes.INVOKESPECIAL(调用实例的构造方法,非static)=183
Opcodes.INVOKEDYNAMIC(lambda脱糖方法,后续会解释)=186

创建变量
Opcodes.NEW(加载某个类的构造函数)

举个代码的例子:

public void test(){
    HandlerThread ht = new HandlerThread("xxx");
    ht.start();
}

上述这段代码会被编译成如下指令
//new handlerThread("xxx")这一句被翻译成如下指令
TypeInsnNode(opcodes:187, desc:android/os/HandlerThread) 
LdcInsnNode(opcodes:18, cst:xx) ----加载常量
MethodInsnNode(opcodes:183, owner:android/os/HandlerThread, name:<init>, desc:(Ljava/lang/String;)V)    --调用构造方法
VarInsnNode(opcodes:58, var:1) ----将上面构造方法创建的对象,赋值给第二个变量(第一个是该类的this,var是按照变量创建顺序,如果方法有参数,会排在this位置后)

//ht.start()被翻译成如下指令
VarInsnNode(opcodes:25, var:1) ----加载第二个变量,即thread变量
MethodInsnNode(opcodes:182, owner:android/os/HandlerThread, name:start, desc:()V)   ----调用加载变量的start方法。

我们想要的监测效果代码效果如下:

public void test(){
    HandlerThread ht = new HandlerThread("xx");
    ht.start();
    AopUtil.addThread(ht);    //插入我们自己的监测代码

因为Thread在初始化是,其thread id和thread name已经确定。因此当我们检测到thread.start方法执行后,在其后面追加如下指令即可:

VarInsnNode(opcodes:25, var:1) ----加载thread变量
MethodInsnNode(opcodes:184, owner:com/example/project/AopUtil, name:addThread, desc:(Ljava/lang/Thread;)V)

当然我们要在调用start方法前,记住编译器加载的是哪个变量,也就是记住VarInsnNode.opcodes为25(OpCodes.ALOAD)时,VarInsnNode.var的值,方便我们后续加载这个变量去调用我们的插桩方法。

那么问题来了,有时候业务方代码是这么写的:

new HandlerThread("xxx").start();

这段代码被翻译成指令如下:
TypeInsnNode(opcodes:187, desc:android/os/HandlerThread) 
LdcInsnNode(opcodes:18, cst:xx) ----加载常量
MethodInsnNode(opcodes:183, owner:android/os/HandlerThread, name:<init>, desc:(Ljava/lang/String;)V)    --调用构造方法
MethodInsnNode(opcodes:182, owner:android/os/HandlerThread, name:start, desc:()V)   ----调用加载变量的start方法。

和刚刚唯一的区别就是这段指令少了Opcodes.ASTORE和Opcodes.ALOAD

此时该方法中没有Thread的变量,因此我们要增加指令,在start指令前,增加创建变量(newLocal)、存储对象(Opcodes.ASTORE)、读取变量(Opcodes.ALOAD)指令即可。因此我们要记住在start方法前的指令,若指令直接为调用init构造方法的指令,则需要增加刚刚说的指令,若在start方法前,调用的是ALOAD指令,那我们只需要记住ALOAD指令中的var参数即可。

看下核心代码(稍后有全部代码):

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor methodVisitor = this.cv.visitMethod(access, name, descriptor, signature, exceptions);
        boolean injectLambda = hasLambda(name);
        boolean isInject = injectMethods.contains(new Method(name, descriptor));
        if (!isInject && !injectLambda) {
            return methodVisitor;
        }
        return new AdviceAdapter(groovyjarjarasm.asm.Opcodes.ASM5, methodVisitor, access, name, descriptor) {

            int lastThreadVarIndex = -1; //记住thread变量的位置
            String lastThreadInstruction;  //上一条执行thread的instruction

            @Override
            public void visitVarInsn(int opcode, int var) {
                super.visitVarInsn(opcode, var);
                if(isInject) {
                    if (opcode == ALOAD) {
                        lastThreadInstruction = VISIT_VAR_INSN_LOAD;
                        lastThreadVarIndex = var;
                    }
                }
            }

            @Override
            public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
                if (isInject) {
                    if (!THREAD.equals(owner) && !HANDLER_THREAD.equals(owner)) {
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                        return;
                    }
                    if (!"<init>".equals(name) && !"start".equals(name)) {
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                        return;
                    }
                    //如果走到了thread.start或者是handler thread.start方法
                    if ("<init>".equals(name)) {
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                        lastThreadInstruction = VISIT_METHOD_THREAD_INIT;
                    } else if ("start".equals(name)) {
                        //先检测之前thread是否被存储为本地变量
                        if (lastThreadInstruction.equals(VISIT_METHOD_THREAD_INIT)) {
                            //如果start的上一句话是init,则说明thread没有被存储为本地变量,那么创建本地变量
                            Type threadType = Type.getObjectType("java/lang/Thread");
                            lastThreadVarIndex = newLocal(threadType);
                            this.mv.visitVarInsn(ASTORE, lastThreadVarIndex);
                            this.mv.visitVarInsn(ALOAD, lastThreadVarIndex);
                        }
                        //继续调用start方法
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                        if (lastThreadVarIndex > 0) {
                            //拿到上一个thread变量
                            this.mv.visitVarInsn(ALOAD, lastThreadVarIndex);
                            //获取thread id值
//                        this.mv.visitMethodInsn(INVOKEVIRTUAL, owner, "getId", "()J", false);
                            this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "addThread", "(Ljava/lang/Thread;)V", false);
                        }
                    }
                } else {
                    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                }
            }
        };
    }

讲完这里,大部分情况已经实现,接下来将ASM对lambda表达式的处理。

class Java8 {
  interface Logger {
    void log(String s);
  }

  public static void main(String... args) {
    sayHi(s -> System.out.println(s));
  }

  private static void sayHi(Logger logger) {
    logger.log("Hello!");
  }
}


上述代码在编译后变成:
 public class Java8 {
    interface Logger {
        void log(String s);
    }

    public static void main(String... args) {
		//这里使用 Logger 的实现类 Java8$1
        sayHi(s -> new Java8$1());
    }

    private static void sayHi(Logger logger) {
        logger.log("Hello!");
    }

    //方法体中的内容移到这里
    static void lambda$main$0(String str){
        System.out.println(str);
    }
}

public class Java8$1 implements Java8.Logger {
    public Java8$1(){
    }

    @Override
    public void log(String s) {
		//这里调用 Java8 方法的静态方法
        Java8.lambda$main$0(s);
    }
}

在main函数中,会有一个Opcodes.INVOKEDYNAMIC的指令(InvokeDynamicInsnNode),查看下该指令中的参数:

QQ20210711-0.png 首先我们判断该指令中的desc是不是包含java/lang/Runnable,且name为run,如果匹配成功,则获取该方法被脱糖后真正的执行函数(bsmArgs[1]),在该函数中增加我们插桩代码。 查看具体代码:

public class HandlerThreadVisitor extends ClassVisitor {

    public static final String HANDLER_THREAD = "android/os/HandlerThread";
    public static final String THREAD = "java/lang/Thread";
    private final String VISIT_VAR_INSN_LOAD = "visitVarInsn-Load";
    private final String VISIT_METHOD_THREAD_INIT = "visitMethod-ThreadInit";

    private ClassNode classnode;
    ArrayList<Method> injectMethods = new ArrayList<>();
    ArrayList<String> lambdaMethods = new ArrayList<>();

    public HandlerThreadVisitor(ClassReader classReader, ClassVisitor classVisitor) {
        super(Opcodes.ASM5, classVisitor);
        classnode = new ClassNode();
        classReader.accept(classnode, 0);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        getInjectMethods(classnode);
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor methodVisitor = this.cv.visitMethod(access, name, descriptor, signature, exceptions);
        boolean injectLambda = hasLambda(name);
        boolean isInject = injectMethods.contains(new Method(name, descriptor));
        if (!isInject && !injectLambda) {
            return methodVisitor;
        }
        return new AdviceAdapter(groovyjarjarasm.asm.Opcodes.ASM5, methodVisitor, access, name, descriptor) {

            int lastThreadVarIndex = -1;
            String lastThreadInstruction;

            @Override
            protected void onMethodEnter() {
                super.onMethodEnter();
                if (injectLambda) {
                    this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "runStart", "()V", false);
                }
            }

            @Override
            protected void onMethodExit(int opcode) {
                super.onMethodExit(opcode);
                if (injectLambda) {
                    this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "runEnd", "()V", false);
                }
            }

            @Override
            public void visitVarInsn(int opcode, int var) {
                super.visitVarInsn(opcode, var);
                if(isInject) {
                    if (opcode == ALOAD) {
                        lastThreadInstruction = VISIT_VAR_INSN_LOAD;
                        lastThreadVarIndex = var;
                    }
                }
            }

            @Override
            public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
                if (isInject) {
                    if (!THREAD.equals(owner) && !HANDLER_THREAD.equals(owner)) {
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                        return;
                    }
                    if (!"<init>".equals(name) && !"start".equals(name)) {
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                        return;
                    }
                    //如果走到了thread.start或者是handler thread.start方法
                    if ("<init>".equals(name)) {
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                        lastThreadInstruction = VISIT_METHOD_THREAD_INIT;
                    } else if ("start".equals(name)) {
                        //先检测之前thread是否被存储为本地变量
                        if (lastThreadInstruction.equals(VISIT_METHOD_THREAD_INIT)) {
                            //如果start的上一句话是init,则说明thread没有被存储为本地变量,那么创建本地变量
                            Type threadType = Type.getObjectType("java/lang/Thread");
                            lastThreadVarIndex = newLocal(threadType);
                            this.mv.visitVarInsn(ASTORE, lastThreadVarIndex);
                            this.mv.visitVarInsn(ALOAD, lastThreadVarIndex);
                        }
                        //继续调用start方法
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                        if (lastThreadVarIndex > 0) {
                            //拿到上一个thread变量
                            this.mv.visitVarInsn(ALOAD, lastThreadVarIndex);
                            this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "addThread", "(Ljava/lang/Thread;)V", false);
                        }
                    }
                } else {
                    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                }
            }
        };
    }

    public void getInjectMethods(ClassNode classnode) {
        for (MethodNode method : classnode.methods) {
            for (int i = 0; i < method.instructions.size(); i++) {
                AbstractInsnNode insnNode = method.instructions.get(i);
                if (insnNode.getOpcode() == Opcodes.NEW) {
                    TypeInsnNode methodInsnNode = (TypeInsnNode) insnNode;
                    if (HANDLER_THREAD.equals(methodInsnNode.desc) || THREAD.equals(methodInsnNode.desc)) {
                        injectMethods.add(new Method(method.name, method.desc));
                    }
                } else if (insnNode instanceof InvokeDynamicInsnNode) {
                    //判断是否为runnable的lambda表达式
                    if (((InvokeDynamicInsnNode) insnNode).desc.contains("Ljava/lang/Runnable;")
                            && ((InvokeDynamicInsnNode) insnNode).name.equals("run")) {
                        lambdaMethods.add(((Handle) ((InvokeDynamicInsnNode) insnNode).bsmArgs[1]).getName());
                    }
                }
            }
        }
    }

    private boolean hasLambda(String name){
        for (int i = 0; i < lambdaMethods.size(); i++) {
            if (lambdaMethods.get(i).contains(name)) {
                return true;
            }
        }
        return false;
    }

    static class Method {
        String name;
        String desc;

        public Method(String name, String desc) {
            this.name = name;
            this.desc = desc;
        }

        @Override
        public boolean equals(Object o) {
            Method temp = (Method) o;
            return name.equals(temp.name) && desc.equals(temp.desc);
        }
    }
}

至此全部代码已经讲完。看下我们AopUtils中的代码:

public class AopUtil {

    static HashSet<Long> allThread = new HashSet<>();
    static HashSet<Long> usedThread = new HashSet<>();
    static ConcurrentHashMap<String, Long> threadRunStartTime = new ConcurrentHashMap<>();

    public static void runStart() {
        logThreadUsage(Thread.currentThread(), true);
        threadRunStartTime.put(getKey(), System.currentTimeMillis());
    }

    private static String getKey(){
        String stackTrace = Log.getStackTraceString(new Throwable());
        stackTrace = stackTrace.split("\n\t")[3];   //获取到第几行执行run函数,作为key存储
        return stackTrace.substring(0,stackTrace.indexOf("("));
    }

    public static void runEnd() {
        String key = getKey();
        Long start = threadRunStartTime.get(key);
        if (start != null) {
            Log.d("ThreadAop-runCost", key + "cost time:" + (System.currentTimeMillis() - start));
            threadRunStartTime.remove(key);
        }
    }

    public static void addThread(Thread thread) {
        logThreadUsage(thread, false);
    }

    private static void logThreadUsage(Thread thread, boolean isFromRun) {
        if (thread.getName().equals("main")) {
            return;
        }
        synchronized (AopUtil.class) {
            if (usedThread == null) {
                usedThread = new HashSet<>();
            }
            if (isFromRun) {
                Log.d("ThreadAop-used1", "thread is used: " + thread.getId() + ", name is " + thread.getName());
                usedThread.add(thread.getId());
            }
            if (allThread == null) {
                allThread = new HashSet<>();
            }
            if (allThread.contains(thread.getId())) {
                Log.d("ThreadAop", Log.getStackTraceString(new Throwable()));
                return;
            }
            allThread.add(thread.getId());
            Log.d("ThreadAop-1", "current size:" + allThread.size() + "  add new thread:" + thread.getName() + ", usedThread:" + usedThread.size());
            Log.d("ThreadAop", Log.getStackTraceString(new Throwable()));
        }
    }
}

现在插件已经开发完成,在我们工程里引入:

首先在buildSrc模块中,添加如下文件:

743A5B02-16BB-4B06-A042-5F67D7C213D5.png

然后在app模块中的build.gradle中增加如下代码:

plugins {
    id 'thread-inject'
}

或者是apply plugin: 'thread-inject'

打包运行app后,就可以看到我们插桩代码的日志输出了。至此代码讲述完毕。

ASM如果大家不熟悉的话,推荐一个工具:Android Studio->Preferences->Plugins,安装如下插件:ASM Bytecode Viewer,安装完后重启。重启Android Studio之后,在工程的build目录里,随便找一个.class文件,右键选择:

WechatIMG82.png

WechatIMG84.png