Hook系统源码实现权限管理架构。
简单的一个hook思想体现:
1. 准备工作
点击按钮,在不更改源代码的基础上,动态修改button
文字内容。
Button button = findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "button 文本内容: " + ((Button) v).getText());
}
});
思路:通过hook
思想,在onClick()
方法执行前,修改代码即可。即 将View.OnClickListener
接口对象替换成自己的代理对象。
public void setOnClickListener(@Nullable OnClickListener l) {
// ....
getListenerInfo().mOnClickListener = l;
}
即 set进去的变量l
最终赋值给getListenerInfo().mOnClickListener
也就是android.view.View.ListenerInfo#mOnClickListener
变量
2. 代理一个View.OnClickListener
接口对象
/**
* 1. 动态代理 onClickListenerProxy实质是被监听的OnClickListener接口
*/
Object onClickListenerProxy = Proxy.newProxyInstance(
getClassLoader(),
// 要监听的接口,监听什么接口就返回什么接口
new Class[]{View.OnClickListener.class},
// 监听接口方法的回调,
new InvocationHandler() {
/**
* void onClick(View v);
* @param proxy
* @param method 对应 onClick(View v);
* @param args 对应 v
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 加入自己的逻辑,
Log.e(TAG, "hook: 拦截到了OnClickListener#onClick");
Button button = new Button(MainActivity.this);
button.setText("hello hook");
// 让程序继续执行
/**
* method.invoke()相当于 OnClickListener 中的onClick()方法
* @param obj OnClickListener对象
* @param args
*/
return method.invoke(onClickListenerObj, button);
}
}
);
通过button.setOnClickListener
设置的接口类对象,查看源码最终赋值给ListenerInfo
的mOnClickListener
变量。需要找到该变量。
3. 通过反射找到ListenerInfo的mOnClickListener
属性变量
通过反射替换掉View$ListenerInfo
内部类的mOnClickListener
属性。
先找打ListenerInfo
类
// 首先找到View$ListenerInfo
Class<?> listenerInfoClass = Class.forName("android.view.View$ListenerInfo");
找到ListenerInfo
类的mOnClickListener
变量
// getDeclaredField 获取私有属性 2.getField获取公开属性
Field onClickListenerFiled = listenerInfoClass.getField("mOnClickListener");
4. 替换
将步骤3中找打的onClickListenerFiled
属性替换为 自己代理的onClickListenerProxy
onClickListenerFiled.set(listenerInfoObj, onClickListenerProxy);
缺少onClickListenerFiled
属性的类对象listenerInfoObj
。查看源码,通过执行android.view.View#getListenerInfo
方法可以返回一个ListenerInfo
对象。代码如下:
// 获取 ListenerInfo对象,执行 ListenerInfo getListenerInfo() 方法就可以获得对象
Class<?> classView = Class.forName("android.view.View");
Method getListenerInfoMethod = classView.getDeclaredMethod("getListenerInfo");
//拿到方法之后,给方法授权 getListenerInfoMethod
getListenerInfoMethod.setAccessible(true);
//执行方法 getListenerInfo
/**
* @param obj 方法对应的对象 也就是 getListenerInfo 方法对应的View对象, view.getListenerInfo()
*/
Object listenerInfoObj = getListenerInfoMethod.invoke(view);
// 拿到字段对应的对象
final Object onClickListenerObj = onClickListenerFiled.get(listenerInfoObj);
执行结果:
2020-02-27 10:17:27.367 14405-14405/com.purang.hook E/MainActivity: hook: 拦截到了OnClickListener#onClick
2020-02-27 10:17:27.369 14405-14405/com.purang.hook E/MainActivity: button 文本内容: hello hook