一、什么是Hook 技术
Android 程序有一套特有的事件分发机制,都是按既定程序从前往后执行的。Hook 技术就是利用反射和代理,在既定程序中插入我们自己写的程序。比如,我们想在某个View的点击事件中添加播放音乐的效果。控件的点击事件,分发流程都是系统已经写好了,这时我们怎么做到在其中插入我们的播放音乐的效果呢?
二、如何寻找Hook点
1.尽量选择静态变量和单例对象,因为一旦创建对象,他们不容易变化,非常容易定位。
2.尽量Hook public的对象和方法
三、Hook过程
选中到了合适的Hook点后,选择合适的代理方式,如果是接口就可以用动态代理,然后偷梁换柱,用代理对象替换原始对象。
四、Hook View的点击事件
先看看View 点击事件的源码
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
@UnsupportedAppUsage
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
由代码可以看出,我们正常设置进去的OnClickListener是保存在ListenerInfo里面的。
如果我们可以通过反射将这个ListenerInfo里面的mOnClickListener 替换成我们自定义的OnClickListener是不是就可以实现我们目的了。接下来我们试试!
先看看通过反射修改对象属性的API
field.set(Object obj, Object value)
- field是我们要修改的变量的属性,也就是mOnClickListener
- obj就是要修改的对象,就是ListenerInfo
- value就是要替换mOnClickListener 的新值
接下来围绕准备这几个变量,我们来写代码
private void hookOnClickListener(View view) {
try {
// 得到待hook view 的 ListenerInfo 对象
Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
getListenerInfo.setAccessible(true);
Object listenerInfo = getListenerInfo.invoke(view);
// 得到 原始的 mOnClickListener 对象
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
mOnClickListener.setAccessible(true);
View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
// 用自定义的 hookedOnClickListener 替换原始的 mOnClickListener
View.OnClickListener hookedOnClickListener = new HookOnClickListener(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
} catch (Exception e) {
Log.d(TAG, "hook clickListener failed!: ");
}
}
自定义的HookOnClickListener
class HookOnClickListener implements View.OnClickListener {
private View.OnClickListener origin;
HookOnClickListener(View.OnClickListener origin) {
this.origin = origin;
}
@Override
public void onClick(View v) {
Log.d(TAG,"执行点击事件之前");
if (origin != null) {
origin.onClick(v);
}
Log.d(TAG,"执行点击事件之后");
}
}
在MainActivity中的使用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvHook = findViewById(R.id.tv_hook);
tvHook.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG,"tvHook 的点击事件");
}
});
hookOnClickListener(tvHook);
}
点击tvHook 看看输出结果
/com.example.hook D/MainActivity: 执行点击事件之前
/com.example.hook D/MainActivity: tvHook 的点击事件
/com.example.hook D/MainActivity: 执行点击事件之后
看到这里我们已经成功Hook到了View的点击事件
五、Hook注意点
Android 的API版本比较多,各个厂家也对系统有不同程度的定制,所以类和方法有可能不太一样,这就要求我们做好兼容。