Hook技术: Hook就是有一段程序逻辑一直走下去,我们可以捕获到其中间的一些逻辑,加于处理然后再让他接着执行下去;
比如Android里面的setOnclickListener这个方法. 正常我们是这样操作的
TextView textView = findViewById(R.id.act_invoke_tv);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(InvokeOnClickActivity.this, ((TextView)v).getText(), Toast.LENGTH_SHORT).show();
}
});
怎么在不改变这段代码的情况下,把里面的的Toast内容给替换掉
分析思路: 1肯定要用到动态代码,动态代码用到的是Proxy这个类
Object proxyInstance = Proxy.newProxyInstance(getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Toast.makeText(InvokeOnClickActivity.this, "已经hokd到方法了", Toast.LENGTH_SHORT).show();
return method.invoke(mOnClickListener, null);
}
});
等我们通过反射获取到textView设置的mClickListener的时候就将其替换
首先我们在setOnclickListener的时候传的是一个View.OnClickListener过去
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
这方法里面将我们传入的OnclickListener附值给了getListenerInfo()获取的类的一个成员变量,而getListenerInfo()这个方法获取的是ListenerInfo这个类,也就是说,在这里操作的是将我们设置的OnClickListener传给了ListenerInfo,是他里面的一个成员变量,
所以我们第一步就是获取到View里面的getListenerInfo这个方法
Class.forName这个方法可以获取到一个Class类,然后通过这个Class类获取到getListenerInfo这个方法,然后再执行这个方法代码如下
Class> viewClass = Class.forName("android.view.View");
Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");
getListenerInfo.setAccessible(true);
Object listenerInfo = getListenerInfoMethod.invoke(textView)
listenerInfo则就是通过VIew里的getListenerInfo获取到的对象 因为OnClickListener附给了ListenerInfo里的一个成员变量,这里我们继续通过反射获取到它的成员变量mOnClickListener
Object listenerInfoClass = Class.forName(android.view.View$ListenerInfo);
//获取到名字为mOnClickListener的成员变量
Field onClickListenerField = listenerInfoClass.getDeclaredField("mOnClickListener");
//获取到原来设置的OnClickListener,其实可以不用这个,只是为了不影响正常逻辑,因为我们代理完了要重新
//执行原来的方法
Object mOnClickListener = onClickListenerField.get(listenerInfo);
最后一步就是替换操作了
onClickListenerField.set(listenerInfo,proxyInstance);这样就远的成自己的了,当我们点击Teview的点击事件的时候就会先走到我们代码的proxyInstance里面的invoke方法,