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
保存在 mListenerInfo
的mOnClickListener
中。通过getListenerInfo()
方法来获取mListenerInfo
对象。由于getListenerInfo()
是缺省方法,ListenerInfo
是缺省类,所以都需要通过反射获取和设置,然后给mOnClickListener
属性设置点击事件代理对象。
- 获取
mListenerInfo
对象。
// 反射获取 getListenerInfo() 方法对象
val mListenerInfoMethod = View::class.java.getDeclaredMethod("getListenerInfo")
// 设置可修改
mListenerInfoMethod.isAccessible = true
// 执行 invoke,获取 mListenerInfo
val mListenerInfo = mListenerInfoMethod.invoke(view)
- 获取
mListenerInfo
中保存的mOnClickListener
//反射 mOnClickListener 属性
val mOnClickListenerField = mListenerInfo::class.java.getDeclaredField("mOnClickListener")
//设置可修改
mOnClickListenerField.isAccessible = true
// 获取`mListenerInfo`中保存的`mOnClickListener`
val mOnClickListener = mOnClickListenerField.get(mListenerInfo)
- 生成
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
- 把生成的代理对象设置给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
}