当前位置: 移动技术网 > 移动技术>移动开发>Android > Android 通过ASM实现多次点击拦截

Android 通过ASM实现多次点击拦截

2020年08月14日  | 移动技术网移动技术  | 我要评论
从事Android开发的同学可能都会有这个需求,最近在学ASM相关的知识,拿这个想法练了一下手。大体思路是这样的,通过字节码Hook所有onClick(View view)方法,通过view.setTag(key,value)设置tag为当前时间戳,这样再次点击的时候就有一个时间差,通过对这个时间差,可以过滤掉多余的响应操作。首先我们看一下lamba表达式和普通的setOnClickListener编译完是什么样的。由截图可以看到不管我们以哪种方式设置监听点击,最终都是一个实现View.OnCl

从事Android开发的同学可能都会有这个需求,最近在学ASM相关的知识,拿这个想法练了一下手。大体思路是这样的,通过字节码Hook所有onClick(View view)方法,通过view.setTag(key,value)设置tag为当前时间戳,这样再次点击的时候就有一个时间差,通过对这个时间差,可以过滤掉多余的响应操作。

首先我们看一下lamba表达式和普通的setOnClickListener编译完是什么样的。

由截图可以看到不管我们以哪种方式设置监听点击,最终都是一个实现View.OnClickListener接口的静态内部类,由此我们可以Hook所有实现了View.OnClickListener接口的类中的名字为onClick,签名为(Landroid/view/View;)V的方法,在方法前面插入我们想要的代码。具体实现是这样的:

定义一个ClassVisitor:

class MutiClickHandleVisitor(classVisitor: ClassVisitor): ClassVisitor(Opcodes.ASM5,classVisitor) {


    private val classFullName = "android/view/View\$OnClickListener"
    private var isMatchClass = false

    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<String>
    ) {
        super.visit(version, access, name, signature, superName, interfaces)
        isMatchClass = matchClass(interfaces, classFullName)
    }

    override fun visitMethod(
        access: Int,
        name: String,
        desc: String,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        val mv = cv.visitMethod(access, name, desc, signature, exceptions)
        if (isMatchClass && matchMethod(name, desc)){
            return MutiClickHandleMethodAdapter(mv)
        }
        return mv
    }


    private fun matchMethod(name: String, desc: String): Boolean {
        return name == "onClick" && desc == "(Landroid/view/View;)V"
    }

    private fun matchClass(
        interfaces: Array<String>,
        classFullName: String
    ): Boolean {
        var isMatch = false
        // 是否满足实现的接口
        for (anInterface in interfaces) {
            if (anInterface == classFullName) {
                isMatch = true
                break
            }
        }
        return isMatch
    }

}

其中matchMethod方法就是确保名字和签名符合预期即 name =="onClick" && desc =="(Landroid/view/View;)V"

接着定义一个MethodVisitor:

class MutiClickHandleMethodAdapter(methodVisitor: MethodVisitor) : MethodVisitor(Opcodes.ASM5,methodVisitor) {

    override fun visitCode() {
        super.visitCode()

        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
        mv.visitVarInsn(Opcodes.LSTORE, 2)

        mv.visitVarInsn(Opcodes.ALOAD, 1)
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "android/view/View", "getId", "()I", false)
        mv.visitVarInsn(Opcodes.ISTORE, 4)

        mv.visitVarInsn(Opcodes.ALOAD, 1)
        mv.visitVarInsn(Opcodes.ILOAD, 4)
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "android/view/View", "getTag", "(I)Ljava/lang/Object;", false)
        mv.visitVarInsn(Opcodes.ASTORE, 5)

        mv.visitVarInsn(Opcodes.ALOAD, 1)
        mv.visitVarInsn(Opcodes.ILOAD, 4)
        mv.visitVarInsn(Opcodes.LLOAD, 2)
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false)
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "android/view/View", "setTag", "(ILjava/lang/Object;)V", false)


        mv.visitVarInsn(Opcodes.ALOAD, 5)
        val l5 = Label()
        mv.visitJumpInsn(Opcodes.IFNULL, l5)

        mv.visitVarInsn(Opcodes.ALOAD, 5)
        mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Long")
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false)
        mv.visitVarInsn(Opcodes.LSTORE, 6)

        mv.visitVarInsn(Opcodes.LLOAD, 2)
        mv.visitVarInsn(Opcodes.LLOAD, 6)
        mv.visitInsn(Opcodes.LSUB)
        mv.visitLdcInsn(1500L)
        mv.visitInsn(Opcodes.LCMP)
        mv.visitJumpInsn(Opcodes.IFGE, l5)

        mv.visitInsn(Opcodes.RETURN);
        mv.visitLabel(l5)
        mv.visitFrame(
            Opcodes.F_APPEND,
            3,
            arrayOf<Any>(
                Opcodes.LONG,
                Opcodes.INTEGER,
                "java/lang/Object"
            ),
            0,
            null
        )

    }

}

这里还有一些插件开发的常识,这里就不多说了,百度一下很多。等于一切都配置好了,我们来看下插桩后的代码长啥样。

好了到这里多次点击拦截过滤功能就实现了。

奉上源码

本文地址:https://blog.csdn.net/s127838498/article/details/107968840

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网