防止网络请求过快loading闪烁

715 阅读2分钟

问题

1.网络请求过快,loading show之后立马dismiss 造成屏幕闪烁
2.一个页面多个请求,导致 show dismiss show dismiss 闪烁问题

这是一个很常见的问题,优化完以后,用户体验直接+100分。

思路

参考:ContentLoadingProgressBar所写

  1. 当展示时间少于一个阈值(500ms)时不展示。
  2. 每次展示最少展示(500ms)。

只要完成这两效果就不会有闪烁问题。

实现

  1. show的时候,发送一个延迟500ms的mHandler.postDelayed()任务,在这个任务时间到了执行的过程中,判断是不是已经dismiss了,如果dismiss则不展示。

  2. dismiss的时候,移除之前show的延迟任务,并且已经show了的话,如果时间过短,则延迟dismiss。如果时间长的话,直接dismiss

  3. 处理内存泄漏问题。在onAttachedToWindowonDetachedFromWindowonDestroy的时候,得移除handler中的任务

image.png image.png

天坑

简简单单的实现完了,开开心心的用在项目中,结果翻车了。
测试反应几天,总有那么一次两次,dialog show了,并且dismiss关不掉。并且无法复现。
反反复复看代码,啥毛病没有。

终于,最后定位在mHandler.removeCallbacks(mDelayedShow);有时候会失效。原因未知,网上也是众说纷纭。什么前后台切换,会导致Runnable重新构建,所以remove的时候对象变了等。

总结:removeMessages的object对象会改变,导致移除失败

源码:
image.png

解决:
直接不用object,用what进行判断

image.png

问题2:
扫码等页面时,可能会触发onAttachedToWindow,这时如果刚好发了一个延迟dismiss的handler信息,刚好被remove了。就无法正常关闭

代码

dialog样式换成自己的


/**
 *
 * loading...
 *
 * 防止网络请求过快,loading show之后立马dismiss 造成屏幕闪烁
 * 防止一个页面多个请求,导致  show dismiss  show  dismiss 闪烁问题
 * 参考谷歌的 ContentLoadingProgressBar 写的
 *
 * mHandler.removeCallbacks 有时会失效,原因未知
 */
public class ContentLoadingDialog extends AppCompatDialog {

    public ContentLoadingDialog(Context context) {
        super(context, R.style.Theme_AppCompat_DayNight_Dialog);
        initView(context);
    }

    public ContentLoadingDialog(Context context, int theme) {
        super(context, R.style.Theme_AppCompat_DayNight_Dialog);
        initView(context);
    }

    protected ContentLoadingDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
        super(context, cancelable, cancelListener);
        initView(context);
    }

    private void initView(Context context) {
        getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
        getWindow().setDimAmount(0f);
//        dialog.getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
        setCancelable(true);
        setCanceledOnTouchOutside(false);
        setContentView(R.layout.dialog_loading);
        BackgroundLayout backgroundLayout = findViewById(R.id.background);
        backgroundLayout.setBaseColor(context.getResources().getColor(R.color.color_b1000000));
        backgroundLayout.setCornerRadius(10);
        FrameLayout containerFrame = findViewById(R.id.container);
        int wrapParam = UIUtils.dip2px(GlobalApplication.getContext(),30);
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(wrapParam, wrapParam);
        SpinView view = new SpinView(context);
        containerFrame.addView(view, params);
        view.setAnimationSpeed(1);
        TextView label = findViewById(R.id.label);
        label.setVisibility(View.VISIBLE);
        label.setText("正在加载中...");
    }


    /**
     * show以后在此期间调用dismiss 则不展示
     */
    private static final int MIN_SHOW_TIME = 500;
    /**
     * 最小展示时间,show以后最少展示的时间
     */
    private static final int MIN_DELAY = 500;
    private long mStartTime = -1;
    private boolean mPostedHide = false;
    private boolean mPostedShow = false;
    private boolean mDismissed = false;
    private static final int SHOW_HANDLER_WHAT = 964;
    private static final int HIDE_HANDLER_WHAT = 965;

    private final Handler mHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW_HANDLER_WHAT:
                    mPostedShow = false;
                    if (!mDismissed) {
                        mStartTime = System.currentTimeMillis();
                        show();
                    }
                    break;
                case HIDE_HANDLER_WHAT:
                    mPostedHide = false;
                    mStartTime = -1;
                    dismiss();
                    break;
                default:
                    dismiss();
                    break;
            }
            super.handleMessage(msg);
        }
    };

    public void showDialog() {
        ThreadUtils.runOnUiThread(() -> {
            mStartTime = -1;
            mDismissed = false;
            mHandler.removeMessages(HIDE_HANDLER_WHAT);
            mPostedHide = false;
            if (!mPostedShow) {
                mHandler.sendEmptyMessageDelayed(SHOW_HANDLER_WHAT, MIN_DELAY);
                mPostedShow = true;
            }
        });
    }

    public void hideDialog() {
        ThreadUtils.runOnUiThread(() -> {
            mDismissed = true;
            mHandler.removeMessages(SHOW_HANDLER_WHAT);
            mPostedShow = false;
            long diff = System.currentTimeMillis() - mStartTime;
            if (diff >= MIN_SHOW_TIME || mStartTime == -1) {
                dismiss();
            } else {
                if (!mPostedHide) {
                    mHandler.sendEmptyMessageDelayed(HIDE_HANDLER_WHAT, MIN_SHOW_TIME - diff);
                    mPostedHide = true;
                }
            }
        });
    }

    public void close(){
        removeCallbacks();
        if (isShowing()){
            dismiss();
        }
    }

    private void removeCallbacks() {
        mHandler.removeMessages(HIDE_HANDLER_WHAT);
        mHandler.removeMessages(SHOW_HANDLER_WHAT);
    }
}