1. Phenomenon analysis

When we upgraded targetSDK beyond 26, we found that a lot of BadTokenExceptions were reported in the project, looking at the stack almost all related to Toast:

Through the stack view source TN know Toast is through the inner class handleShow () method to show floating window, and the way it is possible to sell WindowManager. BadTokenException abnormal, although api26 after Google to capture this exception, So that it does not crash the application, but nothing is done before 26:


On machines prior to api26 (especially 26), there was a stable reoccurrence path. After the main thread called Toast’s show method, blocking for about 3s would throw the above BadTokenException and cause crash:

QQToast.makeText(this."Ha ha ha.", Toast.LENGTH_SHORT).show();
try {
    Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
Copy the code

It is possible to get crash stack:

2. Solutions

So before api26, we can imitate the way Google handles this exception in Android8. By reflection, we can define a proxy of the Handler to catch this exception, so as to ensure that the application will not crash

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()); }}}Copy the code

First, define a Handler proxy, mainly used to encapsulate TN Handler in Toast

The TN Handler in Toast is reflected as follows:

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
    try {
        /** * gets the mTN object * and gets its class type */
        Class<Toast> clazzToast = Toast.class;
        Field fieldTN = clazzToast.getDeclaredField("mTN");
        fieldTN.setAccessible(true);
        Object objTn = fieldTN.get(toast);
        Class clazzTn = objTn.getClass();
        /** * get the mHandler object in TN * and wrap it with our custom HandlerProxy class * so it can catch exceptions */
        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()); }}Copy the code

Code comments are relatively straightforward and not difficult to write. In fact, the BadTokenException is mainly captured in the same way as 8.0

reference

Android 7.X Toast Bug

On Android7.x system Toast displays BadTokenException exception solution

Toast the BadTokenException