此时该方法中没有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'
# **学习分享**
在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
**2021最新上万页的大厂面试真题**
![image](https://www.icode9.com/i/ll/?i=img_convert/95a67fb22a3d695280085d7165508367.png)
**七大模块学习资料:如NDK模块开发、Android框架体系架构...**
![image](https://www.icode9.com/i/ll/?i=img_convert/c664773193f90ca3b84deb5198f2eaa3.png)
2021大厂面试真题:
![image](https://www.icode9.com/i/ll/?i=img_convert/e0b0ad5f851dfc8092ea625a4a0e8437.png)
只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](
)**
> **本文已被[腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)收录,自学资源及系列文章持续更新中...**
{
id 'thread-inject'
}
或者是apply plugin: 'thread-inject'
# **学习分享**
在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
**2021最新上万页的大厂面试真题**
[外链图片转存中...(img-dpltVccT-1631247528229)]
**七大模块学习资料:如NDK模块开发、Android框架体系架构...**
[外链图片转存中...(img-ZTajorw9-1631247528230)]
2021大厂面试真题:
[外链图片转存中...(img-Ztk9Sk42-1631247528232)]
只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](
)**
> **本文已被[腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)收录,自学资源及系列文章持续更新中...**