#### 8.3.3 Toast的Window創建過程
Toast和Dialog不同,它的工作過程就稍顯復雜。首先Toast也是基于Window來實現的,但是由于Toast具有定時取消這一功能,所以系統采用了Handler。在Toast的內部有兩類IPC過程,第一類是Toast訪問NotificationManagerService,第二類是Notification-ManagerService回調Toast里的TN接口。關于IPC的一些知識,請讀者參考第2章的相關內容。為了便于描述,下面將NotificationManagerService簡稱為NMS。
Toast屬于系統Window,它內部的視圖由兩種方式指定,一種是系統默認的樣式,另一種是通過setView方法來指定一個自定義View,不管如何,它們都對應Toast的一個View類型的內部成員mNextView。Toast提供了show和cancel分別用于顯示和隱藏Toast,它們的內部是一個IPC過程,show方法和cancel方法的實現如下:
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}
從上面的代碼可以看到,顯示和隱藏Toast都需要通過NMS來實現,由于NMS運行在系統的進程中,所以只能通過遠程調用的方式來顯示和隱藏Toast。需要注意的是TN這個類,它是一個Binder類,在Toast和NMS進行IPC的過程中,當NMS處理Toast的顯示或隱藏請求時會跨進程回調TN中的方法,這個時候由于TN運行在Binder線程池中,所以需要通過Handler將其切換到當前線程中。這里的當前線程是指發送Toast請求所在的線程。注意,由于這里使用了Handler,所以這意味著Toast無法在沒有Looper的線程中彈出,這是因為Handler需要使用Looper才能完成切換線程的功能,關于Handler和Looper的具體介紹請參看第10章。
首先看Toast的顯示過程,它調用了NMS中的enqueueToast方法,如下所示。
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
NMS的enqueueToast方法的第一個參數表示當前應用的包名,第二個參數tn表示遠程回調,第三個參數表示Toast的時長。enqueueToast首先將Toast請求封裝為ToastRecord對象并將其添加到一個名為mToastQueue的隊列中。mToastQueue其實是一個ArrayList。對于非系統應用來說,mToastQueue中最多能同時存在50個ToastRecord,這樣做是為了防止DOS(Denial of Service)。如果不這么做,試想一下,如果我們通過大量的循環去連續彈出Toast,這將會導致其他應用沒有機會彈出Toast,那么對于其他應用的Toast請求,系統的行為就是拒絕服務,這就是拒絕服務攻擊的含義,這種手段常用于網絡攻擊中。
// Limit the number of toasts that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
if (! isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
}
正常情況下,一個應用不可能達到上限,當ToastRecord被添加到mToastQueue中后,NMS就會通過showNextToastLocked方法來顯示當前的Toast。下面的代碼很好理解,需要注意的是,Toast的顯示是由ToastRecord的callback來完成的,這個callback實際上就是Toast中的TN對象的遠程Binder,通過callback來訪問TN中的方法是需要跨進程來完成的,最終被調用的TN中的方法會運行在發起Toast請求的應用的Binder線程池中。
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record ! = null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.
callback);
try {
record.callback.show();
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show notification " + record.
callback
+ " in package " + record.pkg);
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
Toast顯示以后,NMS還會通過scheduleTimeoutLocked方法來發送一個延時消息,具體的延時取決于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是3.5s,而SHORT_DELAY是2s。延遲相應的時間后,NMS會通過cancelToastLocked方法來隱藏Toast并將其從mToastQueue中移除,這個時候如果mToastQueue中還有其他Toast,那么NMS就繼續顯示其他Toast。
Toast的隱藏也是通過ToastRecord的callback來完成的,這同樣也是一次IPC過程,它的工作方式和Toast的顯示過程是類似的,如下所示。
try {
record.callback.hide();
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to hide notification " + record.callback
+ " in package " + record.pkg);
// don't worry about this, we're about to remove it from
// the list anyway
}
通過上面的分析,大家知道Toast的顯示和影響過程實際上是通過Toast中的TN這個類來實現的,它有兩個方法show和hide,分別對應Toast的顯示和隱藏。由于這兩個方法是被NMS以跨進程的方式調用的,因此它們運行在Binder線程池中。為了將執行環境切換到Toast請求所在的線程,在它們的內部使用了Handler,如下所示。
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
上述代碼中,mShow和mHide是兩個Runnable,它們內部分別調用了handleShow和handleHide方法。由此可見,handleShow和handleHide才是真正完成顯示和隱藏Toast的地方。TN的handleShow中會將Toast的視圖添加到Window中,如下所示。
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams)
而NT的handleHide中會將Toast的視圖從Window中移除,如下所示。
if (mView.getParent() ! = null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
到這里Toast的Window的創建過程已經分析完了,相信讀者對Toast的工作過程有了一個更加全面的理解了。除了上面已經提到的Activity、Dialog和Toast以外,PopupWindow、菜單以及狀態欄等都是通過Window來實現的,這里就不一一介紹了,讀者可以找自己感興趣的內容來分析。
本章的意義在于讓讀者對Window有一個更加清晰的認識,同時能夠深刻理解Window和View的依賴關系,這有助于理解其他更深層次的概念,比如SurfaceFlinger。通過本章讀者應該知道,任何View都是附屬在一個Window上面的,那么這里問一個問題:一個應用中到底有多少個Window呢?相信讀者都已經清楚了。
- 前言
- 第1章 Activity的生命周期和啟動模式
- 1.1 Activity的生命周期全面分析
- 1.1.1 典型情況下的生命周期分析
- 1.1.2 異常情況下的生命周期分析
- 1.2 Activity的啟動模式
- 1.2.1 Activity的LaunchMode
- 1.2.2 Activity的Flags
- 1.3 IntentFilter的匹配規則
- 第2章 IPC機制
- 2.1 Android IPC簡介
- 2.2 Android中的多進程模式
- 2.2.1 開啟多進程模式
- 2.2.2 多進程模式的運行機制
- 2.3 IPC基礎概念介紹
- 2.3.1 Serializable接口
- 2.3.2 Parcelable接口
- 2.3.3 Binder
- 2.4 Android中的IPC方式
- 2.4.1 使用Bundle
- 2.4.2 使用文件共享
- 2.4.3 使用Messenger
- 2.4.4 使用AIDL
- 2.4.5 使用ContentProvider
- 2.4.6 使用Socket
- 2.5 Binder連接池
- 2.6 選用合適的IPC方式
- 第3章 View的事件體系
- 3.1 View基礎知識
- 3.1.1 什么是View
- 3.1.2 View的位置參數
- 3.1.3 MotionEvent和TouchSlop
- 3.1.4 VelocityTracker、GestureDetector和Scroller
- 3.2 View的滑動
- 3.2.1 使用scrollTo/scrollBy
- 3.2.2 使用動畫
- 3.2.3 改變布局參數
- 3.2.4 各種滑動方式的對比
- 3.3 彈性滑動
- 3.3.1 使用Scroller7
- 3.3.2 通過動畫
- 3.3.3 使用延時策略
- 3.4 View的事件分發機制
- 3.4.1 點擊事件的傳遞規則
- 3.4.2 事件分發的源碼解析
- 3.5 View的滑動沖突
- 3.5.1 常見的滑動沖突場景
- 3.5.2 滑動沖突的處理規則
- 3.5.3 滑動沖突的解決方式
- 第4章 View的工作原理
- 4.1 初識ViewRoot和DecorView
- 4.2 理解MeasureSpec
- 4.2.1 MeasureSpec
- 4.2.2 MeasureSpec和LayoutParams的對應關系
- 4.3 View的工作流程
- 4.3.1 measure過程
- 4.3.2 layout過程
- 4.3.3 draw過程
- 4.4 自定義View
- 4.4.1 自定義View的分類
- 4.4.2 自定義View須知
- 4.4.3 自定義View示例
- 4.4.4 自定義View的思想
- 第5章 理解RemoteViews
- 5.1 RemoteViews的應用
- 5.1.1 RemoteViews在通知欄上的應用
- 5.1.2 RemoteViews在桌面小部件上的應用
- 5.1.3 PendingIntent概述
- 5.2 RemoteViews的內部機制
- 5.3 RemoteViews的意義
- 第6章 Android的Drawable
- 6.1 Drawable簡介
- 6.2 Drawable的分類
- 6.2.1 BitmapDrawable2
- 6.2.2 ShapeDrawable
- 6.2.3 LayerDrawable
- 6.2.4 StateListDrawable
- 6.2.5 LevelListDrawable
- 6.2.6 TransitionDrawable
- 6.2.7 InsetDrawable
- 6.2.8 ScaleDrawable
- 6.2.9 ClipDrawable
- 6.3 自定義Drawable
- 第7章 Android動畫深入分析
- 7.1 View動畫
- 7.1.1 View動畫的種類
- 7.1.2 自定義View動畫
- 7.1.3 幀動畫
- 7.2 View動畫的特殊使用場景
- 7.2.1 LayoutAnimation
- 7.2.2 Activity的切換效果
- 7.3 屬性動畫
- 7.3.1 使用屬性動畫
- 7.3.2 理解插值器和估值器 /
- 7.3.3 屬性動畫的監聽器
- 7.3.4 對任意屬性做動畫
- 7.3.5 屬性動畫的工作原理
- 7.4 使用動畫的注意事項
- 第8章 理解Window和WindowManager
- 8.1 Window和WindowManager
- 8.2 Window的內部機制
- 8.2.1 Window的添加過程
- 8.2.2 Window的刪除過程
- 8.2.3 Window的更新過程
- 8.3 Window的創建過程
- 8.3.1 Activity的Window創建過程
- 8.3.2 Dialog的Window創建過程
- 8.3.3 Toast的Window創建過程
- 第9章 四大組件的工作過程
- 9.1 四大組件的運行狀態
- 9.2 Activity的工作過程
- 9.3 Service的工作過程
- 9.3.1 Service的啟動過程
- 9.3.2 Service的綁定過程
- 9.4 BroadcastReceiver的工作過程
- 9.4.1 廣播的注冊過程
- 9.4.2 廣播的發送和接收過程
- 9.5 ContentProvider的工作過程
- 第10章 Android的消息機制
- 10.1 Android的消息機制概述
- 10.2 Android的消息機制分析
- 10.2.1 ThreadLocal的工作原理
- 10.2.2 消息隊列的工作原理
- 10.2.3 Looper的工作原理
- 10.2.4 Handler的工作原理
- 10.3 主線程的消息循環
- 第11章 Android的線程和線程池
- 11.1 主線程和子線程
- 11.2 Android中的線程形態
- 11.2.1 AsyncTask
- 11.2.2 AsyncTask的工作原理
- 11.2.3 HandlerThread
- 11.2.4 IntentService
- 11.3 Android中的線程池
- 11.3.1 ThreadPoolExecutor
- 11.3.2 線程池的分類
- 第12章 Bitmap的加載和Cache
- 12.1 Bitmap的高效加載
- 12.2 Android中的緩存策略
- 12.2.1 LruCache
- 12.2.2 DiskLruCache
- 12.2.3 ImageLoader的實現446
- 12.3 ImageLoader的使用
- 12.3.1 照片墻效果
- 12.3.2 優化列表的卡頓現象
- 第13章 綜合技術
- 13.1 使用CrashHandler來獲取應用的crash信息
- 13.2 使用multidex來解決方法數越界
- 13.3 Android的動態加載技術
- 13.4 反編譯初步
- 13.4.1 使用dex2jar和jd-gui反編譯apk
- 13.4.2 使用apktool對apk進行二次打包
- 第14章 JNI和NDK編程
- 14.1 JNI的開發流程
- 14.2 NDK的開發流程
- 14.3 JNI的數據類型和類型簽名
- 14.4 JNI調用Java方法的流程
- 第15章 Android性能優化
- 15.1 Android的性能優化方法
- 15.1.1 布局優化
- 15.1.2 繪制優化
- 15.1.3 內存泄露優化
- 15.1.4 響應速度優化和ANR日志分析
- 15.1.5 ListView和Bitmap優化
- 15.1.6 線程優化
- 15.1.7 一些性能優化建議
- 15.2 內存泄露分析之MAT工具
- 15.3 提高程序的可維護性