Toast做爲Android應用中最多見的一種提示方式,因爲簡單的api設計和簡潔的交互體驗被咱們普遍使用,可是這並表明他很完美,本文將記錄我在開發中遇到的問題。java
最近項目好多用戶反應有bug,而後看log出現了一個奇怪的問題,並且次數不不少,以下:android
#1664 android.view.WindowManager$BadTokenException
Unable to add window -- window android.view.ViewRootImpl$W@4a51004 has already been added
android.view.ViewRootImpl.setView(ViewRootImpl.java:695)
android.view.ViewRootImpl.setView(ViewRootImpl.java:691)
android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
android.widget.Toast$TN.handleShow(Toast.java:506)
android.widget.Toast$TN$2.handleMessage(Toast.java:389)
android.os.Handler.dispatchMessage(Handler.java:102)
android.os.Looper.loop(Looper.java:154) android.app.ActivityThread.main(ActivityThread.java:6292) java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
複製代碼
而後發現竟然都是7.1.1設備纔出現的,見下圖:api
首先Toast顯示依賴於一個窗口,這個窗口被WMS管理(WindowManagerService),當須要show的時候這個請求會放在WMS請求隊列中,而且會傳遞一個TN類型的Bider對象給WMS,WMS並生成一個token傳遞給Android進行顯示與隱藏,可是若是UI線程的某個線程發生了阻塞,而且已經NotificationManager檢測已經超時就不刪除token記錄,此時token已通過期,阻塞結束的時候再顯示的時候就發生了異常。bash
在android7.1.1的Toast源碼handleShow是這樣寫的:app
mWM.addView(mView, mParams);
複製代碼
而在8.0則是這樣的:ide
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
複製代碼
到這裏能看到在發生異常的時候使用了try catch捕獲,程序不會掛掉oop
/** * @author CH * @date 2018/6/26 * 部分7.1.1手機崩潰Toast解決方案 */
public class ToastCompat {
private static Field sField_TN;
private static Field sField_TN_Handler;
private Toast mToast;
static {
try {
sField_TN = Toast.class.getDeclaredField("mTN");
sField_TN.setAccessible(true);
sField_TN_Handler = sField_TN.getType().getDeclaredField("mHandler");
sField_TN_Handler.setAccessible(true);
} catch (Exception e) {
}
}
private static void hook(Toast toast) {
try {
Object tn = sField_TN.get(toast);
Handler preHandler = (Handler) sField_TN_Handler.get(tn);
sField_TN_Handler.set(tn, new SafelyHandlerWarpper(preHandler));
} catch (Exception e) {
}
}
public void showToast(Context context, CharSequence cs, int length) {
if (mToast == null) {
mToast = Toast.makeText(context, cs, length);
} else {
mToast.setText(cs);
}
hook(mToast);
mToast.show();
}
public static class SafelyHandlerWarpper extends Handler {
private Handler impl;
public SafelyHandlerWarpper(Handler impl) {
this.impl = impl;
}
@Override
public void dispatchMessage(Message msg) {
try {
super.dispatchMessage(msg);
} catch (Exception e) {
}
}
@Override
public void handleMessage(Message msg) {
impl.handleMessage(msg);//須要委託給原Handler執行
}
}
}
複製代碼
簡單來講就是經過反射注入在發生異常的地方進行try catchthis
聲明:此解決方案參考QQ音樂團隊spa