Hook View 的点击事件

Hook View 的点击事件

前言

Hook 大法好啊

原理

反射 + 动态代理 用点击监听代理对象替换给View设置的点击监听。

通过调用View.setOnclick(View.OnClickListener)设置其点击监听后,View类在ListenerInfo中保存了点击事件对象。通过 反射 获取到ListenerInfo对象,通过 动态代理 生成新的View.OnClickListener代理对象,然后将代理对象设置给反射到的引用,完成点击监听的替换。

View.java关键类和方法

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
        
·····省略其他代码····


// 获取当前View的 ListenerInfo
@UnsupportedAppUsage
    ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }
    
// 设置点击事件,这里是把点击事件保存到了 ListenerInfo 中
public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
    
// 保存各 View 设置的各个事件的类    
static class ListenerInfo {

·····省略其他代码····

    // View的点击事件 
    @UnsupportedAppUsage
    public OnClickListener mOnClickListener;
    
·····省略其他代码····
}
    
·····省略其他代码····
}

通过 View 的源码可知,给View设置的点击监听OnClickListener保存在 mListenerInfomOnClickListener中。通过getListenerInfo()方法来获取mListenerInfo对象。由于getListenerInfo()是缺省方法,ListenerInfo是缺省类,所以都需要通过反射获取和设置,然后给mOnClickListener属性设置点击事件代理对象。

  1. 获取mListenerInfo对象。
// 反射获取 getListenerInfo() 方法对象
val mListenerInfoMethod = View::class.java.getDeclaredMethod("getListenerInfo")

// 设置可修改
mListenerInfoMethod.isAccessible = true

// 执行 invoke,获取 mListenerInfo
val mListenerInfo = mListenerInfoMethod.invoke(view)
  1. 获取mListenerInfo中保存的mOnClickListener
//反射 mOnClickListener 属性
val mOnClickListenerField = mListenerInfo::class.java.getDeclaredField("mOnClickListener")

//设置可修改
mOnClickListenerField.isAccessible = true

// 获取`mListenerInfo`中保存的`mOnClickListener` 
val mOnClickListener = mOnClickListenerField.get(mListenerInfo)
                
  1. 生成 View.OnClickListener 的动态代理对象
val proxyClick = Proxy.newProxyInstance(mOnClickListener::class.java.classLoader, arrayOf(
                        View.OnClickListener::class.java),object : InvocationHandler {
                        override fun invoke(
                            proxy: Any?,
                            method: Method?,
                            vararg args: Any?
                        ): Any? {
                            Log.d(TAG, "invoke: before")
                            val result = method?.invoke(mOnClickListener,*args)
                            Log.d(TAG, "invoke: after")
                            return result
                        }

                    }) as? View.OnClickListener
  1. 把生成的代理对象设置给View
mOnClickListenerField.set(mListenerInfo,proxyClick)

mOnClickListenerField.isAccessible = false

具体实现

Hook 某个 Activity 中所有 View 的点击事件

/**
     * Hook 某个 Activity 中所有 View 的点击事件
     *
     * @param delegate
     */
    fun hookViewClick(delegate: AppCompatDelegate) {
        val contentView = delegate.findViewById<ViewGroup>(android.R.id.content)
        contentView?.let {
            LogUtil.log(it.childCount.toString())
            // 获取所有view
            val views = findAllViews(contentView)
            LogUtil.log("all views size $views")

            val mListenerInfoMethod = View::class.java.getDeclaredMethod("getListenerInfo")
            mListenerInfoMethod.isAccessible = true
            // 遍历view,反射获取点击事件
            views.forEach {  view ->
                val mListenerInfo = mListenerInfoMethod.invoke(view)
                val mOnClickListenerField = mListenerInfo::class.java.getDeclaredField("mOnClickListener")
                mOnClickListenerField.isAccessible = true
                val mOnClickListener = mOnClickListenerField.get(mListenerInfo)

                if (mOnClickListener != null) {
                    val proxyClick = Proxy.newProxyInstance(mOnClickListener::class.java.classLoader, arrayOf(
                        View.OnClickListener::class.java),object : InvocationHandler {
                        override fun invoke(
                            proxy: Any?,
                            method: Method?,
                            vararg args: Any?
                        ): Any? {
                            Log.d(TAG, "invoke: before")
                            val result = method?.invoke(mOnClickListener,*args)
                            Log.d(TAG, "invoke: after")                            return result
                        }

                    }) as? View.OnClickListener
                    mOnClickListenerField.set(mListenerInfo,proxyClick)
                    mOnClickListenerField.isAccessible = false

                }

            }
            mListenerInfoMethod.isAccessible = false
        }

    }

    private fun findAllViews(root: View): MutableList<View> {
        val views = mutableListOf<View>()

        if (root is ViewGroup) {
            for (child in root.children) {
                views.addAll(findAllViews(child))
            }
        }
        views.add(root)
        return views
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值