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