Android 7.1.1(api25) Toast BadTo

作者: 蓅哖伊人为谁笑 | 来源:发表于2018-05-16 14:48 被阅读128次

    1.问题抛出

    在android 7.1.1上 如果同时展示两个windowmanager.LayoutParams.type = type_toast,那么必现下面的报错信息,测试用例的代码 也在下面贴出:

    测试用例代码:

     WindowManager mw = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
            TextView tv = new TextView(this);
            tv.setLayoutParams(new WindowManager.LayoutParams(-2,-2));
            tv.setText("windowmanager 添加悬浮窗");
            WindowManager.LayoutParams  params =new WindowManager.LayoutParams();
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            mw.addView(tv,params);
    
            Toast toast = new Toast(this);
            toast.makeText(this,"API25 type_toast",Toast.LENGTH_LONG).show();
    
    

    必现报错信息

    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.tmall.android/com.tmall.android.MainActivity}: android.view.WindowManager$BadTokenException: Unable to add window -- window android.view.ViewRootImpl$W@3afb76 has already been added
                                                                         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
                                                                         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
                                                                         at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4519)
                                                                         at android.app.ActivityThread.-wrap19(ActivityThread.java)
                                                                         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1483)
                                                                         at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                         at android.os.Looper.loop(Looper.java:154)
                                                                         at android.app.ActivityThread.main(ActivityThread.java:6119)
                                                                         at java.lang.reflect.Method.invoke(Native Method)
                                                                         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                                                                         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
    

    虽然报错的异常类型是WindowManager$BadTokenException
    但是仔细看后面window android.view.ViewRootImpl$W@3afb76 has already been added,给出的是一个windowToken重复添加view的解释,因为在android 7.1.1上谷歌对type_toast类型的弹框做了限制,同一时刻 对于一个app 不能展示两个type_toast类型的弹框,因为该类型可以覆盖到其他程序上,造成不好的体验。

    2.Toast弹出流程图

    为了明白它是怎么将toast的视图添加到窗口中展示到我们眼前,以及明白这个BadTokenException异常是如果被抛出的,画了一张简要流程图,远程服务与本地通信都写在了图中,文字不在铺张开来

    image.png

    3. 7.1.1与其他版本发起Toast的差异

    android 7.1.1(Api25)上Toast请求windowmanager添加视图


    image.png

    android 8.0(Api27)上Toast 请求windowmanager添加视图


    image.png

    在更高的版本中 尽管还会抛出错误,但是谷歌在内部捕获了异常 ,不会致使程序Crash了,机智的谷歌。

    4.问题解决

    如果你对源码够了解看[2]的流程图就会知道着手点还是Toast,了解的不深也可以从[3]中看到 Toast的视图添加是在Toast&NT&handleShow方法中被发起的,,而且谷歌也在更高版本中在该方法中添加了异常捕获。那么我们怎么在7.1.1上解决这个问题呢,反射代理Toast&NT$Handler

    package com.tmall.android.toast;
    
    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) {
    
            }
        }
    
        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) {
            }
        }
    
    
        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执行
            }
        }
    }
    
    

    5.再次验证

    不会崩溃,但是同时显示两个type_toast类型的悬浮窗异常依旧会抛出,但是被我们捕获了。

     WindowManager mw = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
            TextView tv = new TextView(this);
            tv.setLayoutParams(new WindowManager.LayoutParams(50,50));
            tv.setBackgroundColor(Color.WHITE);
            tv.setText("windowmanager 添加悬浮窗");
            WindowManager.LayoutParams  params =new WindowManager.LayoutParams();
            params.width=50;
            params.height=50;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            mw.addView(tv,params);
    
            //Toast toast = new Toast(this);
            // toast.makeText(this,"API25 type_toast",Toast.LENGTH_LONG).show();
    
            SafeToast.show(this,"safe_toast on android 7.1.1", Toast.LENGTH_LONG);
    
    

    相关文章

      网友评论

      • zbiext:难怪,7.1的吐司,会报,我场景是这样的 camera /photo使用(因为用的系统的)时,会切换到别app界面去,然后这时候自己app有弹吐司的逻辑的话,貌似就出现同样的crash

      本文标题:Android 7.1.1(api25) Toast BadTo

      本文链接:https://www.haomeiwen.com/subject/wivpdftx.html