当咱们将targetSDK升级到26以上后,发现项目中报告了不少BadTokenException异常,查看堆栈几乎都与Toast有关:java
经过堆栈查看源码知道Toast是经过内部类TN的handleShow()
方法来展现浮窗,而这个方式是可能会抛出WindowManager.BadTokenException异常的,虽然api26以后google对这个异常进行了捕获,使其不至于形成应用crash,但在26以前并无作任何处理:segmentfault
在api26以前(特别是26)的机器上有一个稳定复现的路径,在主线程调用Toast的show方法后,阻塞3s左右就会抛出上面的BadTokenException异常并致使crash:api
QQToast.makeText(this, "哈哈哈", Toast.LENGTH_SHORT).show();
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
复制代码
这是能够获得crash堆栈: ide
那么在api26以前,咱们能够模仿Android8中google针对这个异常的处理方式,经过反射自定义一个Handler的代理,使其捕获这个异常,从而保证应用不会所以而crashui
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 (Throwable throwable) {
GLog.e(TAG, "toast error: " + throwable.getMessage());
}
}
}
复制代码
首先定义个Handler的代理,主要用来对Toast中TN的Handler作一个封装this
下面经过反射的方式对Toast中TN的Handler作处理:google
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
try {
/** * 获取mTN对象 * 并获取它的class类型 */
Class<Toast> clazzToast = Toast.class;
Field fieldTN = clazzToast.getDeclaredField("mTN");
fieldTN.setAccessible(true);
Object objTn = fieldTN.get(toast);
Class clazzTn = objTn.getClass();
/** * 获取TN中的mHandler对象 * 而后用咱们自定义的HandlerProxy类包裹它 * 使得它能捕获异常 */
Field fieldHandler = clazzTn.getDeclaredField("mHandler");
fieldHandler.setAccessible(true);
fieldHandler.set(objTn, new HandlerProxy((Handler) fieldHandler.get(objTn)));
} catch (Throwable throwable) {
GLog.e(TAG, "hack toast handler error: " + throwable.getMessage());
}
}
复制代码
代码注释写的比较明白,也不难。其实这里就主要是模仿8.0的处理方式来捕获了这个BadTokenExceptionspa
参考线程