Toast 在9.0上的重复特性的更改(也是重复显示的bug)

Toast是日常android开发经常会用的类,用来显示一些Toast信息。经常会遇到需要重复显示需求。为了防止Toast不停的弹出(以前是toast以队列形式依次显示,会很烦),我们通常会使用如下代码来覆盖上一个的Toast,代码如下:


    /**
     * 显示不重复Toast消息
     */
    @SuppressLint("ShowToast")
    public static void showToastNoRepeat(String msg) {
        if (TextUtils.isEmpty(msg)) {
            return;
        }
        if (mToast == null) {
            mToast = Toast.makeText(TestAPP.getApp(), msg, Toast.LENGTH_SHORT);
        } else {
            mToast.setText(msg);
        }
        hook(mToast);
        mToast.show();
    }

通过保存一个全局Toast,然后每次覆盖内容就可以了,但是最近在我们项目在9.0的系统上会重复显示toast的时候第二次就不显示了,在8.0上面的却没有问题,我就猜想是不是源码的有该更,于是对源码进行了初探,了解到Toast的显隐控制都是通过系统的服务NotificationManagerService来控制的,来看下8.0的源码,Toast show的时候其实是调用NotificationManagerService的enqueueToast将toast入队

@Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                        + " duration=" + duration);
            }

            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                return ;
            }
            final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());

            if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
                    (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
                            || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended
                                ? " due to package suspended by administrator."
                                : " by user request."));
                return;
            }

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index;
                    // All packages aside from the android package can enqueue one toast at a time
                    if (!isSystemToast) {
                        index = indexOfToastPackageLocked(pkg);
                    } else {
                        index = indexOfToastLocked(pkg, callback);
                    }

                    // If the package already has a toast, we update its toast
                    // in the queue, we don't move it to the end of the queue.
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                        record.update(callback);
                    } else {
                        Binder token = new Binder();
                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                    }
                    keepProcessAliveIfNeededLocked(callingPid);
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated.  Call back and tell it to show itself.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

然后在  showNextToastLocked()中来显示(如果是第一个),可以看出当第二次show同一个Toast会更新内容和时间。但是在9.0的代码中如下图所示,如果显示的第二次发现是上一个Toast是同一个,更新时间和callback,然后就会先hide掉当前的,hide之后会removewindow的Token,然后在显示第二次的Toast,和第一次的Toast的WIndowToken同一个,然而就为空了,WMS就会为添加失败,在App中的表现就是第二次Toast无法显示

@Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                        + " duration=" + duration);
            }

            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                return ;
            }
            final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());

            if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
                    (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
                            || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended
                                ? " due to package suspended by administrator."
                                : " by user request."));
                return;
            }

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index;
                    // All packages aside from the android package can enqueue one toast at a time
                    if (!isSystemToast) {
                        index = indexOfToastPackageLocked(pkg);
                    } else {
                        index = indexOfToastLocked(pkg, callback);
                    }

                    // If the package already has a toast, we update its toast
                    // in the queue, we don't move it to the end of the queue.
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                        try {
                            record.callback.hide();
                        } catch (RemoteException e) {
                        }
                        record.update(callback);
                    } else {
                        Binder token = new Binder();
                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                    }
                    keepProcessAliveIfNeededLocked(callingPid);
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated.  Call back and tell it to show itself.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

最后当然也有解决方案,其实是在9.0系统中的Toast就不需要重复使用同一个Toast,就把Toast当成一次性餐具一样,用完就不要了就可以,而低于9.0就重复使用同一Toast就OK了,具体代码如下

public static void show(Context context, CharSequence message, int duration) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){//9.0以上toast直接用原生的方法即可,并不用setText防止重复的显示的问题
        Toast.makeText(ApplicationUtil.getInstance(), message, Toast.LENGTH_SHORT).show();
    } else {
        if (mToast == null) {
            mToast = Toast.makeText(context.getApplicationContext(), message, duration);
        } else {
            mToast.setDuration(duration);
            mToast.setText(message);
        }
        mToast.show();
    }
}

完!!!

你可能感兴趣的:(java,android,Toast,android,9.0)