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
插件开发
在开发字节码插件时,对字节码不熟悉怎么办?
- 先了解下asm的api
- 经过其他工具得到方针代码的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的其他办法调用相似,然后开端写插桩逻辑并测验
插桩逻辑
- 经过上面的过程,咱们知道了方针代码的指令特征,先遍历字节码,然后匹配到调用代码。
- 匹配到方针调用代码后,将与创立Runnable实例无关的指令删去去:如new Thead,Thread.start等。
- 提取出Runnable实例,并将Runnable实例传给一个新的办法,在新的办法里会将这个Runnable实例丢给Executors来履行,当然也能够依据业务来决定,比如做个ab实验。
- 将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参考