显示一个Toast只须要调用它的show()
方法,看一下源码java
/** 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)
,这三个参数分别是 :ide
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
来完成切换线程oop
接下来咱们会一层一层的深刻分析,贴一张图来记录进度:
源码分析
接下来分析enqueueToast(pkg,tn,mDuration)
里面是作了什么事情呢?咱们继续点开看看布局
NotificationManagerService.java #enqueueToast()post
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}
复制代码
showNestToastLocked()
来显示当前的Toast ,index = 0,就表明队列中只剩下一个Toast,就是当前的Toast1119 index = mToastQueue.size() - 1;
...
1126 if (index == 0) {
1127 showNextToastLocked();
1128 }
复制代码
enqueueToast()分析完了,记录一下this
先贴上源码spa
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显示时长发送一个延时消息。线程
当前记录:
下面来看一下延时消息是如何实现的
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()
方法中看一下,码住:
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();
将其显示.
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,那就移除它。
ps: 这是我第一次写关于源码分析的博客,还有不少可能写的不清楚的地方,你们能够指出来互相学习O(∩_∩)O。我以为经过看源码可以让你对系统的理解层次清晰,不会及停留在表面。看源码的时候注意不要被各个类之间的调用关系搞混,能够随手画出来记录一下。。。
Reference: 《Android艺术开发探索》-
(完~)