Window 表示一個(gè)窗口的概念,是一個(gè)抽象類鸭轮,它的具體實(shí)現(xiàn)是 PhoneWindow臣淤。
8.1 Window 和 WindowManager
WindowManager windowManager = getWindowManager();
Button button = new Button(this);
button.setText("一個(gè)按鈕");
mLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
windowManager.addView(button,mLayoutParams);
FLAG_NOT_TOUCH_MODAL
表示 Window 不需要獲取焦點(diǎn),也不需要輸入時(shí)間窃爷,會(huì)啟用 FLAG_NOT_TOUCH_MODAL邑蒋,最終事件會(huì)直接傳遞給具有焦點(diǎn)的 Window。FLAG_NOT_TOUCH_MODAL
在此模式下按厘,系統(tǒng)會(huì)將當(dāng)前 Window 區(qū)域以外的點(diǎn)擊事件傳遞給底層的 Window医吊,區(qū)域以內(nèi)的單擊事件則自己處理。FLAG_SHOW_WHEN_LOCKED
開啟此模式可以讓 Winodw 顯示在鎖屏的界面上逮京。
Type 參數(shù)表示 Window 的類型卿堂,Window 有三種類型:
應(yīng)用 Window:對(duì)應(yīng)這一個(gè) Activity,層級(jí)范圍:1~99懒棉;
子 Window:子 Window 不能單獨(dú)存在草描,它需要附屬在特定的父 Window 中,比如 Dialog策严。層級(jí)范圍:1000~1999穗慕;
系統(tǒng) Window:需要聲明權(quán)限才能創(chuàng)建,比如 Toast 和系統(tǒng)狀態(tài)欄妻导。層級(jí)范圍:2000~2999逛绵;
層級(jí)大的覆蓋層級(jí)小的,對(duì)應(yīng)這 WindowManager.LayoutParams 的type 參數(shù)倔韭。系統(tǒng)的 WindowManager 層級(jí)是最大的暑脆,一般我們可以選用 TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERROR
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;//對(duì)應(yīng)值:2006
使用 TYPE_SYSTEM_ERROR 必須聲明權(quán)限
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//對(duì)應(yīng)值:2010
...
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
WindowManager 所提供的功能,只有三個(gè)常用方法:
public interface ViewManager
{
// 添加 View
public void addView(View view, ViewGroup.LayoutParams params);
// 更新 View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
// 移除 View
public void removeView(View view);
}
8.2 Window 的內(nèi)部機(jī)制
Window 是一個(gè)抽象的概念狐肢,每一個(gè) Window 都對(duì)應(yīng)著一個(gè) View 和 ViewRootImpl添吗,Window 和 View 通過 View RootIml來建立聯(lián)系,因此 View 并不是實(shí)際存的份名,它是以 View 的形式存在碟联,對(duì) Window 的訪問必須通過 WindowManager妓美。
8.2.1 Window 的添加過程
Window 的添加過程需要通過 WIndowManager 的 addView 來實(shí)現(xiàn)。
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManager 是一個(gè)接口鲤孵,它的真正實(shí)現(xiàn)是 WindowManagerImpl壶栋。
public interface WindowManager extends ViewManager {
...
WindowManagerImpl 類中并沒有直接實(shí)現(xiàn) Window 的三大操作,而是交給了 WindowManagerGlobal普监。
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
...
WindowManagerImpl 這種工作米時(shí)候是典型的橋接模式贵试,將所有方法都委托給 WindowManagerGlpbal 來實(shí)現(xiàn)。
- 檢查參數(shù)是否合法凯正,如果是子 Window 那么還需要調(diào)整一些布局參數(shù)
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
...
- 創(chuàng)建 ViewRootImpl 并將 View 添加到列表中
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 將一系列對(duì)象添加到列表中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
- mViews:儲(chǔ)存的是所有Window 所對(duì)應(yīng)的 View毙玻;
- mRoots:存儲(chǔ)的是所有 Window 所對(duì)應(yīng)的 ViewRootImpl;
- ** mParams:**存儲(chǔ)的是所有 Window 的布局參數(shù)廊散;
- mDyingViews:存儲(chǔ)了那些正在被刪除的 View 對(duì)象桑滩,調(diào)用 removeView 但是刪除操作還未完成的 Window 對(duì)象;
-
通過 ViewTootImpl 來更新界面并完成 Window 的添加過程
這個(gè)步驟由 ViewRootImpl 的 setView 方法來完成允睹,在 setView 內(nèi)部會(huì)通過 requestLayout 來完成異步刷新請(qǐng)求运准。
public final class WindowManagerImpl implements WindowManager {
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
requestLayout();
....
scheduleTraversals 實(shí)際是 View 繪制的入口;
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
接著會(huì)通過 WindowSession 來最終完成 Window 的添加過程缭受,WindowSession 是一個(gè) Binder 對(duì)象胁澳,真正的實(shí)現(xiàn)類是 Session,也就是 Window 的添加過程是一次 IPC 調(diào)用米者。在Session 內(nèi)部會(huì)通過 WIndowManagerService 來實(shí)現(xiàn) Window 的添加韭畸。 如此一來,Window 的添加請(qǐng)求就交給 WindowManagerService 去處理了塘雳。WindowManagerService 內(nèi)部就不用去深入分析了陆盘。(此處看不到源碼普筹。)
8.2.2 Window 的刪除過程
Window 的刪除過程和添加過程一樣败明,都是先通過 WIndowManagerImpl 后,在進(jìn)一步通過 WindowManagerGlobal 來實(shí)現(xiàn)的太防。
public final class WindowManagerGlobal {
...
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
removeView 的邏輯很清晰妻顶,首先通過 findViewLocked 來查找待刪除的 View 的索引,過程就是調(diào)用數(shù)組遍歷蜒车,再調(diào)用 removeViewLocked 來進(jìn)一步的刪除讳嘱。
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
//異步刪除時(shí),添加到 mDyingViews 中
if (deferred) {
mDyingViews.add(view);
}
}
}
removeViewLocked 是通過 ViewRootImpl 來完成刪除操作的酿愧,在 WindowManager 中提供兩種刪除接口:
removeView:異步刪除(常用)
在 die 方法中異步刪除沥潭,那么就發(fā)送一個(gè) MSG_DIE 的消息,ViewRootImpl 中的 Handler 會(huì)處理此消息并調(diào)用 doDie 方法嬉挡。removeViewImmediate:同步刪除(基本用不到)
在 die 方法中同步刪除钝鸽,會(huì)直接就直接調(diào)用 doDie 方法汇恤。
這里主要說明異步刪除,具體的刪除操作由 ViewRootImpl 的 die 方法來完成拔恰,這里異步刪除會(huì)返回到 removeViewLocked 方法中繼續(xù)處理因谎,而同步刪除則不會(huì)返回。
boolean die(boolean immediate) {
// 同步刪除
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
// 異步刪除
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
在 doDie 方法內(nèi)部會(huì)調(diào)用 dispatchDetachedFromWindow 方法颜懊,真正的刪除 View 的邏輯在該方法內(nèi)部實(shí)現(xiàn)财岔,該方法主要做四件事:
- 垃圾回收相關(guān)的工作,比如清除數(shù)據(jù)和消息河爹、移除回調(diào)匠璧。
- 通過 Session 的 remove 方法刪除 Window:mWindowSession.remove(mWindw),這同樣是一個(gè) IPC 過程昌抠,最終會(huì)調(diào)用 WindowManagerService 的 removeWindow 方法患朱。
- 調(diào)用 VIew 的dispatahDetachedFromWindow 方法,在內(nèi)部會(huì)調(diào)用 View 的onDetachedFromWIndow()以及 onDetachedFromWindowInternal()炊苫。(onDetachedFromWIndow 當(dāng) View 被移除時(shí)會(huì)被調(diào)用)
- 調(diào)用 WindowManagerGlobal 的 doRemoveView 方法刷新數(shù)據(jù)裁厅,包括 mRoots、mParams侨艾、mDyingViews执虹,需要將當(dāng)前 Window 所關(guān)聯(lián)的這三類對(duì)象從列表中刪除。
8.2.3 Window 的更新過程
public final class WindowManagerGlobal {
...
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
updateViewLayout 的步驟:
- 更新 View 的 LayoutParams 并替換掉老的 LayoutParams唠梨。
- 再更新 ViewRootImpl 中的 LayoutParanms袋励,這一步通過 ViewRootImpl 的 setLayoutParams 方法來實(shí)現(xiàn)。
ViewRootImpl 的 setLayoutParams 方法會(huì)通過 scheduleTraversals 方法來對(duì) View 重新布局当叭,包括測(cè)量茬故、布局、重繪這三個(gè)過程蚁鳖。ViewRootImpl 還會(huì)通過 WIndowSession 來更新 Window 的視圖磺芭,這過程由 WindowManagerService 的 relayoutWindow()來具體實(shí)現(xiàn),是個(gè) IPC 過程醉箕。
8.3 Window 的創(chuàng)建過程
有視圖的地方就有 Window
8.3.1 Activity 的 Window 創(chuàng)建過程
要分析 Activity 中的 Window 創(chuàng)建過程就必須立交 Activity 的啟動(dòng)過程钾腺,這里先大概了解即可。Activity 的啟動(dòng)過程很復(fù)雜讥裤,最終會(huì)由 ActivityThread 中的 performLaunchActivity來完成整個(gè)啟動(dòng)過程放棒,在這個(gè)方法內(nèi)部會(huì)通過類加載器創(chuàng)建 Activity 的實(shí)例對(duì)象,并調(diào)用其 attach 方法為其關(guān)聯(lián)運(yùn)行過程中所依賴的一系列上下文環(huán)境變量己英。
public final class ActivityThread {
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
if (activity != null) {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
在 Activity 的 attch 方法里间螟,系統(tǒng)會(huì)創(chuàng)建 Activity 所屬的 Window 對(duì)象并為其設(shè)置回調(diào)接口,Window 對(duì)象的創(chuàng)建是通過 PolicyManager 的makeNewWindow 方法實(shí)現(xiàn)的。由于 Activity 實(shí)現(xiàn)了 Window 的 Callback 接口厢破,因此當(dāng) WIndow 接收到外界的狀態(tài)改變時(shí)就會(huì)回調(diào) Activity 的方法邮府。Callback 接口中的方法有很多,比如 onAttachedToWindow溉奕、onDetachedFromWindow褂傀、dispatchTouchEvent等待。
Activity 的 Window 是通過 PolicyManager 的一個(gè)工廠方法來創(chuàng)建的加勤,但是從 PolicayManager 的類名可以看出仙辟,他不是一個(gè)普通的類,它是一個(gè)策略類鳄梅。
public interface IPolicy {
public Window makeNewWindow(Context context);
public LayoutInflater makeNewLayoutInflater(Context context);
public WindowManagerPolicy makeNewWindowManager();
public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
在實(shí)際的調(diào)用中叠国,PolicyManager是如何關(guān)聯(lián)到Policy類中的mackNewWindow方法來實(shí)現(xiàn)如下,由此可以發(fā)現(xiàn)戴尸,window的具體實(shí)現(xiàn)的確就是phoneWindow
public static Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
關(guān)于PolicyManager是如何關(guān)聯(lián)到IPolicy中粟焊,這個(gè)無法從源碼中調(diào)用關(guān)系得到,這里的猜測(cè)可能是編譯環(huán)節(jié)動(dòng)態(tài)控制的孙蒙,到這里window已經(jīng)創(chuàng)建完成了项棠,下面分析activity的視圖是怎么依附在window上的由于activity的視圖是由setContentView開始的,所有我們先看下這個(gè)方法:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow 的 setContentView 方法大致遵循如下幾個(gè)步驟:
-
如果沒有 DecorView挎峦,那么就創(chuàng)建它
DecorView 是一個(gè) FrameLayout香追,是Activity 的頂級(jí) View,包含標(biāo)題欄和內(nèi)容欄坦胶。DecorView 的創(chuàng)建過程由 installDecor 方法來完成透典,在方法內(nèi)部會(huì)通過 generateDecor 方法來直接創(chuàng)建 DecorView,這時(shí)候 DecorView 還只是一個(gè)空白的 FrameLayout:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
protected DecorView generateDecor(int featureId) {
....
return new DecorView(context, featureId, this, getAttributes());
}
為了初始化 DecorView 的結(jié)構(gòu)顿苇,PhoneWIndow還需要通過 generateLayout 方法來加載具體的布局文件到 DecorView 中峭咒,具體的布局文件和系統(tǒng)版本以及主題有關(guān)。
protected ViewGroup generateLayout(DecorView decor) {
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
其其中 ID_ANDROID_CONTENT 的定義如下纪岁,這個(gè)id對(duì)應(yīng)的就是 ViewGroup 的 mContentParent凑队。
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
-
將 View 添加到 DecorView 的 mContentParent 中
這一步直接將 Activity 的視圖添加到 DecorView 的 mContentParent 中即可:mLayoutInflater.inflate(layoutResID, mContentParent)。 -
回調(diào) Activity 的 onCreateChanged 方法來通知 Activity 視圖已經(jīng)發(fā)生改變
由于 Activity 實(shí)現(xiàn)了 Window 的 Callback 接口蜂科,這里表示 Activity 的布局文件以及被添加到 DecorVIew 的 mContentParent 中了顽决,于是需要通知 Acitivity 可以做相應(yīng)的處理短条。 Activity 的 onContentChanged 方法是個(gè)空實(shí)現(xiàn)导匣,我們可以在子 Activity 中處理這個(gè)回調(diào)。
@Override
public void setContentView(int layoutResID) {
....
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
...
}
經(jīng)過上面的三個(gè)步驟茸时,到這里為止 DecorView 以及被創(chuàng)建并初始化完畢贡定,Activity 的布局文件也以及成功添加到了 DecorView 的 mContentParent 中,但是這個(gè)時(shí)候DecorView還沒有被windowmanager添加到window中可都,這里需要正確的理解window的概念缓待,window更多的是表示一種抽象的功能集合蚓耽,雖然說早在activity的attch中window就已經(jīng)被創(chuàng)建了,但是這個(gè)時(shí)候由于DecorView還沒有被windowmanager識(shí)別旋炒,所有還不能提供具體的功能步悠,因?yàn)樗€無法接收外界的輸入,在 ActivityThread 的 handleResumeActivity 方法瘫镇,首先會(huì)調(diào)用 Acitivity 的onResume 方法沒接調(diào)用 makeVisible 方法 鼎兽。 在activityThread的makeVisible中,DecorView 才正在地完成了添加和顯示兩個(gè)過程铣除,到這里 Activity 的視圖才能被用戶看到:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
8.3.2 Dialog 的 Window 創(chuàng)建過程
Dialog 的 Window 創(chuàng)建過程和 Activity 類似谚咬,有幾個(gè)步驟:
- 創(chuàng)建 Window
public class Dialog implements....{
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//創(chuàng)建 PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
- 初始化 DecorView 并將 Dialog 的視圖添加到 DecorView 中
public void setContentView(@NonNull View view) {
mWindow.setContentView(view);
}
-
將 DecorView 添加到 Window 中并顯示
在 Dialog 的 show 方法中,會(huì)通過 WindowManager 將 DecorView 添加到 Window 中尚粘。
public void show() {
...
mWindowManager.addView(mDecor, l);
mShowing = true;
當(dāng) Dialog 被關(guān)閉時(shí)择卦,它會(huì)通過 WIndowManager 來移除 DecorView。
void dismissDialog() {
...
mWindowManager.removeViewImmediate(mDecor);
普通 Dialog 必須采用 Activity 的 Context郎嫁,如果采用 Application 的Context秉继,那么會(huì)報(bào)錯(cuò)。
Dialog dialog = new Dialog(this.getApplicationContext());
TextView textView = new TextView(this);
textView.setText("this is a toast");
dialog.setContentView(textView);
dialog.show()
錯(cuò)誤信息提示:是沒有應(yīng)用 token 所導(dǎo)致泽铛,而應(yīng)用 token 一般只有 Activity 擁有秕噪。另外系統(tǒng) Window 它可以不需要 token,只需要設(shè)定一下 Window 的層級(jí)范圍為 2000~2999即可:
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//對(duì)應(yīng)值:2010
...
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
8.3.3 Toast 的 Window 創(chuàng)建過程
Toast 也是基于 Window 來實(shí)現(xiàn)的厚宰,但是由于 Toast 具有定時(shí)取消這一功能腌巾,所以系統(tǒng)采用了 Handler。在 Toast 的內(nèi)部有兩類 IPC 過程铲觉。
- 第一類:Toast 訪問 NotificaitonManagerService(簡(jiǎn)稱:NMS)
- 第二類:NotificationManagerService 回調(diào) Toast 里的 TN 接口
Toast 屬于系統(tǒng) Window澈蝙,它內(nèi)部的視圖由兩種方式指定:
- 一種是系統(tǒng)默認(rèn)
- 一種是通過 setView 方法來指定一個(gè)自定義 View
他們都對(duì)應(yīng) Toast 的一個(gè)View 類型的內(nèi)部成員 mNewView。Toast 提供了 show 和cancel 撵幽,他們內(nèi)部是一個(gè) IPC 過程灯荧。
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getPackageName();
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 的 show 和 cancel 都要通過 NMS 來實(shí)現(xiàn),NMS 運(yùn)行在系統(tǒng)的進(jìn)程中盐杂,所以只能通過遠(yuǎn)程調(diào)用的方式來顯示和隱藏 Toast逗载。TN 這個(gè)類是一個(gè)Binder 類,在 Toast 和 NMS 進(jìn)行 IPC 的過程中链烈,當(dāng) NMS 處理 Toast 的請(qǐng)求時(shí)會(huì)跨進(jìn)程回調(diào) TN 中的方法厉斟,由于 TN 運(yùn)行在 Binder 線程池中,所以需要通過 Handler 將其切換當(dāng)發(fā)送 Toast 請(qǐng)求所在的線程强衡。這里使用 Handler 擦秽,意味著 Toast 無法在沒有 Looper 的線程中彈出,因?yàn)?Handler 需要 Looper 才能完成切換線程的功能。
Toast 的 show 過程感挥。它調(diào)用了 enqueneToast 方法:
INotificationManager service = getService();
String pkg = mContext.getPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
// 參數(shù) :包名缩搅、遠(yuǎn)程回調(diào)、Toast 的時(shí)常
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
enqueneToast 首先將 Toast 請(qǐng)求封裝為 ToastRecord 對(duì)象并將其添加到一個(gè)名為 mToastQuene 的隊(duì)列中触幼。mToastRecord 其實(shí)是一個(gè) ArrayList硼瓣。對(duì)于非系統(tǒng)應(yīng)用來說,mToastQuene 中最多能同時(shí)存在 50 個(gè) ToastRecord 置谦,這樣做是防止 Dos(Denial of Service)巨双,防止其他應(yīng)用沒有機(jī)會(huì)彈出 Toast。
此處無源碼(要翻墻),摘一條重要的
final ToastRecord r = mToastQueue.get(i);
正常情況下霉祸,一個(gè)應(yīng)用不可能達(dá)到上限筑累,當(dāng)ToastRecord被添加到mToastQueue中后,NMS就會(huì)通過showNextToastLocked方法來顯示Toast丝蹭,下面的代碼很好理解慢宗,需要注意的是,Toast的顯示是由ToastRecord的callback來完成的奔穿,這個(gè)callback實(shí)際上就是TN對(duì)象的遠(yuǎn)程Binder镜沽,通過callback來訪問TN中的方法是需要跨進(jìn)程來完成的,最終被調(diào)用的對(duì)象是TN中方法發(fā)起在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(record.token);
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);
}
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
Toast 顯示以后缅茉,NMS 還會(huì)通過 scheduleTimeoutLocked 方法來發(fā)送一個(gè)延時(shí)消息:
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會(huì)通過 cancelToastLocked來隱藏 toast 并且清除隊(duì)列
通過上面的分析蔬墩,大家知道toast的顯示隱藏實(shí)際上是toast的TN這個(gè)類來實(shí)現(xiàn)的,分別對(duì)應(yīng)的show/hide耗拓,由于這兩個(gè)方法都是NMS以跨進(jìn)程的方式調(diào)用的拇颅,因此他運(yùn)行在Binder池中,為了切換乔询,在他們內(nèi)部使用了handler:
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = ()—>{ handleShow(); } };
final Runnable mHide = ()—> { handleHide();
mNextView = null;
}
};
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
上述代碼中樟插,mShow 和 mHide 是兩個(gè) Runnable,他們內(nèi)部分別調(diào)用 handleShow 和 handleHide 方法竿刁。TN 的 handleShow 中會(huì)將 Toast 的視圖添加到 Window 中:
public void handleShow() {
...
if (mView != mNextView) {
handleHide();
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
...
mWM.addView(mView, mParams);
...
}
}
而 TN 的handleHide 中會(huì)將 Toast 的視圖從 Window 中移除:
public void handleHide() {
if (mView != null) {
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
mView = null;
}
}