序言
Toast.makeText(context,"msg",Toast.Length_SHORT).show();
我们都知道调用了这行代码,便会触发显示 Toast 信息,但是从调用开始到显示出来的具体过程是怎么样的,里面具体实现又是怎么样的呢?
原理
在 Toast 内部有两类 IPC 过程。
- 第一类: Toast 访问 NotificationManagerService
- 第二类:NotificationManagerService 回调 Toast 里的 TN 接口。
Toast 属于系统 Window,它内部的视图由两种方式指定,一种是系统默认的样式,另一种是自定义一个 view 然后通过 setView 方法将 view 设置进去。
不管哪一种方式,它们都对应 Toast 的一个 View 类型的内部成员 mNextView。
Toast 提供 show 和 cancel 方法分别用于显示和隐藏 view,它们的内部都是一个 IPC 过程。
Toast 有定时取消功能,所以系统采用了 Handler(利用其中的 sendMessageDelayed() 方法)
Toast.show() 调用流程大致如下:

先来看看 Toast.makeText 方法
再瞧一瞧 Toast.show(); 方法
这里面有几个地方看起来比较陌生 INotificationManager 是什么,TN 又是什么?
INotificationManager 的值是从 getService() 方法中得来的。我们先查看下 getService() 的内部实现:
- 了解 Binder 的同学应该一看便知道,这里用到了 Binder。
- INotificationManager 的具体实现在 NotificationManagerService 中,简便起见,后续内容将 NotificationManagerService 称为 NMS。
TN 又是什么?
TN 是 Toast 的一个私有的静态内部类,继承自 ITransientNotification.Stub
- 也是用到了 Binder 机制。
在回到 show 方法。该方法最后调用了 service.enqueueToast(pkg, tn, mDuration); 方法。我们到 NMS 看看该方法的主要实现。
|
|
该方法会将 Toast 请求封装为 ToastRecord 并将其添加进 mToastQueue 队列中。
- mToastQueue 是一个 ArrayList
- 注意:每个非系统应用最多只能有 50 个ToastRecord。如果超出了最大值,就会出错。
- 这样做主要是为了 防止 DOS(Denial Of Service)
拒绝服务攻击(英语:denial-of-service attack,缩写:DoS attack、DoS)亦称洪水攻击,是一种网络攻击手法,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。
将 ToastRecord 加入队列之后, enqueueToast 还调用了 showNextToastLocked(); 方法, 该方法的具体实现如下:
这里的 callBack 是什么?
- 它是在 ToastRecord 中的,瞅瞅 ToastRecord 的构造方法。12345678ToastRecord(int pid, String pkg, ITransientNotification callback, int duration,Binder token) {this.pid = pid;this.pkg = pkg;this.callback = callback;this.duration = duration;this.token = token;}
在 enqueueToast 方法中,将 Toast 包装为 ToastRecord ,创建了 ToastRecord 对象。
record = new ToastRecord(callingPid, pkg, callback, duration, token);//将 Toast 包装为 ToastRecord- callBack 是 enqueueToast 中的一个参数,我们的调用如下:
service.enqueueToast(pkg, tn, mDuration); 没错,callBack 实际上就是前面讲到的 Toast 的内部类 TN。
- 回到前面看看,TN 确实继承了
ITransientNotification.Stub。
- 回到前面看看,TN 确实继承了
showNextToastLocked() 方法调用 callBack 的 show() 方法来显示 Toast。
12345@Overridepublic void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.obtainMessage(0, windowToken).sendToTarget();}
|
|
其具体实现又是在 handleShow(token);
以上代码核心在于
将 Toast 的视图添加到 Window 中。 这样 View 就能顺利显示出来了。
你可能会问,为什么 TN 调用 show 方法要用到的 Handler ?
- 因为该方法是被是被 NMS 以跨进程方式调用的,因此它们运行在 Binder 线程池中。为了将执行环境切换到 Toast 请求所在的线程,在它们内部使用了 Handler。
那么时间到了 Toast 又是怎么样取消的呢?
在令 Toast 显示方法调用过程中 我们也调用了
scheduleTimeoutLocked(record);方法。123456private void scheduleTimeoutLocked(ToastRecord r){mHandler.removeCallbacksAndMessages(r);Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;//设置 延迟时间mHandler.sendMessageDelayed(m, delay);}SHORT_DELAY 为 2s
- LONG_DELAY 为 3.5s
scheduleTimeoutLocked 会发出消息给 hanlder,收到相应的信息后 handleMessage 方法会调用handleTimeout((ToastRecord)msg.obj);, 该方法又会调用 cancelToastLocked(index);
|
|
可以看到隐藏 Toast 的实现也是在 TN 中的。与 handleShow 对应,有一个 handleHide 方法。
该方法会将 Toast 的视图从 Window 中移除。如下所示:
Toast 的「一个 Bug」
其实这是一个系统的Bug,谷歌为了让应用的 Toast 能够显示在其他应用上面,所以使用了通知栏相关的 API,但是这个 API 随着用户屏蔽通知栏而变得不可用,系统错误地认为你没有通知栏权限,从而间接导致 Toast 有 show 请求时被系统所拦截
参考资料与学习资源推荐
由于本人水平有限,可能出于误解或者笔误难免出错,如果发现有问题或者对文中内容存在疑问请在下面评论区告诉我,谢谢!