一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情。
2.拦截点击事件统计点击次数
a.寻找hook点
对一个view设置点击事件的调用流程如下:
hkBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
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;
}
static class ListenerInfo {
......
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
......
}
Hook操作onClickListener实例,需要首先获取到onClickListener的拥有者,即:ListenerInfo,最后通过ListenerInfo获取到原始的mOnClickListener。
b.选择合适的代理方式
从上面的流程可以发现,要将ListenerInfo类里面的mOnClickListener替换成修改过的代理对象;要替换ListenerInfo里面的字段,得先拿到ListenerInfo,View类中有一个方法getListenerInfo() ,通过它可以拿到这个ListenerInfo,实现如下:
public static void hookOnClickListener(View view) {
//step1:反射执行View类的getListenerInfo()方法,拿到view的mListenerInfo对象,这个对象是点击事件mOnClickListener的持有者
try {
Class<?> viewClz = Class.forName("android.view.View");
Method method = viewClz.getDeclaredMethod("getListenerInfo");
//由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
method.setAccessible(true);
//拿到mListenerInfo,也就是点击事件的持有者
Object listenerInfo = method.invoke(view);
//step2:找到mListenerInfo持有的点击事件对象mOnClickListener
//内部类的表示方法:android.view.View$ListenerInfo
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field onClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
final View.OnClickListener onClickListenerInstance = (View.OnClickListener) onClickListener.get(
listenerInfo);
//step3:创建自己点击事件的OnClickListener代理类
动态或静态创建OnClickListener的代理类proxyOnClickListener
//step4:将持有者拥有的点击事件替换成代理对象[将listenerInfo里面的onClickListener变量替换为proxyOnClickListener]
onClickListener.set(listenerInfo, proxyOnClickListener);
} catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException | NoSuchFieldException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
接下来要创建OnClickListener的代理对象,由于OnClickListener是一个接口,因此可以使用JDK动态代理方式Java动态代理,也可以用静态代理类实现,实现方式如下:
//方式1:自己实现代理类,将原始的View.OnClickListener对象onClickListenerInstance作为参数传入
ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
static class ProxyOnClickListener implements View.OnClickListener{
private View.OnClickListener listener;
private int clickCount = 0;
ProxyOnClickListener(View.OnClickListener listener){
this.listener = listener;
}
@Override
public void onClick(View v) {
clickCount++;
Log.d("Seven", "Hook OnClickListener 2 click count " + clickCount);
if(this.listener != null){
this.listener.onClick(v);
}
}
//方式2:由于View.OnClickListener是一个接口,所以可以直接用动态代理模式
//参数:类的加载器,要代理实现的接口(用Class数组表示,支持多接口),代理类的实际逻辑封装在new出来的InvocationHandler内
Object proxyOnClickListener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),
new Class[]{View.OnClickListener.class}, new InvocationHandler() {
private int clickCount = 0;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
clickCount++;
Log.d("Seven", "Hook OnClickListener 1 click count " + clickCount);
return method.invoke(onClickListenerInstance, args);
}
});
c.启动运行
hkBtn.setOnClickListener(this);
HookUtils.hookonClickListener(hkBtn);
运行结果如下:
10:51:12.190 D/Seven (26125): Hook OnClickListener 1 click count 1
10:51:21.428 D/Seven (26125): Hook OnClickListener 1 click count 2
10:51:21.607 D/Seven (26125): Hook OnClickListener 1 click count 3
10:51:21.770 D/Seven (26125): Hook OnClickListener 1 click count 4
10:51:21.918 D/Seven (26125): Hook OnClickListener 1 click count 5
10:51:22.068 D/Seven (26125): Hook OnClickListener 1 click count 6
10:51:22.217 D/Seven (26125): Hook OnClickListener 1 click count 7
10:51:22.394 D/Seven (26125): Hook OnClickListener 1 click count 8
10:51:22.543 D/Seven (26125): Hook OnClickListener 1 click count 9
10:51:22.722 D/Seven (26125): Hook OnClickListener 1 click count 10
10:51:22.854 D/Seven (26125): Hook OnClickListener 1 click count 11
10:51:23.009 D/Seven (26125): Hook OnClickListener 1 click count 12
10:51:23.158 D/Seven (26125): Hook OnClickListener 1 click count 13
至此已经实现了对点击事件的hook。
3.总结
针对以上两个案例分析,我们可以看到在进行hook时主要分为以下几步:
a.寻找合适的hook点;
b.找到Hook点对应的class;
c.直接通过class或class内部的method找到对应的filed;
d.替换对象是interface的话,直接用动态代理方式创建对象;否则,直接写个类继承要替换的类;
e.调用filed.set(obj, proxy)来对变量进行替换;
f.针对hook点进行相应的处理;
hook技术涉及到的知识点主要有反射、代理及android源码的熟练程度。