Toast系列(二):Toast基本工作原理(android 7.1变化)

任何UI都是通过window的方式来展现,如Activity,Dialog等,Toast也不例外,每种window添加时都会指定类型,Toast的类型为TYPE_TOAST。

TN() {

            final WindowManager.LayoutParams params = mParams;
            ...
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            ...
        }

TYPE_TOAST类型的窗口可自由显示在屏幕上,不依赖于其他任何窗口,可以凌驾于运行在前台的其他app之上。在Android7.1之前很多应用都通过指定添加的窗口类型为TYPE_TOAST,实现屏幕悬浮球。为避免这种TYPE_TOAST的滥用,从Android 7.1开始,对TYPE_TOAST类型的窗口进行了限制——通过Token失效的方式避免TYPE_TOAST类型的窗口长时间显示不消失。Toast的源码也相应做出了变化。

在7.1之前,TN对象添加Toast窗口是这样实现的。

先是定义了用于handler执行的Runnable对象mShow

        final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };

然后,show方法直接通过handler发送mShow对象执行

        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

最后在handleShow方法里添加窗口。

android 7.1开始,show逻辑不再通过handler执行Runnable的形式,而是通过handler发送message消息,然后在handler的handleMessage里执行handleShow的逻辑。

        @Override
        public void show(IBinder windowToken) {
            mHandler.obtainMessage(0, windowToken).sendToTarget();
        }

在这里,为message传入了一个windowToken,windowToken在Toast发起显示请求,加入ITransientNotification维护的队列时创建,待轮到该Toast显示,调用它TN对象的show方法时传入。

然后,在handleMessage里处理

        final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                IBinder token = (IBinder) msg.obj;
                handleShow(token);
            }
        };

windowToken会继续传入handleShow方法里

        public void handleShow(IBinder windowToken) {
            if (mView != mNextView) {
                ...
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                ...
                mParams.token = windowToken;
                ...
                mWM.addView(mView, mParams);
                ...
            }
        }

当Toast的时长耗尽,ITransientNotification会将其从队列中去除并将windowToken设为失效,调用其TN对象的hide方法隐藏Toast窗口。

这种设计,引入了一个系统bug,Toast之BadTokenException。产生这个异常的原因以及解决方案可查看我的另一篇文章Toast系列(四):Android 7.1系统Toast BadTokenException解决方案。

你可能感兴趣的:(源码)