关于Android7.x系统Toast显示异常BadTokenException解决方案
BadTokenException详细及原因
Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@fcd9ef6 is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:806)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:369)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
at android.widget.Toast$TN.handleShow(Toast.java:459)
at android.widget.Toast$TN$2.handleMessage(Toast.java:342)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:186)
at android.app.ActivityThread.main(ActivityThread.java:6491)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:914)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:804)
这是线上一直存在的一个异常,由于无法复现,所以没有管他。最近几天崩溃数暴增,忍无可忍,通过参考美团的这篇文章把这个异常解决了一下。
复现步骤
调用Toast的show()方法后,将主线程阻塞5s,即可稳定复现这个异常
Toast.makeToast(context, "123", LENGTH_LONG).show();
Thread.sleep(5*1000);
出现这个异常的原因就是Toast显示时会有一个Token,这个Token是由WindowManager创建并管理的,重点是这个Token有时间限制,当超过了一个Toast的显示时长(LENGTH_LONG)后,会把这个Token置为无效。所以把Toast延迟显示后,使用的Token就已经过期了。
这个问题全部发生在7.x系统上,在8.x的系统已经把显示Toast视图的位置添加了try catch捕获。如下代码所示:
public void handleShow(IBinder windowToken) {
...
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
}
解决方案
在Android7.x系统上,利用反射机制,改由我们自己Handler处理Toast的显示,并添加BadTokenException异常捕获。
int sdkInt = Build.VERSION.SDK_INT;
if (sdkInt >= Build.VERSION_CODES.N && sdkInt < Build.VERSION_CODES.O && !isReflectedHandler) {
reflectTNHandler(mToast);
//这里为了避免多次反射,使用一个标识来限制
isReflectedHandler = true;
}
private static void reflectTNHandler(Toast toast) {
try {
Field tNField = toast.getClass().getDeclaredField("mTN");
if (tNField == null) {
return;
}
tNField.setAccessible(true);
Object TN = tNField.get(toast);
if (TN == null) {
return;
}
Field handlerField = TN.getClass().getDeclaredField("mHandler");
if (handlerField == null) {
return;
}
Timber.d("TN class is %s.", TN.getClass());
handlerField.setAccessible(true);
handlerField.set(TN, new ProxyTNHandler(TN));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
- 反射拿到Toast的成员变量TN mTN
- 反射拿到mTN的成员变量Handler mHandler
- 替换为我们自己的Handler实现
- 在show Toast时添加异常捕获,并调用mTN响应的Toast显示方法handleShow
//Toast$TN持有的Handler变量
private static class ProxyTNHandler extends Handler {
private Object tnObject;
private Method handleShowMethod;
private Method handleHideMethod;
ProxyTNHandler(Object tnObject) {
this.tnObject = tnObject;
try {
this.handleShowMethod = tnObject.getClass().getDeclaredMethod("handleShow", IBinder.class);
this.handleShowMethod.setAccessible(true);
Timber.d("handleShow method is %s", handleShowMethod);
this.handleHideMethod = tnObject.getClass().getDeclaredMethod("handleHide");
this.handleHideMethod.setAccessible(true);
Timber.d("handleHide method is %s", handleHideMethod);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0: {
//SHOW
IBinder token = (IBinder) msg.obj;
Timber.d("handleMessage(): token is %s", token);
if (handleShowMethod != null) {
try {
handleShowMethod.invoke(tnObject, token);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (WindowManager.BadTokenException e) {
//显示Toast时添加BadTokenException异常捕获
e.printStackTrace();
Timber.e(e, "show toast error.");
}
}
break;
}
case 1: {
//HIDE
Timber.d("handleMessage(): hide");
if (handleHideMethod != null) {
try {
handleHideMethod.invoke(tnObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
break;
}
case 2: {
//CANCEL
Timber.d("handleMessage(): cancel");
if (handleHideMethod != null) {
try {
handleHideMethod.invoke(tnObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
break;
}
}
super.handleMessage(msg);
}
}