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); } }