原谅我见识短浅,Android竟然还能这么玩!!
什么是Hook
首先我们来了解一下,什么是Hook?Hook有什么用?
我们都知道有一种设计模式 - 模板方法模式,原理是父类定义多个方法,但是不实现任何内容,然后将这些方法组合起来,来实现某种功能。
其中的没有实现的方法我们就称之为“Hook”钩子方法,主要目的是留给子类定制自己的东西。
而这里的Hook有点不一样,运用反射技术和代理模式,以实现改变Android系统的API的功能,这里称为API Hook技术。
网上说的都是这么一个例子,因为比较容易记住,所以我也打算用这个例子。说是要在所有View的点击事件都要打印出日志。依我们正常的做法,挨个找出View的setOnClickListener方法,在设置的监听器中都加上打印日志。但是,项目中如果有很多很多个类,然后还有很多很多的View,估计程序不崩,你都崩了。
这里主要的解决办法是,统一处理OnClickListener监听器的onClick方法。
基本思路是创建一个监听器的代理HookOnClickListener,替换原来设置的OnClickListener监听器,然后在代理监听器HookOnClickListener的onClick方法去实现打印日志和原来OnClickListener监听器的onClick方法。如此一来,我们即使不改变原来OnClickListener监听器的onClick方法内容,也可以实现代码“入侵”到onClick方法中。
怎么Hook
具体怎么实现API Hook技术呢?
由于我们要改的是API的功能,所以得从源码开始了解。先看看View的setOnClickListener方法源码:
// View 源码
...
static class ListenerInfo {
...
public OnClickListener mOnClickListener;
...
}
...
ListenerInfo mListenerInfo;
...
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
...
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
...
从源码我们知道两个信息:
setOnClickListener方法把我们设置的“l”赋给了ListenerInfo的mOnClickListener属性,好,我们只要把ListenerInfo的mOnClickListener替换为代理对象,就可以实现了。
ListenerInfo对象时从getListenerInfo方法得到的,看到没有,mListenerInfo是在实例中是不会改变的。我们把ListenerInfo作为Hook对象。
知道了这两个东西,我们就可以开始撸代码了。
先定义代理对象,实现打印日志和执行原来监听器的onClick方法:
static class HookOnClickListener implements View.OnClickListener {
private View.OnClickListener origin;
public HookOnClickListener(View.OnClickListener origin) {
this.origin = origin;
}
@Override
public void onClick(View v) {
System.err.println("before view click");
origin.onClick(v);
System.err.println("after view click");
}
}
再来,我们创建代理对象,来代替原来的监听器:
private void hookOnClick(View view) {
try {
// 1 获取Hook对象,这里是ListenerInfo实例
// 源码中,ListenerInfo实例是从getListenerInfo方法获取的,
// 但是getListenerInfo方法不是公开的,所以我们通过反射调用getListenerInfo方法来获取ListenerInfo实例
// 1.1 获取View的Class
Class viewClass = Class.forName("android.view.View");
// 1.2 获取View的getListenerInfo方法,getDeclaredMethod面向所有方法,包括private和protected
Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");
// 1.3 改变getListenerInfo方法的可见性,设置为可访问
getListenerInfoMethod.setAccessible(true);
// 1.4 反射调用getListenerInfo方法得到ListenerInfo实例
Object listenerInfoObject = getListenerInfoMethod.invoke(view);
// 2 获取Hook对象中要被代理的对象,这里是ListenerInfo的mOnClickListener
// 也就是我们设置的监听器,因为我们要在监听中改变行为
// 2.1 获取ListenerInfo的class,由于ListenerInfo是View的内部类,所以需要这么写类名:android.view.View$ListenerInfo
Class listenerInfoClass = Class.forName("android.view.View$ListenerInfo");
// 2.2 获取ListenerInfo的mOnClickListener字段,getDeclaredField面向所有字段,包括private和protected
Field onClickListenerField = listenerInfoClass.getDeclaredField("mOnClickListener");
// 2.3 改变onClickListenerField字段的可见性,设置为可访问
onClickListenerField.setAccessible(true);
// 2.4 获取ListenerInfo的mOnClickListener字段的值
View.OnClickListener originOnClickListener = (View.OnClickListener) onClickListenerField.get(listenerInfoObject);
// 3 创建代理对象,并代替Hook对象需要改变的字段,这里我们要代替ListenerInfo原来的mOnClickListener
// 3.1 创建代理对象
View.OnClickListener hookOnClickListener = new HookOnClickListener(originOnClickListener);
// 3.2 代理对象代替原来对象
onClickListenerField.set(listenerInfoObject, hookOnClickListener);
} catch (Exception exception) {
exception.printStackTrace();
System.err.println(exception.getMessage());
}
}
相信代码注释足够解析整个代替流程,主要有3个步骤:
获取Hook对象。Hook对象的选取很重要,一般选单例对象或者静态变量。
获取Hook对象中要被代理的对象,也就是你想要改变的对象。
创建代理对象,并代替Hook对象中要被代理的对象。
使用:
Button clickButton = (Button) findViewById(R.id.click_button);
clickButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.err.println("view clicking");
}
});
hookOnClick(clickButton);
打印结果:
W/System.err: before view click
W/System.err: view clicking
W/System.err: after view click
这只是我对Android Hook技术的初探,如有错误,请不吝赐教,谢谢!!