在Toast与Snackbar的那点事儿这篇文章中,受益良多,翻看了自己项目,发现项目也有重构一份SafeToastManager来统一管理应用Toast的弹出,并且解决方案和美团的这篇文章竟然出奇的一致!由于项目的最高TargetSdk版本还是23,所以暂时还没用到后续提到的SnackBar。
但是在翻看该篇文章的时候,发现作者有一处错误的地方,在作者解释Toast导致BadTokenException的时候,有说明到该奔溃主要集中在Android 5.0 -- Android 7.1.2的机型上,自己有翻看了源码并实践,发现这种说法是有问题的。
经过试验发现,在Sdk25(Android 7.1)版本以下的机型上,是不会产生BadTokenException问题的,只有在Sdk版本为 25 的机型上,才有可能产生异常崩溃,因为在Android O(26)中,Google修复了此问题。
Toast导致BadTokenException的原因详见Toast与Snackbar的那点事儿,其根本原因就是主线程Looper阻塞导致的异常,NMS会在固定时间之后移除生成的Token标识,而此时主线程由于阻塞没能及时的执行WindowManager.addView(),导致出现BadTokenException异常。在Android 7.1.1上,WMS执行addWindow()判断代码如下:
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { .............. if (token == null) { .............. if (type == TYPE_TOAST) { // 注 : 此处判断的为 Target Sdk 版本是否大于 25 // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. if (doesAddToastWindowRequireToken(attrs.packageName, callingUid, attachedWindow)) { Slog.w(TAG_WM, "Attempted to add a toast window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } } ...............}
从上述可看出,在TargetSdk为25以上,当需要弹出一个window type为toast类型的弹窗时,首先需要强制判断Token是否存在。那么当一个toast弹出的时候,这个token是什么时候产生的呢。
通过对Toast.show()的代码跟踪如下,最终会调用到NMS去管理Toast弹出,NMS维护一个toastRecord队列,依次弹出toast,代码如下:
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 = isCallerSystem() || ("android".equals(pkg)); final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, Binder.getCallingUid()); if (ENABLE_BLOCKED_TOASTS && (!noteNotificationOp(pkg, Binder.getCallingUid()) || isPackageSuspended)) { if (!isSystemToast) { 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 = indexOfToastLocked(pkg, callback); // If it's already in the queue, we update it in place, we don't // move it to the end of the queue. if (index >= 0) { record = mToastQueue.get(index); record.update(duration); } else { // Limit the number of toasts that any given package except the android // package can enqueue. Prevents DOS attacks and deals with leaks. if (!isSystemToast) { int count = 0; final int N = mToastQueue.size(); for (int i=0; i<N; i++) { final ToastRecord r = mToastQueue.get(i); if (r.pkg.equals(pkg)) { count++; if (count >= MAX_PACKAGE_NOTIFICATIONS) { Slog.e(TAG, "Package has already posted " + count + " toasts. Not showing more. Package=" + pkg); return; } } } } // 产生一个新的Token 对象 source from android 7.1 // 注 : 从 Android 7.1 开始,才有的代码 Binder token = new Binder(); // 通知 WMS 去添加 Token mWindowManagerInternal.addWindowToken(token, WindowManager.LayoutParams.TYPE_TOAST); record = new ToastRecord(callingPid, pkg, callback, duration, token); // 将新生成的 ToastRecord 添加至 mToastQueue 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) { // 通知客户端去弹toast showNextToastLocked(); } } finally { Binder.restoreCallingIdentity(callingId); } } }
从上述代码分析可知,在SDK 25版本及以上,产生ToastRecord时会强制生成一个Token,并且在 SDK 为25 的 WMS addWindow()的代码中,会强制判断当前Token是否有效(注:强制判断的条件为当前应用的 TargetSdk 版本是否大于 25),所以,即使在 Android 7.1 的手机上,如果应用的TargetSdk 版本在 26以下,也是不会有问题的。
综上所述,唯一可能因为Token 不生效,而导致BadTokenException 的情况,只有可能是在Android 7.1 的机型上,但是安装应用的Target Sdk版本为26!
在Android O(SDK 26)中,Google为此做了修复处理,具体代码参加Toast源码如下:
public void handleShow(IBinder windowToken) { ................... if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) { return; } if (mView != mNextView) { // remove the old view if necessary handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); String packageName = mView.getContext().getOpPackageName(); if (context == null) { context = mView.getContext(); } mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); // We can resolve the Gravity here by using the Locale for getting // the layout direction final Configuration config = mView.getContext().getResources().getConfiguration(); final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); mParams.gravity = gravity; if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } mParams.x = mX; mParams.y = mY; mParams.verticalMargin = mVerticalMargin; mParams.horizontalMargin = mHorizontalMargin; mParams.packageName = packageName; mParams.hideTimeoutMilliseconds = mDuration == Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT; mParams.token = windowToken; if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); // Since the notification manager service cancels the token right // after it notifies us to cancel the toast there is an inherent // race and we may attempt to add a window after the token has been // invalidated. Let us hedge against that. // 注: Android O 在 windowmanager.addView()的时候,做了 try catch // 处理,手动捕获了异常 try { mWM.addView(mView, mParams); trySendAccessibilityEvent(); } catch (WindowManager.BadTokenException e) { /* ignore */ } } }
Android O在 Toast 最终要展示的时候(即 mWM.addView())的时候,做了 try catch处理,手动捕获了 badTokenException 异常。
那么如何打造一个Safe Toast 来规避Android 7.1可能遇到的问题呢,在Toast与Snackbar的那点事儿这篇文章中也已经写得很明白,将 NMS 管理Toast的代码移出来,形成一个自己的ToastManager,然后思路和Android O一致,在真正 addView() 的地方,加一个try catch去保护。至于后续文章中提到的为了适配 Android 7.1 而采用的SnackBar策略。自己觉得如果对应用自身TargetSdk版本没有特别高要求的话,可以暂时不升到26。这样的话,即使不try catch 的话,Android 7.1 也不会产生 BadTokenException问题。
参考文章:http://zhuanlan.51cto.com/art/201804/569585.htm
最后编辑于
:
©
著作权归作者所有,转载或内容合作请联系作者
人面猴
序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
沈念sama阅读 174,493评论 5赞 416
死咒
序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
沈念sama阅读 73,611评论 2赞 334
救了他两次的神仙让他今天三更去死
文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
开封第一讲书人阅读 123,360评论 0赞 288
道士缉凶录:失踪的卖姜人
文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
开封第一讲书人阅读 47,406评论 0赞 245
港岛之恋(遗憾婚礼)
正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
茶点故事阅读 56,176评论 3赞 325
恶毒庶女顶嫁案:这布局不是一般人想出来的
文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
开封第一讲书人阅读 42,743评论 1赞 241
城市分裂传说
那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
沈念sama阅读 33,832评论 3赞 355
双鸳鸯连环套:你想象不到人心有多黑
文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
开封第一讲书人阅读 32,406评论 0赞 228
万荣杀人案实录
序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
沈念sama阅读 36,439评论 1赞 269
护林员之死
正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
茶点故事阅读 32,120评论 2赞 275
白月光启示录
正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
茶点故事阅读 33,773评论 1赞 288
活死人
序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
沈念sama阅读 29,889评论 3赞 284
日本核电站爆炸内幕
正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
茶点故事阅读 35,050评论 3赞 279
男人毒药:我在死后第九天来索命
文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
开封第一讲书人阅读 27,059评论 0赞 11
一桩弑父案,背后竟有这般阴谋
文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
开封第一讲书人阅读 28,223评论 1赞 232
情欲美人皮
我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
沈念sama阅读 38,151评论 2赞 309
代替公主和亲
正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
茶点故事阅读 37,849评论 2赞 313
推荐阅读更多精彩内容
拥有这些网址,将成为电脑高手!转载收藏吧
http://www.huai235.com/网址大全http://www.quzhuanpan.com/ 转盘网...
haoning7788阅读 389评论 0赞 8
《通往财富自由之路》专栏留言版(七)
落后 (一) “落后”这个观念,主要讲了这三个问题: ...
一访文阅读 329评论 0赞 7
从1.6W名面试者中收集的Java面试题精选汇总(内附知识脑图)
本篇的面试题是接之前读者的要求,发出来的。 首先,声明下,以下知识点并非全部来自BAT的面试题。 如果觉得在本文中...
程序员技术圈阅读 3,922评论 6赞 205
假如我是一个妈妈,该如何安排自己的一切?
之所以会想到这个主题,是因为看到一位读博的妈妈,凌晨5店雷打不动开始做功课,今年已经带着孩子上学了,由此,就明白一...
海豚的世界阅读 124评论 0赞 0
挑战通灵者究竟可不可信?
最近迷上了俄罗斯综艺节目——《挑战通灵者》。这是一档尺度较大内容争议的真人秀,关于心灵、灵修、灵媒、宗教等,凡是身...
林果儿shirley阅读 13,180评论 1赞 2