任何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解决方案。