在开发中,经常会出现用户连续点击一个按钮,如果机器老化,卡顿,会出现连续打开几个activity或者发送几个网络请求的情况,所以需要处理频繁点击的问题,之前可能会采用重写onClickListener,然后修改onClick里面的方法达到处理频繁点击,这样就需要在每个点击事件替换自己的onClickListener事件。工程庞大的时候一次需要替换的数量太大,所以我考虑能不能利用反射原理去替换系统的onClickListener,减少工作量。首先我们分析一下OnClickListener源码的实际流程。
1.设置点击事件,跟进setOnClickListener方法里面,看做了什么操作。
btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "click", Toast.LENGTH_SHORT).show(); } });
2.看一下setOnClickListener方法,发现onClickLisener赋值给了getListenerInfo方法里面去了,所以我们要继续查看getListenerInfo方法里面对onClickListener做了什么操作。
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
3.进入getListenerInfo方法,,找到了ListenerInfo为了大家观看方便我只提取了有用的一行代码,看到了mOnClickListener,ListenerInfo的实例可以说是信息的载体,那么很简单,我们这里只需要将这个字段替换成我们的自己实现的点击事件,就能实现我们的频繁点击处理了。
static class ListenerInfo { public OnClickListener mOnClickListener; }
4,最后献上我在项目中使用的代码了:
public class ViewDoubleClickUtil { public static void hookView(View view) { try { Class viewClazz = Class.forName("android.view.View"); //事件监听器都是这个实例保存的 Method listenerInfoMethod = viewClazz.getDeclaredMethod("getListenerInfo"); if (!listenerInfoMethod.isAccessible()) { listenerInfoMethod.setAccessible(true); } Object listenerInfoObj = listenerInfoMethod.invoke(view); @SuppressLint("PrivateApi") Class listenerInfoClazz = Class.forName("android.view.View$ListenerInfo"); Field onClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener"); //修改修饰符带来不能访问的问题 if (!onClickListenerField.isAccessible()) { onClickListenerField.setAccessible(true); } View.OnClickListener mOnClickListener = (View.OnClickListener) onClickListenerField.get(listenerInfoObj); //自定义事件监听器 View.OnClickListener onClickListenerProxy = new OnClickListenerProxy(mOnClickListener); //更换成自己的点击事件 onClickListenerField.set(listenerInfoObj, onClickListenerProxy); } catch (Exception e) { e.printStackTrace(); } } //自定义的事件监听器 private static class OnClickListenerProxy implements View.OnClickListener { private View.OnClickListener object; //点击时间控制 private int MIN_CLICK_DELAY_TIME = 1000; private long lastClickTime = 0; private OnClickListenerProxy(View.OnClickListener object) { this.object = object; } @Override public void onClick(View v) { long currentTime = Calendar.getInstance().getTimeInMillis(); if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) { lastClickTime = currentTime; if (object != null) object.onClick(v); } } } }
因为劫持的是View,所以我们需要在需要处理的页面执行下面的代码,就能替换改页面下的所有点击事件了,如果是Fragment,那么就直接在Fragment依附的avtivity执行下面的代码就OK了。
getWindow().getDecorView().post(new Runnable() { @Override public void run() { ViewDoubleClickUtil.hookView(inflate); } });如果想对view进行其他操作其实只需要修改onClickListenerPoxy里面的onClick方法就能达到自己想要的效果,如果想修改双击或者长按事件,也能找到对应的字段进行修改,这里就不再叙述了。