在开发中,经常会出现用户连续点击一个按钮,如果机器老化,卡顿,会出现连续打开几个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方法就能达到自己想要的效果,如果想修改双击或者长按事件,也能找到对应的字段进行修改,这里就不再叙述了。