前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Toast BadTokenException

Toast BadTokenException

作者头像
提莫队长
发布2021-02-22 14:43:11
9550
发布2021-02-22 14:43:11
举报
文章被收录于专栏:刘晓杰刘晓杰

公司的产品在线上一直会报错BadTokenException

Toast报错信息

从报错信息看应该是Toast的。而且都在7.1.2以下,也就是API25以下。查看Toast的handleShow方法(api27和api25),发现是因为 mWM.addView(mView, mParams); 这一段代码在 25 没有 try catch,而 27 是加了 try catch 的。 鉴于 handleShow 是 Toast 的内部类 TN 里面的方法。因此一共有两种思路

1.handleShow添加try-catch

对Toast进行Hook,替换Toast中TN对象的Handler,对分发消息handleMessage()方法进行try-catch

代码语言:javascript
复制
public class SafeToast {
    private static Field mTN;
    private static Field mTNHandler;

    static {
        try {
            //反射获取TN对象
            mTN = Toast.class.getDeclaredField("mTN");
            mTN.setAccessible(true);
            mTNHandler = mTN.getType().getDeclaredField("mHandler");
            mTNHandler.setAccessible(true);
        } catch (Exception e) {
            Log.e("SafeToast", e.getMessage());
        }
    }

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

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

    public static void show(Context context, int resId, int duration) {
        show(context, resId, duration, null);
    }

    public static void show(Context context, 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 = mTN.get(toast);
            Handler originHandler = (Handler) mTNHandler.get(tn);
            mTNHandler.set(tn, new SafeHandler(toast, originHandler, badTokenListener));//用 SafeHandler 替换原来的 handler
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * 替换Toast原有的Handler
     */
    private static class 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 handleMessage(Message msg) {
            try {
                //需要委托给原Handler执行.原Handler会在这里面handleShow
                mOriginImpl.handleMessage(msg);
            } catch (WindowManager.BadTokenException e) {
                e.printStackTrace();
                if (mBadTokenListener != null) {
                    mBadTokenListener.onBadTokenCaught(mToast);
                }
            }
        }
    }
}

顺便贴上 BadTokenListener 代码

代码语言:javascript
复制
public interface BadTokenListener {
    /**
     * 当Toast抛出BadTokenException时回调
     *
     * @param toast 发生异常的Toast实例
     */
    void onBadTokenCaught(@NonNull Toast toast);
}

2.mWM.addView添加try-catch

注意这三句代码

代码语言:javascript
复制
Context context = mView.getContext().getApplicationContext();
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);

从下往上,我们需要 (1)WindowManager的代理类WindowManagerWrapper (2)重写getSystemService,在里面获取WindowManagerWrapper (3)重写 getApplicationContext

代码语言:javascript
复制
/**
 * 只需要把 SafeToastContext 替换原来的 Toast 里面 mView.getContext()
 */
public class SafeToastContext extends ContextWrapper {

    private Toast mToast;
    private BadTokenListener mBadTokenListener;

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

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

    @Override
    public Context getApplicationContext() {
        //代理原本的Context
        return new ApplicationContextWrapper(super.getApplicationContext());
    }

    private class ApplicationContextWrapper extends ContextWrapper {

        public ApplicationContextWrapper(Context base) {
            super(base);
        }

        @Override
        public Object getSystemService(String name) {
            if (Context.WINDOW_SERVICE.equals(name)) {
                Context baseContext = getBaseContext();
                // 获取自定义 WindowManagerWrapper
                return new WindowManagerWrapper((WindowManager) baseContext.getSystemService(name));
            }
            return super.getSystemService(name);
        }
    }

    private class WindowManagerWrapper implements WindowManager {

        private WindowManager mImpl;

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

        /**
         * mWM.addView(mView, mParams); 在这里 try catch
         */
        @Override
        public void addView(View view, ViewGroup.LayoutParams params) {
            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);
        }

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

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

然后用ToastCompat替换Toast

代码语言:javascript
复制
public class ToastCompat extends Toast {

    private Toast mToast;

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

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

    public static ToastCompat makeText(Context context, CharSequence text, int duration) {
        Toast toast = Toast.makeText(context, text, duration);
        setContextCompat(toast.getView(), new SafeToastContext(context, toast));
        return new ToastCompat(context, toast);
    }

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

    /**
     * 反射设置View中的Context,Toast会获取View的Context来获取WindowManager
     */
    private static void setContextCompat(View view, 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();
            }
        }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

参考文献

Android 7.x Toast BadTokenException处理 # Toast与Snackbar的那点事

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.handleShow添加try-catch
  • 2.mWM.addView添加try-catch
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档