android-view button 防止短时间内重复点击

短时间用户多次发出某个点击事件

1,比如点击某个按钮跳转(详情页面)activity,手机性能不加。在未跳转之前用户多次触发了点击事件就会产生两个activity。可能对于四大组件的activity ,会有人说。使用activity的启动模式 在manifest 中配置activity  launchMode标签(singleTask ,singleTop,singleInstance )  然并不卵用。

2,点击某个按钮弹出dialog,如果dialog 渲染比较慢 。用户并且在此刻多次点击按钮,会产生多个dialog。体验相当之差

 

解决思路: 点击之后然后按钮在一段时间再次点击不生效。

1,那个按钮需要防止重复点击,针对那个按钮进行处理

那就上代码:

private final static String TAG = "MainActivity";
private Button btn;
private long lastClickTime = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    btn = findViewById(R.id.btn);
    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            long now = System.currentTimeMillis();
            if(now - lastClickTime >1000){
                lastClickTime = now;
                Log.e(TAG,"perform click!!!");
            }
        }
    });
}

逻辑很简单,控制点主要是在(now - lastClickTime >1000) 如果间隔不大于1s 不会执行perform click 的

 

但是   我们岂能这点追求 ,很多情况下,项目已经是超级复杂了 ,需要对旧项目或者就模块 进行优化,防止重复点问题。

如果一个按钮,一个按钮处理,此项工程将会边的超级繁琐,还会产生很多无关的逻辑。导致业务逻辑变得更加复杂。那我们怎么才能不修改,原有的业务逻辑就能到达控制,所有按钮的重复点击问题呢。

既然有了需求,我们就要想办法解决。

问题:修改一处可以达到全局修改的作用:

解决方案:全局代理click 事件  并重新click 时间

先说下代码层的大致思路

1,通过全局监控activity   (方案 如下:

 a, 通过android 提供的 Application.ActivityLifecycleCallbacks  本例子选用方案 

 b,通过 android Instrumentation 这个类,监控所有的activity 生命周期)

2, 监控 activity 上面的 view ,动态替换view 的clicklistener 

 

上代码:

主要实现   是在application 中实现的
/**
 * Created by caoyanglong on 2018/8/14.
 */
public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(lifecycleCallbacks);
    }

    private ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
        @Override
        public void onActivityCreated(Activity activity, Bundle bundle) {
            fixViewMutiClickInShortTime(activity);
        }

        @Override
        public void onActivityStarted(Activity activity) {

        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivityStopped(Activity activity) {

        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {

        }
    };

    //防止短时间内多次点击,弹出多个activity 或者 dialog ,等操作
    private void fixViewMutiClickInShortTime(final Activity activity) {
        activity.getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                proxyOnlick(activity.getWindow().getDecorView(),5);
            }
        });
    }

    private void proxyOnlick(View view, int recycledContainerDeep) {
        if (view.getVisibility() == View.VISIBLE) {
            boolean forceHook = recycledContainerDeep == 1;
            if (view instanceof ViewGroup) {
                boolean existAncestorRecycle = recycledContainerDeep > 0;
                ViewGroup p = (ViewGroup) view;
                if (!(p instanceof AbsListView || p instanceof ListView) || existAncestorRecycle) {
                    getClickListenerForView(view);
                    if (existAncestorRecycle) {
                        recycledContainerDeep++;
                    }
                } else {
                    recycledContainerDeep = 1;
                }
                int childCount = p.getChildCount();
                for (int i = 0; i < childCount; i++) {
                    View child = p.getChildAt(i);
                    proxyOnlick(child, recycledContainerDeep);
                }
            } else {
                getClickListenerForView(view);
            }
        }
    }

    /**
     * 通过反射  查找到view 的clicklistener 
     * @param view
     */
    public static void getClickListenerForView(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);

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

            Field onClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener");

            if (null != onClickListenerField) {
                if (!onClickListenerField.isAccessible()) {
                    onClickListenerField.setAccessible(true);
                }
                View.OnClickListener mOnClickListener = (View.OnClickListener) onClickListenerField.get(listenerInfoObj);
                if (!(mOnClickListener instanceof ProxyOnclickListener)) {
                    //自定义代理事件监听器
                    View.OnClickListener onClickListenerProxy = new ProxyOnclickListener(mOnClickListener);
                    //更换
                    onClickListenerField.set(listenerInfoObj, onClickListenerProxy);
                }else{
                    Log.e("OnClickListenerProxy", "setted proxy listener ");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }//自定义的代理事件监听器


    static class ProxyOnclickListener implements View.OnClickListener {
        private View.OnClickListener onclick;

        private int MIN_CLICK_DELAY_TIME = 500;

        private long lastClickTime = 0;

        public ProxyOnclickListener(View.OnClickListener onclick) {
            this.onclick = onclick;
        }

        @Override
        public void onClick(View v) {
            //点击时间控制
            long currentTime = System.currentTimeMillis();
            if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {
                lastClickTime = currentTime;
                Log.e("OnClickListenerProxy", "OnClickListenerProxy"+this);
                if (onclick != null) onclick.onClick(v);
            }
        }
    }

}

 

注意:上面大致思路已经说得很清楚了

1,第一监控到view  是通过

View.getViewTreeObserver().addOnGlobalLayoutListener

 

2,proxyOnlick(activity.getWindow().getDecorView(),5);然后对view 的进行遍历查询

前两步通过android 提供的api 都可以搞定

3,找到view 的 clicklistener 并且修改为代理,没有直接的api ,这个时候需要查询view 源码

分析  咱们先看   view.setOnClickListener()

源码如下:

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

可以看到 getListenerInfo().mOnClickListener = l;   那就继续追踪源码:getListenerInfo().mOnClickListener

android-view button 防止短时间内重复点击_第1张图片

 

根据源码看到 View 中内部类的 mOnclickListener 即为我们的设置的Onclicklistener 变量 既然知道思路了,下面的反射代码就不能理解了

 

public static void getClickListenerForView(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);

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

        Field onClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener");

        if (null != onClickListenerField) {
            if (!onClickListenerField.isAccessible()) {
                onClickListenerField.setAccessible(true);
            }
            View.OnClickListener mOnClickListener = (View.OnClickListener) onClickListenerField.get(listenerInfoObj);
            if (!(mOnClickListener instanceof ProxyOnclickListener)) {
                //自定义代理事件监听器
                View.OnClickListener onClickListenerProxy = new ProxyOnclickListener(mOnClickListener);
                //更换
                onClickListenerField.set(listenerInfoObj, onClickListenerProxy);
            }else{
                Log.e("OnClickListenerProxy", "setted proxy listener ");
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 

ok     这样我们的优化 就做完了,是不是很简单。我们完全不需了解对应的业务,也不需要到各个页面去修改代码,便达到了我们的优化需求。  

你可能感兴趣的:(UI,动态加载,反射,重构,动态技术)