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

Posted Android Developer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASM插桩--多线程运行监测相关的知识,希望对你有一定的参考价值。

最近需要优化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, 0)
                        ClassVisitor visitor = new ThreadRunVisitor(className, classWriter)
                        classReader.accept(visitor, 0)
                        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, 0)
                    ClassVisitor visitor = new HandlerThreadVisitor(classReader,classWriter)
                    classReader.accept(visitor, 0)
                    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),查看下该指令中的参数:

首先我们判断该指令中的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模块中,添加如下文件:

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

plugins 
    id 'thread-inject'


或者是apply plugin: 'thread-inject'
复制代码

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

本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

以上是关于ASM插桩--多线程运行监测的主要内容,如果未能解决你的问题,请参考以下文章

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

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

字节码插桩AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )

ASM字节码插桩

Transform+ASM插桩系列——熟悉Java字节码

Transform+ASM插桩系列——熟悉Java字节码