基础知识准备:
1.如何实现gradle插件
2.什么是Transform以及如何使用?
3.什么是ASM以及如何使用?下面请阅读:https://www.jianshu.com/p/16ed4d233fd1 (大佬树下好乘凉 ,上来就是一顿神操作)
注:以下场景只是入门示例 希望通过这几个示例能够触类旁通 了解一些ASM的基础应用和API调用
以下为一些场景的使用
在下面示例中的字节码中会有一些如下指令:
LINENUMBER 34 L0
L0 L1 L2 L3
遇见 LINENUMBER 直接忽略它的存在
L0 L1 一般方法内添加代码块的情况下才会使用 例如if判断 trycatch等, 无代码块的时候忽略即可,具体使用看示例即可
1.方法计时统计
//方法计时
public fun setContent(){
val startTime = System.currentTimeMillis()
//......
//doSomething
//......
val endTime = System.currentTimeMillis()
Log.e("TAG" ,"执行setContent方式耗时:${endTime - startTime}")
}
//上述统计代码转化为字节码后的内容
LINENUMBER 34 L0
INVOKESTATIC java/lang/System.currentTimeMillis ()J
LSTORE 1
L1
LINENUMBER 35 L1
INVOKESTATIC java/lang/System.currentTimeMillis ()J
LSTORE 3
L2
LINENUMBER 36 L2
LDC "TAG"
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
LDC "\u6267\u884csetContent\u65b9\u5f0f\u8017\u65f6\uff1a"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LLOAD 3
LLOAD 1
LSUB
INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
POP
L3
//MethodVisitor具体插入细节
class TransformTimeMethodVisitor extends MethodVisitor{
TransformTimeMethodVisitor(int api, MethodVisitor mv) {
super(api, mv)
}
@Override
void visitCode() {
super.visitCode()
//方法前插入
//val startTime = System.currentTimeMillis()
mv.visitMethodInsn(Opcodes.INVOKESTATIC,"java/lang/System","currentTimeMillis","()J",false)
mv.visitVarInsn(Opcodes.LSTORE,1)
}
@Override
void visitInsn(int opcode) {
//方法结束处
if (opcode == Opcodes.RETURN){
//val endTime = System.currentTimeMillis()
mv.visitMethodInsn(Opcodes.INVOKESTATIC,"java/lang/System","currentTimeMillis","()J",false)
mv.visitVarInsn(Opcodes.LSTORE,3)
//Log.e("TAG" ,"执行setContent方式耗时:${endTime - startTime}")
mv.visitLdcInsn("TAG")
mv.visitTypeInsn(Opcodes.NEW,"java/lang/StringBuilder")
mv.visitInsn(Opcodes.DUP)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/StringBuilder","","()V",false)
mv.visitLdcInsn("\u6267\u884csetContent\u65b9\u5f0f\u8017\u65f6\uff1a")
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append","(Ljava/lang/String;)Ljava/lang/StringBuilder;",false)
mv.visitVarInsn(Opcodes.LLOAD,3)
mv.visitVarInsn(Opcodes.LLOAD,1)
mv.visitInsn(Opcodes.LSUB)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append","(J)Ljava/lang/StringBuilder;",false)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","toString","()Ljava/lang/String;",false)
mv.visitMethodInsn(Opcodes.INVOKESTATIC,"android/util/Log","e","(Ljava/lang/String;Ljava/lang/String;)I",false)
mv.visitInsn(Opcodes.POP)
}
super.visitInsn(opcode)
}
}
2.onClick事件添加防快速点击
注意:onClick方法名也不一定是View.OnClickListener的onClick 所以在指定onClick时需要指定下方法的所属 具体实现是在过滤方法时添加判断 指定下onClick的方法的参数以及返回类型信息
//示例代码
public fun click(){
val bt = findViewById(R.id.mainBt);
bt.setOnClickListener{
//此处if分支为即将插入的代码
if (FastClickUtils.isFastClick()){
return@setOnClickListener
}
//doSomething
}
}
//需要插入代码的字节码
LINENUMBER 41 L0
GETSTATIC com/example/utils/FastClickUtils.Companion : Lcom/example/utils/FastClickUtils$Companion;
INVOKEVIRTUAL com/example/utils/FastClickUtils$Companion.isFastClick ()Z
IFEQ L1
L2
LINENUMBER 42 L2
RETURN
L1
//MethodVisitor中的具体实现
class TransformOnclickMethodVisitor extends MethodVisitor{
TransformOnclickMethodVisitor(int api, MethodVisitor mv) {
super(api, mv)
}
private Label L1 = new Label()
@Override
void visitCode() {
super.visitCode()
//方法前插入
mv.visitFieldInsn(Opcodes.GETSTATIC,"com/example/utils/FastClickUtils","Companion","Lcom/example/utils/FastClickUtils\$Companion;")
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"com/example/utils/FastClickUtils\$Companion","isFastClick","()Z",false)
mv.visitJumpInsn(Opcodes.IFEQ,L1)
mv.visitInsn(Opcodes.RETURN)
mv.visitLabel(L1)
}
@Override
void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN){
}
super.visitInsn(opcode)
}
}
3.给方法加try catch (适用场景:某些三方sdk造成的崩溃没有捕获)
//捕获异常
fun tryCatch(){
//异常代码
try {
val number = 10/0
}catch (e: Exception){
e.printStackTrace()
}
}
//上述代码转字节码
TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
L3
LINENUMBER 52 L3
L0
NOP
L4 //L4 此处为运算部分 不需要再次插入 val number = 10/0
LINENUMBER 53 L4
BIPUSH 10
ICONST_0
IDIV
ISTORE 1
L1
GOTO L5
L2
LINENUMBER 54 L2
ASTORE 1
L6
LINENUMBER 55 L6
ALOAD 1
INVOKEVIRTUAL java/lang/Exception.printStackTrace ()V
L7
LINENUMBER 56 L7
L5
class TransformTryCatchMethodVisitor extends MethodVisitor{
private def l0 = new Label(),l1 = new Label(),l2 = new Label()
TransformTryCatchMethodVisitor(int api, MethodVisitor mv) {
super(api, mv)
}
@Override
void visitCode() {
super.visitCode()
//全局加tryCatch try{ 部分
mv.visitTryCatchBlock(l0,l1,l2,"java/lang/Exception")
mv.visitLabel(l0)
mv.visitInsn(Opcodes.NOP)
}
@Override
void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN){
// } catch(e : Exception) { e.printStackTrace() } 部分
mv.visitLabel(l1)
def label = new Label()
mv.visitJumpInsn(Opcodes.GOTO,label)
visitLabel(l2)
mv.visitVarInsn(Opcodes.ASTORE,1)
mv.visitVarInsn(Opcodes.ALOAD,1)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/Exception","printStackTrace","()V",false)
mv.visitLabel(label)
}
super.visitInsn(opcode)
}
}
4.替换new Thread 为自定义的Thread
替换的核心思想
1.找到 NEW 指令 将NEW 指令后的Thread对象替换为自己的Thread
2.替换构造函数的对象 每个对象都至少都会有一个默认的构造函数以下面字节码为例
NEW java/lang/Thread
INVOKESPECIAL java/lang/Thread.(Ljava/lang/Runnable;)V
需要将java/lang/Thread 替换为自己的Thread全路径名 eq: com/xxx/xxx/MyThread
//替换Thread
private fun replaceThread(){
Thread{
}.start()
}
//创建Thread对象的字节码内容
L2
LINENUMBER 51 L2
NEW java/lang/Thread //创建对象指令
DUP
GETSTATIC com/example/aaaa/MainActivity$replaceThread$1.INSTANCE : Lcom/example/aaaa/MainActivity$replaceThread$1;
CHECKCAST java/lang/Runnable
INVOKESPECIAL java/lang/Thread. (Ljava/lang/Runnable;)V //初始化构造函数
L3
LINENUMBER 52 L3
INVOKEVIRTUAL java/lang/Thread.start ()V
//具体替换过程
class TransformThreadMethodVisitor extends MethodVisitor{
private final static String THREAD = "java/lang/Thread" //系统Thread的路径
private final static String MY_THREAD = "com/example/aaaa/MyThread" //自定义Thread的路径
TransformThreadMethodVisitor(int api, MethodVisitor mv) {
super(api, mv)
}
@Override
void visitTypeInsn(int opcode, String type) {
//替换NEW指令的Thread
if (opcode == Opcodes.NEW && type == THREAD){
mv.visitTypeInsn(Opcodes.NEW,MY_THREAD)
return
}
super.visitTypeInsn(opcode, type)
}
@Override
void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
//替换init的Thread
if (owner == THREAD && name.equalsIgnoreCase("")){
mv.visitMethodInsn(opcode,MY_THREAD,name,descriptor,isInterface)
return
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
@Override
void visitCode() {
super.visitCode()
}
@Override
void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN){
}
super.visitInsn(opcode)
}
}
Demo
Demo地址