android项目引用了大量第三方库后,做得好的库,会供给接口给调用方设置统一的线程池,差一点的库在内部运用统一的线程池,可是难免遇到这种库:不仅没有供给接口给调用方设置线程池,而且内部还不运用线程池,大量直接创立线程并运行。

今日就针对这种场景经过插桩实践下治理它们。

主角是agp 7.0之后供给的新api AsmClassVisitorFactory。

先界说插件

(为了方便调试,这儿没有运用独自的工程来开发插件,直接将插件界说到buildSrc里)

public class NewThreadPlugin implements Plugin<Project> {
    @Override
    public void apply(Project target) {
        AndroidComponentsExtension comp = target.getExtensions().getByType(AndroidComponentsExtension.class);
        comp.onVariants(comp.selector().all(), new Action<Component>() {
            @Override
            public void execute(Component variant) {
                variant.transformClassesWith(NewThreadVisitorFactory.class, InstrumentationScope.ALL, v -> null);
                variant.setAsmFramesComputationMode(FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS);
            }
        });
    }
}

创立注册文件 resources/META-INF/gradle-plugins/test.thread.properties

implementation-class=com.test.plugins.newthread.NewThreadPlugin

插件开发

在开发字节码插件时,对字节码不熟悉怎么办?

  1. 先了解下asm的api
  2. 经过其他工具得到方针代码的asm代码

我主要运用ide的ASM Bytecode Outline和ASM Bytecode Viewer。

如下面这段代码:

public void test7(Runnable runnable) {
    Thread thread = new Thread(runnable);
    thread.setName("test7");
    thread.start();
}

经过插件编译后的asm代码为:

methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "test7", "(Ljava/lang/Runnable;)V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(68, label0);
methodVisitor.visitTypeInsn(NEW, "java/lang/Thread");
methodVisitor.visitInsn(DUP);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Thread", "<init>", "(Ljava/lang/Runnable;)V", false);
methodVisitor.visitVarInsn(ASTORE, 2);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(69, label1);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitLdcInsn("test7");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Thread", "setName", "(Ljava/lang/String;)V", false);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(70, label2);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Thread", "start", "()V", false);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(71, label3);
methodVisitor.visitInsn(RETURN);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLocalVariable("this", "Lsg/bigo/tanwei/asm/TestCode;", null, label0, label4, 0);
methodVisitor.visitLocalVariable("runnable", "Ljava/lang/Runnable;", null, label0, label4, 1);
methodVisitor.visitLocalVariable("thread", "Ljava/lang/Thread;", null, label1, label4, 2);
methodVisitor.visitMaxs(3, 3);
methodVisitor.visitEnd();

剖析方针代码的结构,能够得出一些关键性的指令:

methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Thread", "<init>", "(Ljava/lang/Runnable;)V", false);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Thread", "start", "()V", false);

关于Thread的其他办法调用相似,然后开端写插桩逻辑并测验

插桩逻辑

  1. 经过上面的过程,咱们知道了方针代码的指令特征,先遍历字节码,然后匹配到调用代码。
  2. 匹配到方针调用代码后,将与创立Runnable实例无关的指令删去去:如new Thead,Thread.start等。
  3. 提取出Runnable实例,并将Runnable实例传给一个新的办法,在新的办法里会将这个Runnable实例丢给Executors来履行,当然也能够依据业务来决定,比如做个ab实验。
  4. 将3的指令替换掉Thread.start这句指令(当然在2里面删掉了,这儿重新增加也行;在2保存是为了方便定位)
public class NewThreadClassVisitor extends ClassNode {
    public NewThreadClassVisitor(ClassVisitor visitor) {
        super(Opcodes.ASM7);
        classVisitor = visitor;
    }
    @Override
    public void visitEnd() {
        methods.forEach(new Consumer<MethodNode>() {
            @Override
            public void accept(MethodNode methodNode) {
                handleMethodInsn(methodNode);
            }
        });
        if (classVisitor != null) {
            accept(classVisitor);
        }
    }
    private void handleMethodInsn(MethodNode methodNode) {
        InsnList insnList = methodNode.instructions;
        ListIterator<AbstractInsnNode> iterator = insnList.iterator();
        while (iterator.hasNext()) {
            AbstractInsnNode insnNode = iterator.next();
            if (insnNode instanceof MethodInsnNode) {
                MethodInsnNode methodInsn = (MethodInsnNode) insnNode;
                if (checkIsTargetInvoke(methodInsn)) {
                    removeUnusedInsn(methodNode, insnList, methodInsn);
                    MethodInsnNode newMethodInsn = new MethodInsnNode(Opcodes.INVOKESTATIC,
                            HANDLER_CLASS_NAME, HANDLER_METHOD_NAME, HANDLER_METHOD_DESC);
                    insnList.set(methodInsn, newMethodInsn);
                    System.out.println(TAG + "after handle");
                    for (AbstractInsnNode node : methodNode.instructions) {
                        System.out.println("   insn:  type=" + node.getType() + ",opCode=" + node.getOpcode() + ", toStr=" + node);
                    }
                }
            }
        }
    }
    // 仅针对Thread调用包含Runnable参数的结构办法
    private boolean checkIsTargetInvoke(MethodInsnNode methodInsn) {
        int opcode = methodInsn.getOpcode();
        boolean isTarget = (opcode == Opcodes.INVOKESPECIAL && THRED_CLASS_NAME.equals(methodInsn.owner)
                && CONSTRUCTOR_METHOD.equals(methodInsn.name));
        if (isTarget) {
            AbstractInsnNode next = methodInsn.getNext();
            if (next instanceof FieldInsnNode && next.getOpcode() == Opcodes.PUTFIELD) {
                FieldInsnNode fieldNode = (FieldInsnNode) next;
                if (THRED_TYPE_DESC.equals(fieldNode.desc)) {
                    System.out.println(TAG + "checkIsTargetInvoke ignore: next is PUTFIELD: name=" + fieldNode.name + ",desc=" + fieldNode.desc);
                    return false;
                }
            } else {
                // 仅结构Thread可是未调用start,例如仅仅创立对象并返回
                if (next.getOpcode() == Opcodes.ARETURN) {
                    return false;
                }
                boolean callStart = false;
                while (next != null) {
                    if (next.getOpcode() >= Opcodes.IRETURN && next.getOpcode() <= Opcodes.RETURN) {
                        break;
                    }
                    if (next instanceof MethodInsnNode) {
                        MethodInsnNode node = (MethodInsnNode) next;
                        if (THRED_CLASS_NAME.equals(node.owner) && START_METHOD_NAME.equals(node.name)) {
                            callStart = true;
                            break;
                        }
                    }
                    next = next.getNext();
                }
                if (!callStart) {
                    System.out.println(TAG + "checkIsTargetInvoke ignore: not call start");
                    return false;
                }
            }
            String desc = methodInsn.desc;
            System.out.println("\n");
            if (desc.contains(RUNNABLE_CLASS_NAME)) {
                System.out.println(TAG + "checkIsTargetInvoke desc " + desc);
                return true;
            } else {
                System.out.println(TAG + "checkIsTargetInvoke ignore: no Runnable param");
                return false;
            }
        }
        return false;
    }
    // 找出当前指令前后的无效指令并删去
    private void removeUnusedInsn(MethodNode methodNode, InsnList list, MethodInsnNode anchor) {
        List<AbstractInsnNode> removeList = new ArrayList<>();
        removeList.addAll(findConstructParamInsn(list,anchor));
        int size = removeList.size();
        System.out.println(TAG + "remove pre insn size " + size);
        removeList.addAll(findLocalVarUsageInsn(list, anchor));
        System.out.println(TAG + "remove next insn size " + (removeList.size() - size));
        for (AbstractInsnNode node : removeList) {
            list.remove(node);
        }
    }
    // 找出当前指令运用的aload参数指令
    private List<AbstractInsnNode> findConstructParamInsn(InsnList list, MethodInsnNode anchor) {
        List<AbstractInsnNode> removeList = new ArrayList<>();
        List<String> params = parseParamFromDesc(anchor.desc);
        int size = params.size();
        if (size > 0) {
            // 删去结构相关无效指令,如NEW,DUP
            System.out.println(TAG + "pre:remove insn");
            AbstractInsnNode pre = anchor.getPrevious();
            while (pre != null) {
                int opcode = pre.getOpcode();
                int type = pre.getType();
                // 开始指令
                if (opcode == Opcodes.NEW) {
                    if (pre instanceof TypeInsnNode && THRED_CLASS_NAME.equals(((TypeInsnNode) pre).desc)) {
                        removeList.add(pre);
                        AbstractInsnNode next = pre.getNext();
                        if (next != null && next.getOpcode() == Opcodes.DUP) {
                            removeList.add(next);
                        }
                        break;
                    }
                } else if (type == AbstractInsnNode.LDC_INSN) {
                    LdcInsnNode ldc = (LdcInsnNode) pre;
                    if (checkLDCParam(params, ldc.cst)) {
                        removeList.add(pre);
                    }
                }
                pre = pre.getPrevious();
            }
        } else {
            System.out.println(TAG + "pre:constructor use no params desc= " + anchor.desc);
        }
        return removeList;
    }
    private List<AbstractInsnNode> findLocalVarUsageInsn(InsnList list, MethodInsnNode anchor){
        List<AbstractInsnNode> removeList = new ArrayList<>();
        // 删去后边无效指令,如后续的调用
        AbstractInsnNode next = anchor.getNext();
        if (next == null) {
            return removeList;
        }
        if (next instanceof MethodInsnNode) {
            // new xxx().start()
            MethodInsnNode method = (MethodInsnNode) next;
            if (method.getOpcode() == Opcodes.INVOKEVIRTUAL && THRED_CLASS_NAME.equals(method.owner)) {
                removeList.add(method);
            }
        } else if (next instanceof VarInsnNode && next.getOpcode() == Opcodes.ASTORE) {
            // xxx = new XXX
            // xxx.start()
            removeList.add(next);
            VarInsnNode var = (VarInsnNode) next;
            int operand = var.var;
            next = next.getNext();
            boolean loadVar = false;
            while (next != null) {
                // 删去后续调用
                if (next instanceof VarInsnNode && next.getOpcode() == Opcodes.ALOAD && ((VarInsnNode) next).var == operand) {
                    loadVar = true;
                } else if (loadVar && next instanceof MethodInsnNode && THRED_CLASS_NAME.equals(((MethodInsnNode) next).owner)) {
                    AbstractInsnNode pre = next;
                    while (pre != null) {
                        removeList.add(pre);
                        if (pre.getType() == AbstractInsnNode.LABEL) {
                            break;
                        }
                        pre = pre.getPrevious();
                    }
                    loadVar = false;
                }
                next = next.getNext();
            }
        } else {
            // new xxx(new yyy()).start()
            if (next.getType() == AbstractInsnNode.LABEL) {
                ArrayList<AbstractInsnNode> tempList = new ArrayList<>();
                tempList.add(next);
                next = next.getNext();
                while (next != null) {
                    tempList.add(next);
                    if (next.getType() == AbstractInsnNode.METHOD_INSN) {
                        MethodInsnNode method = (MethodInsnNode) next;
                        if (method.getOpcode() == Opcodes.INVOKEVIRTUAL && THRED_CLASS_NAME.equals(method.owner)) {
                            break;
                        }
                    }
                    next = next.getNext();
                }
                removeList.addAll(tempList);
            }
        }
        return removeList;
    }
    private List<String> parseParamFromDesc(String desc) {
        int start = desc.indexOf('(');
        int end = desc.indexOf(')');
        String content = desc.substring(start + 1, end);
        System.out.println(TAG + "parse params from desc " + desc + ",param " + content);
        return parseParam(content);
    }
}

插桩效果

测验代码

public class AsmTest {
    public static final String TAG = "asmTest";
    private Thread mThread;
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Log.v(TAG, "runnable run on " + Thread.currentThread().getName());
        }
    };
    public void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.v(TAG, "runnable run test");
            }
        }, "test").start();
    }
    public void test2() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Log.v(TAG, "runnable run test2");
            }
        };
        new Thread(runnable).start();
    }
    public void test3() {
        Thread thread = new Thread(runnable);
        thread.setName("test3");
        thread.start();
    }
    public void test4() {
        mThread = new Thread(runnable, "test4");
        mThread.start();
    }
    public void test5() {
        new Thread(runnable).start();
    }
    public void test6(Runnable runnable) {
        new Thread(runnable).start();
    }
    public void test7(Runnable runnable) {
        Thread thread = new Thread(runnable);
        thread.setName("test7");
        thread.start();
    }
    public void test8() {
        new Thread("test8").start();
    }
    public Thread newThread(Runnable runnable) {
        return new Thread(runnable);
    }
}

插桩后反编译代码

public class AsmTest {
    public static final String TAG = "asmTest";
    private Thread mThread;
    private Runnable runnable = new Runnable() { // from class: com.barran.sample.asmtest.AsmTest.1
        @Override // java.lang.Runnable
        public void run() {
            Log.v(AsmTest.TAG, "runnable run on " + Thread.currentThread().getName());
        }
    };
    public void test() {
        AsmHandler.handleNewThread(new Runnable() { // from class: com.barran.sample.asmtest.AsmTest.2
            @Override // java.lang.Runnable
            public void run() {
                Log.v(AsmTest.TAG, "runnable run test");
            }
        });
    }
    public void test2() {
        Runnable runnable = new Runnable() { // from class: com.barran.sample.asmtest.AsmTest.3
            @Override // java.lang.Runnable
            public void run() {
                Log.v(AsmTest.TAG, "runnable run test2");
            }
        };
        AsmHandler.handleNewThread(runnable);
    }
    public void test3() {
        AsmHandler.handleNewThread(this.runnable);
    }
    public void test4() {
        Thread thread = new Thread(this.runnable, "test4");
        this.mThread = thread;
        thread.start();
    }
    public void test5() {
        AsmHandler.handleNewThread(this.runnable);
    }
    public void test6(Runnable runnable) {
        AsmHandler.handleNewThread(runnable);
    }
    public void test7(Runnable runnable) {
        AsmHandler.handleNewThread(runnable);
    }
    public void test8() {
        new Thread("test8").start();
    }
    public Thread newThread(Runnable runnable) {
        return new Thread(runnable);
    }
}

现在仅仅处理了局部变量的景象,假如将Thread对象界说为成员变量,会愈加的复杂,而且简单引出更多问题,这儿略过;能够在插件里经过日志记录这些case然后挨个剖析其他可行的处理方式。

更完整的代码
见demo参考