Android知识Android开发经验谈Android开发

Android 7.1.1(api25) Toast BadTo

2018-05-16  本文已影响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);

上一篇下一篇

猜你喜欢

热点阅读