顯示一個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藝術開發探索》-
(完~)