错误信息
android.view.WindowManager$BadTokenException: Unable to add window
-- token android.os.BinderProxy@e428e31 is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:679)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
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:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
复现方式
targetSdk
升至26及以上,Android 7.x手机,代码调用toast.show()
之后,UI线程立即耗时操作2s以上,即会Crash。
原因分析
Toast最终是通过内部类TN的handleShow()
方法展示浮窗:
8.0的toast源码:
public void handleShow(IBinder windowToken) {
...
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
}
}
7.x上:
public void handleShow(IBinder windowToken) {
...
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
这里不深究什么会抛出BadTokenException
异常,有兴趣可以参考这篇文章。
这里handler在处理事件执行addView时,抛出了异常,由于没有try-catch
,导致7.x手机上crash。
解决办法
由于涉及跨线程,所以我们不能直接对Toast嵌套try-catch
。处理办法就是仿造8.0,在handle
时捕获异常。
解决方案即通过反射,替换TN的mHandler
为下面我们的代理类,然后在代理类中try-catch
:
private static class HandlerProxy extends Handler {
private Handler mHandler;
public HandlerProxy(Handler handler) {
this.mHandler = handler;
}
@Override
public void handleMessage(Message msg) {
try {
mHandler.handleMessage(msg);
} catch (WindowManager.BadTokenException e) {
//ignore
}
}
}
Hook方法为:
public static void hookToast(Toast toast) {
Class<Toast> cToast = Toast.class;
try {
//TN是private的
Field fTn = cToast.getDeclaredField("mTN");
fTn.setAccessible(true);
//获取tn对象
Object oTn = fTn.get(toast);
//获取TN的class,也可以直接通过Field.getType()获取。
Class<?> cTn = oTn.getClass();
Field fHandle = cTn.getDeclaredField("mHandler");
//重新set->mHandler
fHandle.setAccessible(true);
fHandle.set(oTn, new HandlerProxy((Handler) fHandle.get(oTn)));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
最终调用:
mToast = Toast.makeText(context, message, duration);
hookToast(mToast);
mToast.show();
有兴趣可以自己做下封装。
网友评论