android-view button prevents repeated clicks in a short period of time

The user sends a certain click event multiple times in a short period of time

1. For example, click a button to jump to the (details page) activity, and the performance of the mobile phone will not increase. Before the jump, the user triggers the click event multiple times and two activities will be generated. Some people may say about the activities of the four major components. Using the activity's launch mode to configure the activity launchMode tag (singleTask, singleTop, singleInstance) in the manifest is of course useless.

2. Click a button to pop up a dialog, if the rendering of the dialog is slow. If the user clicks the button multiple times at this moment, multiple dialogs will be generated. quite poor experience

 

Solution: After clicking, clicking the button again for a period of time does not take effect.

1. That button needs to prevent repeated clicks, and deal with that button

Then put the code:

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!!!");
            }
        }
    });
}

The logic is very simple, the control point is mainly at (now - lastClickTime >1000) If the interval is not greater than 1s, the perform click will not be executed

 

But how can we pursue this point? In many cases, the project is already super complicated, and old projects or modules need to be optimized to prevent repeated problems.

If one button is processed one by one, this project will be super cumbersome and generate a lot of irrelevant logic. Cause business logic to become more complex. Then how can we not modify, the original business logic can reach the control, and the problem of repeated clicking of all buttons.

Now that there is a demand, we have to find a way to solve it.

Question: Modifying one place can achieve the effect of global modification:

Solution: global proxy click event and re-click time

Let me talk about the general idea of ​​​​the code layer

1. Monitor the activity globally (the solution is as follows:

 a, Application.ActivityLifecycleCallbacks provided by android is used in this example 

 b, through the android Instrumentation class, monitor all activity life cycles)

2. Monitor the view on the activity and dynamically replace the clicklistener of the view 

 

Above code:

The main implementation is implemented in the 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) {

        }
    };

    //Prevent multiple clicks in a short period of time, pop up multiple activities or dialogs, and other operations
    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);
            }
        }
    }

    /**
     * Find the clicklistener of the view through reflection
     * @param view
     */
    public static void getClickListenerForView(View view) {
        try {
            Class viewClazz = Class.forName("android.view.View");
            //event listeners are saved by this instance
            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)) {
                    //Custom proxy event listener
                    View.OnClickListener onClickListenerProxy = new ProxyOnclickListener(mOnClickListener);
                    //replace
                    onClickListenerField.set(listenerInfoObj, onClickListenerProxy);
                }else{
                    Log.e("OnClickListenerProxy", "setted proxy listener ");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }//Custom proxy event listener


    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) {
            //click time control
            long currentTime = System.currentTimeMillis();
            if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {
                lastClickTime = currentTime;
                Log.e("OnClickListenerProxy", "OnClickListenerProxy"+this);
                if (onclick != null) onclick.onClick(v);
            }
        }
    }

}

 

Note: The general idea above has been made very clear

1. The first monitoring to view is through

View.getViewTreeObserver().addOnGlobalLayoutListener

 

2, proxyOnlick(activity.getWindow().getDecorView(),5); and then traverse the view

The first two steps can be done through the api provided by android

3. Find the clicklistener of the view and modify it to a proxy. There is no direct API. At this time, you need to query the source code of the view

Analysis Let's look at view.setOnClickListener() first

The source code is as follows:

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

You can see getListenerInfo().mOnClickListener = l; Then continue to track the source code: getListenerInfo().mOnClickListener

 

According to the source code, we can see that the mOnclickListener of the internal class in View is the Onclicklistener variable we set. Now that we know the idea, the following reflection code cannot be understood.

 

public static void getClickListenerForView(View view) {
    try {
        Class viewClazz = Class.forName("android.view.View");
        //event listeners are saved by this instance
        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)) {
                //Custom proxy event listener
                View.OnClickListener onClickListenerProxy = new ProxyOnclickListener(mOnClickListener);
                //replace
                onClickListenerField.set(listenerInfoObj, onClickListenerProxy);
            }else{
                Log.e("OnClickListenerProxy", "setted proxy listener ");
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 

ok In this way, our optimization is finished, isn't it very simple. We don't need to understand the corresponding business at all, and we don't need to go to each page to modify the code to meet our optimization needs.  

Guess you like

Origin blog.csdn.net/tiandiyinghun/article/details/81531209