短时间用户多次发出某个点击事件
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
根据源码看到 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 这样我们的优化 就做完了,是不是很简单。我们完全不需了解对应的业务,也不需要到各个页面去修改代码,便达到了我们的优化需求。