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