目录
5.1确认需要hook操作对象OnClickListener实现的实例
5.2获取要hook干涉执行逻辑的拥有者(ListenerInfo对象);
5.3创建(hook对象)OnClickListener类实例代理类,在代理类内加入自己代码;
1.Hook是做什么的?(解决什么问题?)
API HOOK百度百科解释如下:
API HOOK技术是一种用于改变API执行结果的技术,Microsoft 自身也在Windows操作系统里面使用了这个技术,如Windows兼容模式等。 API HOOK 技术并不是计算机病毒专有技术,但是计算机病毒经常使用这个技术来达到隐藏自己的目的。
Hook可以帮助我们在Android中在SDK源代码逻辑执行过程中,通过代码手动拦截执行该逻辑,加入自己的代码逻辑;
例如常见的一些业务统计功能或者方法执行的性能监测功能我们都可以通过Hook技术进行拦截,加入自己代码逻辑;
以前都是通过友盟,百度创建ID和描述,需要开发将事件记录一个一个加入到需要统计的点上,很容易出现漏记的情况,修改需要重新发包;在APP开发过程中通常需要统计用户点击了那些按钮,页面的打开关闭,页面停留时长都可以通过Hook技术大大减小开发工作量和出错率;
2.HOOK技术如何改变API执行结果的?
最初有些人对某些api函数的功能不太满意,就产生了如何修改这些api,使之更好的服务于程序的想法,这样api hook就自然而然的出现了。
HOOK技术的难点,并不在于hook技术,而在于对SDK源码的学习和理解,找到使用HOOK的入口函数,进行拦截然后加入自己的代码;
3.使用Hook技术需要有实现Java反射和阅读源码的能力
3.1Java反射
Java反射常用类Class,Method,Field等;
SDK源码类有很多用@hide被隐藏的方法,成员,类无法直接访问,需要通过反射访问方法,成员,类等;
TextView
/**
* @return the size (in scaled pixels) of thee default text size in this TextView.
* @hide
*/
@ViewDebug.ExportedProperty(category = "text")
public float getScaledTextSize() {
return mTextPaint.getTextSize() / mTextPaint.density;
}
3.2SDK源码
需要阅读SDK源码找到入口函数,这样才能通过Hook技术进行拦截,添加自己的代码逻辑;当追踪源码时经常看见SDK源码大面积飘红;
所以,推荐从安卓官网下载整套源码,然后使用 SourceInsight
查看源码。
如果不需要跳来跳去的话,直接用 安卓源码网站 一步到位
4.Hook实现的通用思路
无论多么复杂的逻辑,当我们需要干涉执行,最终都是需要将自己的代码逻辑替换干涉执行的逻辑;
a.确认需要hook操作对象A:a;
b.获取要hook干涉执行逻辑的拥有者(B对象);
例如定义类A和B,B拥有A实例,假如需要操作A实例,需要先拿到B;
Class A{}
Class B{
A a;
}
c.创建(hook对象)A类实例代理类,在代理类内加入自己代码;
d.将创建的(hook对象)代理类A对象,替换B类中a对象;
5.Hook拦截点击事件加入统计点击次数
5.1确认需要hook操作对象OnClickListener实现的实例
tvone.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
a.Hook操作onClickListener实例,需要获取onClickListener拥有者;
/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
onClickListener拥有者是ListenerInfo;
b.onClickListener拥有者是mListenerInfo实例
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
mListenerInfo是一个“伪实例“,我们看下ListenerInfo持有的OnClickListener实例:
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;
...其他Listener实例,例如长按等;
}
5.2获取要hook干涉执行逻辑的拥有者(ListenerInfo对象);
5.3创建(hook对象)OnClickListener类实例代理类,在代理类内加入自己代码;
public class HookListener {
/**
* hook核心代码
* 这个方法唯一目的:用自己的点击事件,替换掉View的点击事件
* @param context
* @param view
*/
public static void registerListener(Context context, View view){
//反射执行View类的getListenerInfo()方法,拿到v的mListenerInfo对象,这个对象就是点击事件的持有者
try {
Method method = View.class.getDeclaredMethod("getListenerInfo");
//由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
method.setAccessible(true);
//拿到mListenerInfo,也就是点击事件的持有者
Object listenerInfo = method.invoke(view);
//找到mListenerInfo持有的点击事件对象
//android.view.View$ListenerInfo内部类的表示方法
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
final View.OnClickListener onClickListenerInstance = (View.OnClickListener)field.get(listenerInfo);
//创建自己点击事件的代理类
//方式1:自己实现代理类
//ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
//方式2:由于View.OnClickListener是一个接口,所以可以直接用动态代理模式
//参数:本地类的加载器,要代理实现的接口(用Class数组表示,支持多接口),代理类的实际逻辑封装在new出来的InvocationHandler内
final Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().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("HookOnClickListener", ""+clickCount);
return method.invoke(onClickListenerInstance, args);
}
});
//替换持有者拥有的点击事件代理类,
field.set(listenerInfo, proxyOnClickListener);
//完成
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
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("HookOnClickListener", ""+clickCount);
if(this.listener != null){
this.listener.onClick(v);
}
}
}
}
代码逻辑:
a.借助于Method,Field,Class拿到点击事件的持有者mListenerInfo;
//由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
method.setAccessible(true);
b.用点击事件的代理类替换持有者mListenerInfo点击事件
//替换持有者拥有的点击事件代理类,
field.set(listenerInfo, proxyOnClickListener);
c.用Proxy代理类的创建点击事件的代理类
包含三个参数:本地类的加载器,要代理实现的接口(用Class数组表示,支持多接口),代理类的实际逻辑封装在new出来的InvocationHandler内
测试代码:
tvone.setOnClickListener(this);
HookListener.registerListener(this, tvone);
拦击统计点击次数输出日志:
06-03 04:19:00.563 5603-5603/fan.fragmentdemo D/HookOnClickListener: 3
06-03 04:19:01.023 5603-5603/fan.fragmentdemo D/HookOnClickListener: 4
06-03 04:19:01.543 5603-5603/fan.fragmentdemo D/HookOnClickListener: 5
06-03 04:19:01.733 5603-5603/fan.fragmentdemo D/HookOnClickListener: 6
06-03 04:19:02.013 5603-5603/fan.fragmentdemo D/HookOnClickListener: 7
06-03 04:19:02.243 5603-5603/fan.fragmentdemo D/HookOnClickListener: 8
以上是Hook技术实现整理;
6.其他
实现Hook技术,第一需要先了解Hook实现流程,第二要熟悉SDK源码,才能帮助我们快速定位需要修改的业务代码;
参考:
https://baike.baidu.com/item/API%20HOOK?fr=aladdin
https://www.jianshu.com/p/74c12164ffca?tdsourcetag=s_pcqq_aiomsg