Android Toast BadTokenException分析及解决方案

现象

Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@fcd9ef6 is not valid; is your activity running?
       at android.view.ViewRootImpl.setView(ViewRootImpl.java:806)
       at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:369)
       at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
       at android.widget.Toast$TN.handleShow(Toast.java:459)
       at android.widget.Toast$TN$2.handleMessage(Toast.java:342)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:186)
       at android.app.ActivityThread.main(ActivityThread.java:6491)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:914)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:804)

原因

Toast显示时会有一个Token,这个Token是由WindowManager创建并管理的,重点是这个Token有时间限制,当超过了一个Toast的显示时长(LENGTH_LONG)后,会把这个Token置为无效。所以把Toast延迟显示后,使用的Token就已经过期了。

这个问题发生在7.x系统上,在8.x的系统已经把显示Toast视图的位置添加了try catch捕获,所以如果只需在适配7.x的系统项目才需要解决这个问题。

解决方案

反射代理Toast&NT$Handler。

利用反射机制,改由我们自己Handler处理Toast的显示,并添加BadTokenException异常捕获。

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.StringRes;
import android.widget.Toast;

import java.lang.reflect.Field;

public class SafeToast {
    private static Field sField_TN;
    private static Field sField_TN_Handler;
    private static Toast mToast;


    static {
        try {
            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() {
    }


    public static void show(Context context, CharSequence message, int duration) {
        if (mToast == null) {
            mToast = Toast.makeText(context.getApplicationContext(), message, duration);
            hook(mToast);
        } else {
            mToast.setDuration(duration);
            mToast.setText(message);
        }
        mToast.show();
    }

    public static void show(Context context, @StringRes int resId, int duration) {
        if (mToast == null) {
            mToast = Toast.makeText(context.getApplicationContext(), resId, duration);
            hook(mToast);
        } else {
            mToast.setDuration(duration);
            mToast.setText(context.getString(resId));
        }
        mToast.show();
    }

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


    private static class SafeHandler extends Handler {
        private Handler impl;

        public SafeHandler(Handler impl) {
            this.impl = impl;
        }

        @Override
        public void dispatchMessage(Message msg) {
            try {
                super.dispatchMessage(msg);
            } catch (Exception e) {
            }
        }

        @Override
        public void handleMessage(Message msg) {
            impl.handleMessage(msg);//需要委托给原Handler执行
        }
    }
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值