内容简介:显示一个Toast只需要调用它的可以看到显示由我们发现,在
显示一个Toast只需要调用它的 show()
方法,看一下源码
/** 109 * Show the view for the specified duration. 110 */ 111 public void show() { 112 if (mNextView == null) { 113 throw new RuntimeException("setView must have been called"); 114 } 115 116 INotificationManager service = getService(); //获得NotificationManagerService 117 String pkg = mContext.getOpPackageName(); // 包名 118 TN tn = mTN; 119 tn.mNextView = mNextView; 120 121 try { 122 service.enqueueToast(pkg, tn, mDuration); // 调用NMS 123 } catch (RemoteException e) { 124 // Empty 125 } 126 } 复制代码
可以看到显示由 getService()
获得了 NMS,NMS主要是Android系统用来管理 通知服务的,而且Toast也属于系统通知的一种。 NMS调用了 enqueueToast(pkg,tn,mDuration)
,这三个参数分别是 :
- pkg : 应用包名
- tn : 是Toast的一个静态内部类Tn,用于被回调,内部含有两个主要方法用来显示,隐藏Toast , 并且这两个方法是等着被回调,不会主动调用
private static class TN extends ITransientNotification.Stub { @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.obtainMessage(HIDE).sendToTarget(); } public void cancel() { if (localLOGV) Log.v(TAG, "CANCEL: " + this); mHandler.obtainMessage(CANCEL).sendToTarget(); } } 复制代码
我们发现,在 show()
和 hide()
方法中,都是调用了 Handler
来处理,这是因为 NMS是运行在系统的进程中,Toast和NMS之间是一个IPC过程,NMS只能通过远程调用的方式来显示和隐藏Toast, 而 TN
这个类是一个 Binder
类,它里面的 show()
'hide()'方法会在Toast和NMS进行IPC时回调。
这时, TN
是运行在Binder的线程池中的,而我们的Toast需要在当前UI线程中显示,所以需要通过 Handler
,配合着 Looper
来完成切换线程
- mDuration : 这个就是我们创建Toast时传入的 显示时长。
接下来我们会一层一层的深入分析,贴一张图来记录进度:
INotificationManager.enqueueToast(pkg,tn,mDurtion)
接下来分析 enqueueToast(pkg,tn,mDuration)
里面是做了什么事情呢?我们继续点开看看
NotificationManagerService.java #enqueueToast()
1087 synchronized (mToastQueue) { 1089 ... 1090 try { //将Toast请求封装为ToastRecord 见 1117行 1091 ToastRecord record; 1092 int index = indexOfToastLocked(pkg, callback); 1093 //如果Toast已经在列表中,则更新它的信息 1095 if (index >= 0) { 1096 record = mToastQueue.get(index); 1097 record.update(duration); 1098 } else { 1099 // 限制Toast的个数, MAX_PACKAGE_NOTIFICATIONS = 50 1101 if (!isSystemToast) { 1102 int count = 0; 1103 final int N = mToastQueue.size(); 1104 for (int i=0; i<N; i++) { 1105 final ToastRecord r = mToastQueue.get(i); 1106 if (r.pkg.equals(pkg)) { 1107 count++; 1108 if (count >= MAX_PACKAGE_NOTIFICATIONS) { 1109 Slog.e(TAG, "Package has already posted " + count 1110 + " toasts. Not showing more. Package=" + pkg); 1111 return; 1112 } 1113 } 1114 } 1115 } 1116 1117 record = new ToastRecord(callingPid, pkg, callback, duration); 1118 mToastQueue.add(record); ... 1121 } 1122 // 如果index==0,代表就是当前的Toast 1126 if (index == 0) { 1127 showNextToastLocked(); 1128 } 1129 } finally { 1130 Binder.restoreCallingIdentity(callingId); 1131 } 复制代码
我只截取了重要的一部分代码,有点长,我们来慢慢看:
-
enqueueToast()
方法首先把Toast的请求封装到ToastRecord
中。
record = new ToastRecord(callingPid, pkg, callback, duration); 复制代码
- 将
ToastRecord
添加到一个存储到到mToastQueue
中,这是一个ArrayList的存储结构,对于非系统应用,最多能存下50个Toast,
mToastQueue.add(record); 复制代码
1106 if (r.pkg.equals(pkg)) { 1107 count++; 1108 if (count >= MAX_PACKAGE_NOTIFICATIONS) { //MAX_PACKAGE_NOTIFICATIONS = 50 1109 Slog.e(TAG, "Package has already posted " + count 1110 + " toasts. Not showing more. Package=" + pkg); 1111 return; 1112 } 1113} 复制代码
- 接下来NMS通过
showNestToastLocked()
来显示当前的Toast ,index = 0,就代表队列中只剩下一个Toast,就是当前的Toast
1119 index = mToastQueue.size() - 1; ... 1126 if (index == 0) { 1127 showNextToastLocked(); 1128 } 复制代码
enqueueToast()分析完了,记录一下
INotificationManager.showNextToastLocked()
先贴上源码
void showNextToastLocked() { ToastRecord record = mToastQueue.get(0); while (record != null) { ... try { record.callback.show(record.token); //回调 callback中的show方法 scheduleTimeoutLocked(record); //发送延时消息,取决于Toast的时长 return; } catch (RemoteException e) { ...//省略部分代码 } } } 复制代码
这里 record.callback
就是 我们前面提到的 TN
,在这里回调它的 show()
方法Toast,并通过 scheduleTimeoutLocked(record)
根据指定的Toast显示时长发送一个延时消息。
当前记录:
下面来看一下延时消息是如何实现的
scheduleTimeoutLocked(record)
private 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); } 复制代码
在上面代码中, LONG_DELAY
和 SHORT_DELAY
分别是 3.5s和 2s. 在经过这么长的延时后,发送message
来看一下对应此Message的处理:
@Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_TIMEOUT: handleTimeout((ToastRecord)msg.obj); break; ... } } 复制代码
好,接下来又要进 handleTimeout()
方法中看一下,码住:
handleTimeout(ToastRecord record)
private void handleTimeout(ToastRecord record) { synchronized (mToastQueue) { int index = indexOfToastLocked(record.pkg, record.callback); if (index >= 0) { cancelToastLocked(index); } } } 复制代码
在经过一定的延时时间后,就该去除当前这个Toast了,跟 index 判断 当前这个Toast是否还在队列中,如果还在,NMS就会通过 cancelToastLocked()
方法来隐藏Toast,并将其从队列中移除。 如果队列中还有其他的Toast,继续调用 showNextToastLocked();
将其显示.
cancelToastLocked(int index)
void cancelToastLocked(int index) { ToastRecord record = mToastQueue.get(index); try { record.callback.hide(); } catch (RemoteException e) { ... } ToastRecord lastToast = mToastQueue.remove(index); //从队列中移除 mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY); //移除window keepProcessAliveIfNeededLocked(record.pid); if (mToastQueue.size() > 0) { //如果还有其他的Toast,继续显示 // Show the next one. If the callback fails, this will remove // it from the list, so don't assume that the list hasn't changed // after this point. showNextToastLocked(); } } 复制代码
到这里,一个Toast的显示到隐藏就结束了。刚刚我们说过,Toast的显示和隐藏都是回调 TN
中的方法 :
现在来看一下 TN
中具体显示Toast的方法 : 可以看一下注释
public void handleShow(IBinder windowToken) { ... //如果此时handler又发送 隐藏 或者 取消的消息,则返回,也就是不显示了。 if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) { return; } if (mView != mNextView) { // 如果正在显示的Toast不是当前的Toast.(是之前显示的还没隐藏掉),那就隐藏它 handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); String packageName = mView.getContext().getOpPackageName(); if (context == null) { context = mView.getContext(); } //获得WindowMangaer mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); ... //省略了显示Toast的一些布局参数的设置代码 try { mWM.addView(mView, mParams); //将Toast添加到Window中 trySendAccessibilityEvent(); } catch (WindowManager.BadTokenException e) { ... } } } 复制代码
handleShow()
主要做的就是将Toast添加到Window中。相反, handleHide()
会把Toast的View从Window中移除:
public void handleHide() { if (mView != null) { if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeViewImmediate(mView); } mView = null; } } 复制代码
mView.getParent()
用来判断此View是否已经被添加到Window,如果!= null,说明 有Window包含这个Toast的View,那就移除它。
Toast的流程图:
ps: 这是我第一次写关于源码分析的博客,还有很多可能写的不清楚的地方,大家可以指出来互相学习O(∩_∩)O。我觉得通过看源码能够让你对系统的理解层次清晰,不会及停留在表面。看源码的时候注意不要被各个类之间的调用关系搞混,可以随手画出来记录一下。。。
Reference: 《Android艺术开发探索》-
(完~)
以上所述就是小编给大家介绍的《Android学习笔记14-从源码分析Toast的创建过程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Spring容器创建源码解析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- LayoutInflater创建View源码阅读
- Spring源码系列:BeanFactory的创建
- Sphinx源码学习笔记(一):索引创建
- Netty NioEventLoop 创建过程源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
SHA 加密
SHA 加密工具
Markdown 在线编辑器
Markdown 在线编辑器