欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-04-14 11:19:13
从事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编译完是什么样的。

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

由截图可以看到不管我们以哪种方式设置监听点击,最终都是一个实现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
        )

    }

}

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

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

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

奉上源码

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