當咱們將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
參考線程