Android Hook入门
Hook 英文意思是钩子,可以把一段执行着的代码钩下来,然后加入我们自己的逻辑,最后在放回去。比如我们可以Hook住一段系统代码,在执行系统代码之前加入我们自己的逻辑。
Hook技术主要用到java反射和java动态代理两个知识点,下面来个简单的例子,我们来Hook一个按钮的点击事件
1 | Button button = findViewById(R.id.btn_click); |
点击按钮的时候,我们如何能在不改变上面代码的前提下,在Toast弹出之前执行一些别的逻辑呢?比如现在Toast弹出的是按钮上的字,我们动态的把子给它改了
思路就是我们通过反射拿到系统中的OnClickListener的对象,通过动态代理,创建一个该对象的代理对象,这个代理对象中就可以写一些别的逻辑啦,最后把这个代理对象通过反射设置回系统中来个偷梁换柱就可以啦。
OK下面开始按照步骤来
第一步
反射拿到设置的OnClickListener对象,上面的代码中,我们进入setOnClickListener方法可以看到1
2
3
4
5
6public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
这里把我们传进来的回调对象设置给了getListenerInfo().mOnClickListener。1
2
3
4
5
6
7ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
getListenerInfo()方法返回一个ListenerInfo对象,它内部就保存了我们传过来的mOnClickListener回调对象。我们功过反射执行getListenerInfo方法就能拿到ListenerInfo对象了如下1
2
3
4
5
6Class<?> viewClass = Class.forName("android.view.View");
//需要拿到setOnClickListener方法set过去的对象
Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");
getListenerInfoMethod.setAccessible(true);
//本质是ListenerInfo对象
Object listenerInfo = getListenerInfoMethod.invoke(view);
拿到了ListenerInfo对象,在通过它拿到它的成员变量mOnClickListener1
2
3Class<?> listenerInfoClass = Class.forName("android.view.View$ListenerInfo");
Field onClickListenerField = listenerInfoClass.getField("mOnClickListener");
final Object onClickListener = onClickListenerField.get(listenerInfo);
第二步
使用动态代理定义一个代理的onClickListener,在回调函数中添加一些别的逻辑1
2
3
4
5
6
7
8
9
10Object proxyClickListener = Proxy.newProxyInstance(MainActivity.class.getClassLoader(),
//需要将监听的方法
new Class[]{View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Button button = new Button(MainActivity.this);
button.setText("我就点你");
return method.invoke(onClickListener, button);
}
});
在InvocationHandler方法中就可以添加别的逻辑了,这里添加了Toast用来测试,最后返回一个代理的OnClickListener对象。
第三步
把代理的OnClickListener对象设置回系统中,在第一步中已经拿到了mOnClickListener对象,最后把它给改了就好了。
1 | onClickListenerField.set(listenerInfo,proxyClickListener); |
OK Hook完成,效果
完整代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31/**
* hook
* @param view 需要hook的view
*/
private void hook(View view) {
try {
Class<?> viewClass = Class.forName("android.view.View");
//需要拿到setOnClickListener方法set过去的对象
Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");
getListenerInfoMethod.setAccessible(true);
Object listenerInfo = getListenerInfoMethod.invoke(view);
Class<?> listenerInfoClass = Class.forName("android.view.View$ListenerInfo");
Field onClickListenerField = listenerInfoClass.getField("mOnClickListener");
final Object onClickListener = onClickListenerField.get(listenerInfo);
Object proxyClickListener = Proxy.newProxyInstance(MainActivity.class.getClassLoader(),
//需要将监听的方法
new Class[]{View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Button button = new Button(MainActivity.this);
button.setText("我就点你");
return method.invoke(onClickListener, button);
}
});
onClickListenerField.set(listenerInfo,proxyClickListener);
} catch (Exception e) {
e.printStackTrace();
}
}