hook 循环点击事件用哪个_hook View点击事件(View点击事件劫持)

引入

最近在做项目优化的时候,发现点击某个控件使用某个功能时,事先要判断用户是否登录,翻看代码时发现很多地方写了大量的if或else的操作,代码繁琐,一旦一处修改将涉及到n处修改,于是通过hook的方式,劫持View的点击事件,一行代码做完所有的if或else,代码简洁。

什么是hook

hook网上有大量的解说,我自己认为就是通过钩子函数改变原函数的执行行为。加入自己的思想。

参考:https://www.jianshu.com/p/3382cc765b39

实践

在没有修改代码之前我通常都是这样写:

btn.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

if (!isLogin) {

//弹出提示框,引导用户登录

} else {

//下一步操作

}

}

});

修改代码后通过一行代码做完if else;

HookSetOnClickListenerHelper.getInstance().hook(getContext(), btn);

关键代码

public void hook(Context context, final View view) {

try {

if (context == null) {

throw new NullPointerException("context is not init");

}

this.mCtx = context;

// 反射执行View类的getListenerInfo()方法,拿到view的mListenerInfo对象,这个对象就是点击事件的持有者

Method method = View.class.getDeclaredMethod("getListenerInfo");

//设置权限

method.setAccessible(true);

//得到点击事件的持有者

Object listenerInfo = method.invoke(view);

//得到点击事件对象

Class> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");

Field field = listenerInfoClz.getDeclaredField("mOnClickListener");

final View.OnClickListener onClickListener = (View.OnClickListener) field.get(listenerInfo);

创建点击事件代理

Object proxyOnclickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (!MyApplication.instance.getLogin()) {

goLogin();

return null;

}

return method.invoke(onClickListener, 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();

}

}

代理类代码

public class ProxyOnClickListener implements View.OnClickListener {

View.OnClickListener mOnClickListener;

public ProxyOnClickListener(View.OnClickListener clickListener) {

this.mOnClickListener = clickListener;

}

@Override

public void onClick(View v) {

Log.d(">>>>>", "onClick: 点击了事件");

if (mOnClickListener != null) {

mOnClickListener.onClick(v);

}

}

}

HookSetOnClickListenerHelper全部代码

public class HookSetOnClickListenerHelper {

private static HookSetOnClickListenerHelper instance;

private Context mCtx;

private HookSetOnClickListenerHelper() {

}

public static HookSetOnClickListenerHelper getInstance() {

if (instance == null) {

synchronized (HookSetOnClickListenerHelper.class) {

if (instance == null) {

instance = new HookSetOnClickListenerHelper();

}

}

}

return instance;

}

public void hook(Context context, final View view) {

try {

if (context == null) {

throw new NullPointerException("context is not init");

}

this.mCtx = context;

// 反射执行View类的getListenerInfo()方法,拿到view的mListenerInfo对象,这个对象就是点击事件的持有者

Method method = View.class.getDeclaredMethod("getListenerInfo");

//设置权限

method.setAccessible(true);

//得到点击事件的持有者

Object listenerInfo = method.invoke(view);

//得到点击事件对象

Class> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");

Field field = listenerInfoClz.getDeclaredField("mOnClickListener");

final View.OnClickListener onClickListener = (View.OnClickListener) field.get(listenerInfo);

创建点击事件代理

Object proxyOnclickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (!MyApplication.instance.getLogin()) {

goLogin();

return null;

}

return method.invoke(onClickListener, 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();

}

}

private void goLogin() {

String tip = mCtx.getString(R.string.personal_center18);

WarningDialog dialog = new WarningDialog(Objects.requireNonNull(mCtx), tip, R.style.warningDialog, true) {

@Override

public void OnClickCancel() {

}

@Override

public void OnClickSure() {

Intent intent = new Intent(mCtx, LoginActivity.class);

mCtx.startActivity(intent);

}

};

dialog.show();

}

}

使用

//btn是需要代理的view

btn.setOnClickListener(v -> {

startActivityForResult(CouponActivity.newInstance(getContext(), true), Request.CODE.REQUEST_COMMENT);

});

//ps 这里一定要写在事件之后

HookSetOnClickListenerHelper.getInstance().hook(getContext(), btn);

总结

关于login方法中的dialog代码没有贴出,可以自行修改,做到这里我们是可以正常劫持view的点击事件的,但是存在一个问题就是只能对单个View对象劫持,也就是每操作一个控件都需要加上 HookSetOnClickListenerHelper.getInstance().hook(getContext(), btn);依然有缺陷,后续改进。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值