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.