Android 7.x Toast BadTokenException processing

Keywords: Google

In version 7.x, Token verification is added to Toast, which is true. However, when show() is called to display Toast, if a time-consuming operation gets stuck in the main thread for more than 5 seconds, BadTokenException exception will be thrown. At the beginning of 8.x system, Google will try catch internally. The 7.x system is a permanent pain, which can only be fixed by ourselves.

Repair plan I

Reflect the Context of the agent View, try catch within the Context, and deal with the badtokeneexception problem of Toast

. BadTokenListener, Toast throws a BadTokenException listener

publicinterface BadTokenListener {
    /**
     * Callback when Toast throws BadTokenException
     *
     * @param toast Toast instance with exception
     */
    void onBadTokenCaught(@NonNull Toast toast);
}

. SafeToastContext, the Context used for wrapping Toast

publicclass SafeToastContext extends ContextWrapper {
    private Toast mToast;
    private BadTokenListener mBadTokenListener;

    public SafeToastContext(Context base, Toast toast) {
        super(base);
        mToast = toast;
    }

    public void setBadTokenListener(@NonNull BadTokenListener badTokenListener) {
        mBadTokenListener = badTokenListener;
    }

    @Override
    public Context getApplicationContext() {
        //Proxy original Context
        returnnew ApplicationContextWrapper(super.getApplicationContext());
    }

    privateclass ApplicationContextWrapper extends ContextWrapper {
        public ApplicationContextWrapper(Context base) {
            super(base);
        }

        @Override
        public Object getSystemService(String name) {
            if (Context.WINDOW_SERVICE.equals(name)) {
                //Get the original WindowManager and give it to WindowManagerWrapper agent to catch BadTokenException exception
                Context baseContext = getBaseContext();
                returnnew WindowManagerWrapper((WindowManager) baseContext.getSystemService(name));
            }
            returnsuper.getSystemService(name);
        }
    }

    privateclass WindowManagerWrapper implements WindowManager {
        /**
         * Wrapped WindowManager instance
         */
        private WindowManager mImpl;

        public WindowManagerWrapper(@NonNull WindowManager readImpl) {
            mImpl = readImpl;
        }

        @Override
        public Display getDefaultDisplay() {
            return mImpl.getDefaultDisplay();
        }

        @Override
        public void removeViewImmediate(View view) {
            mImpl.removeViewImmediate(view);
        }

        @Override
        public void addView(View view, ViewGroup.LayoutParams params) {
            //Use the tool in addView to catch BadTokenException exception
            try {
                mImpl.addView(view, params);
            } catch (BadTokenException e) {
                e.printStackTrace();
                if (mBadTokenListener != null) {
                    mBadTokenListener.onBadTokenCaught(mToast);
                }
            }
        }

        @Override
        public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
            mImpl.updateViewLayout(view, params);
        }

        @Override
        public void removeView(View view) {
            mImpl.removeView(view);
        }
    }
}

. ToastCompat, replacing Toast's facade

public class ToastCompat extends Toast {
    private Toast mToast;

    public ToastCompat(Context context, Toast toast) {
        super(context);
        mToast = toast;
    }

    public static ToastCompat makeText(Context context, CharSequence text, int duration) {
        @SuppressLint("ShowToast")
        Toast toast = Toast.makeText(context, text, duration);
        setContextCompat(toast.getView(), context);
        returnnew ToastCompat(context, toast);
    }

    public static Toast makeText(Context context, @StringRes int resId, int duration)
            throws Resources.NotFoundException {
        return makeText(context, context.getResources().getText(resId), duration);
    }

    public@NonNull
    ToastCompat setBadTokenListener(@NonNull BadTokenListener listener) {
        final Context context = getView().getContext();
        if (context instanceof SafeToastContext) {
            ((SafeToastContext) context).setBadTokenListener(listener);
        }
        returnthis;
    }

    @Override
    public void show() {
        mToast.show();
    }

    @Override
    public void setDuration(int duration) {
        mToast.setDuration(duration);
    }

    @Override
    public void setGravity(int gravity, int xOffset, int yOffset) {
        mToast.setGravity(gravity, xOffset, yOffset);
    }

    @Override
    public void setMargin(float horizontalMargin, float verticalMargin) {
        mToast.setMargin(horizontalMargin, verticalMargin);
    }

    @Override
    public void setText(int resId) {
        mToast.setText(resId);
    }

    @Override
    public void setText(CharSequence s) {
        mToast.setText(s);
    }

    @Override
    public void setView(View view) {
        mToast.setView(view);
        setContextCompat(view, new SafeToastContext(view.getContext(), this));
    }

    @Override
    public float getHorizontalMargin() {
        return mToast.getHorizontalMargin();
    }

    @Override
    public float getVerticalMargin() {
        return mToast.getVerticalMargin();
    }

    @Override
    public int getDuration() {
        return mToast.getDuration();
    }

    @Override
    public int getGravity() {
        return mToast.getGravity();
    }

    @Override
    public int getXOffset() {
        return mToast.getXOffset();
    }

    @Override
    public int getYOffset() {
        return mToast.getYOffset();
    }

    @Override
    public View getView() {
        return mToast.getView();
    }

    @NonNull
    public Toast getBaseToast() {
        return mToast;
    }

    /**
     * Reflection sets the Context in View. Toast will get the Context of View to get WindowManager
     */
    private static void setContextCompat(@NonNull View view, @NonNull Context context) {
        //7.1.1
        if (Build.VERSION.SDK_INT == 25) {
            try {
                Field field = View.class.getDeclaredField("mContext");
                field.setAccessible(true);
                field.set(view, context);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }
}

. use ToastCompat instead of Toast

ToastCompat.makeText(context,"I am Toast Content", Toast.LENGTH_SHORT)
    .setBadTokenListener(toast ->
            Logger.d("Toast: " + toast + " => Throw out BadTokenException abnormal")
    )
    .show();

Repair plan II

Hook Toast, replace the Handler of TN object in Toast, and try catch the method of dispatchMessage()

public class SafeToast {
    privatestatic Field sField_TN;
    privatestatic Field sField_TN_Handler;

    static {
        try {
            //Reflection getting TN object
            sField_TN = Toast.class.getDeclaredField("mTN");
            sField_TN.setAccessible(true);
            sField_TN_Handler = sField_TN.getType().getDeclaredField("mHandler");
            sField_TN_Handler.setAccessible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private SafeToast() {
    }

    @SuppressLint("ShowToast")
    public static void show(Context context, CharSequence message, int duration) {
        show(context, message, duration, null);
    }

    @SuppressLint("ShowToast")
    public static void show(Context context, CharSequence message, int duration, BadTokenListener badTokenListener) {
        Toast toast = Toast.makeText(context.getApplicationContext(), message, duration);
        hook(toast, badTokenListener);
        toast.setDuration(duration);
        toast.setText(message);
        toast.show();
    }

    @SuppressLint("ShowToast")
    public static void show(Context context, @StringRes int resId, int duration) {
        show(context, resId, duration, null);
    }

    @SuppressLint("ShowToast")
    public static void show(Context context, @StringRes int resId, int duration, BadTokenListener badTokenListener) {
        Toast toast = Toast.makeText(context.getApplicationContext(), resId, duration);
        hook(toast, badTokenListener);
        toast.setDuration(duration);
        toast.setText(context.getString(resId));
        toast.show();
    }

    private static void hook(Toast toast, BadTokenListener badTokenListener) {
        try {
            Object tn = sField_TN.get(toast);
            Handler originHandler = (Handler) sField_TN_Handler.get(tn);
            sField_TN_Handler.set(tn, new SafeHandler(toast, originHandler, badTokenListener));
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * Replace Toast's original Handler
     */
    privatestaticclass SafeHandler extends Handler {
        private Toast mToast;
        private Handler mOriginImpl;
        private BadTokenListener mBadTokenListener;

        SafeHandler(Toast toast, Handler originHandler, BadTokenListener badTokenListener) {
            mToast = toast;
            mOriginImpl = originHandler;
            mBadTokenListener = badTokenListener;
        }

        @Override
        public void dispatchMessage(Message msg) {
            //Try catch the processing method of distributing Message
            try {
                super.dispatchMessage(msg);
            } catch (WindowManager.BadTokenException e) {
                e.printStackTrace();
                if (mBadTokenListener != null) {
                    mBadTokenListener.onBadTokenCaught(mToast);
                }
            }
        }

        @Override
        public void handleMessage(Message msg) {
            //Need to delegate to the original Handler for execution
            mOriginImpl.handleMessage(msg);
        }
    }
}

. use safeload instead of Toast

SafeToast.show(context,"I am Toast Content", Toast.LENGTH_SHORT, toast ->
            Logger.d("Toast: " + toast + " => Throw out BadTokenException abnormal"));
Published 47 original articles, won praise 3, visited 1350
Private letter follow

Posted by JehovahsWord on Tue, 17 Mar 2020 08:57:50 -0700