Window是一個(gè)抽象類(lèi),它的具體實(shí)現(xiàn)是PhoneWindow。創(chuàng)建一個(gè)Window是很簡(jiǎn)單的事,只需要通過(guò)WindowManager即可完成。WindowManager是外界訪(fǎng)問(wèn)Window的入口,Window的具體實(shí)現(xiàn)位于WindowManagerService中,WindowManager和WindowManagerService的交互是一個(gè)IPC過(guò)程。
一、Window和WindowManager
通過(guò)WindowManager添加Window:


Flags參數(shù)表示W(wǎng)indow的屬性:
FLAG_NOT_FOCUSABLE
表示W(wǎng)indow不需要獲取焦點(diǎn),也不需要接收各種輸入事件,此標(biāo)記會(huì)同時(shí)啟用FLAG_NOT_TOUCH_MODAL,最終事件會(huì)直接傳遞給下層的具有焦點(diǎn)的Window。
FLAG_NOT_TOUCH_MODAL
在此模式下,系統(tǒng)會(huì)將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window,當(dāng)前Window區(qū)域以?xún)?nèi)的單擊事件則自己處理。這個(gè)標(biāo)記很重要,一般來(lái)說(shuō)都需要開(kāi)啟此標(biāo)記,否則其他Window將無(wú)法收到單擊事件。
FLAG_SHOW_WHEN_LOCKED
開(kāi)啟此模式可以讓W(xué)indow顯示在鎖屏的界面上。
type參數(shù)表示W(wǎng)indow的類(lèi)型,Window有三種類(lèi)型,分別是應(yīng)用Window、子Window和系統(tǒng)Window。應(yīng)用類(lèi)Window對(duì)應(yīng)著一個(gè)Activity。子Window不能單獨(dú)存在,需要附屬在特定的父Window之中,比如常見(jiàn)的Dialog。系統(tǒng)Window是需要聲明權(quán)限在能創(chuàng)建的Window,比如Toast和系統(tǒng)狀態(tài)欄。
Window是分層的,每個(gè)Window都有對(duì)應(yīng)的z-ordered,層級(jí)大的會(huì)覆蓋在層級(jí)小的Window的上面。
應(yīng)用Window的層級(jí)范圍時(shí)1~99,子Window的層級(jí)范圍時(shí)1000~1999,系統(tǒng)Window的層級(jí)范圍是2000~2999,這些層級(jí)范圍對(duì)應(yīng)著WindowManager.LayoutParams的type參數(shù)
WindowManager所提供的功能很簡(jiǎn)單,常用的只有三個(gè)方法,即添加View、更新View和刪除View,這三個(gè)方法定義在ViewManager中,而WindowManager繼承了ViewManager。

二、Window的內(nèi)部機(jī)制
每一個(gè)Window都對(duì)應(yīng)著一個(gè)View和一個(gè)ViewRootImpl,Window和View通過(guò)ViewRootImpl來(lái)建立聯(lián)系,因此Window并不是實(shí)際存在的,是以View的形式存在。
2.1 Window的添加過(guò)程
Window的添加過(guò)程需要通過(guò)WindowManager的addView來(lái)實(shí)現(xiàn),WindowManager是一個(gè)接口,它的真正實(shí)現(xiàn)是WindowManagerImplement類(lèi)。其三大操作:

可以發(fā)現(xiàn),WindowManagerImpl并沒(méi)有直接實(shí)現(xiàn)Window的三大操作,而是全部交給了WindowManagerGlobal來(lái)處理,WindowManagerGlobal以工廠(chǎng)的形式向外提供自己的實(shí)例。
WindowManagerGlobal的addView方法主要分為如下幾步:
1、檢查參數(shù)是否合法,如果是子Window那么還需要調(diào)整一些布局參數(shù)

2、創(chuàng)建ViewRootImpl并將View添加到列表中

在上面的聲明中,mViews存儲(chǔ)的是所有Window所對(duì)應(yīng)的View,mRoots所存儲(chǔ)的是所有Window所對(duì)應(yīng)的ViewRootImpl,mParams存儲(chǔ)的是所有Window所對(duì)應(yīng)的布局參數(shù),而mDyingViews則存儲(chǔ)了那些正在被刪除的View對(duì)象,或者說(shuō)是那些已經(jīng)被調(diào)用removeView方法但是刪除操作還未完成的Window對(duì)象。在addView中通過(guò)如下方式將Window的一系列對(duì)象添加到列表中:

3、通過(guò)ViewRootImpl來(lái)更新界面并完成Window的添加過(guò)程
這個(gè)步驟由ViewRootImpl的setView方法來(lái)完成:

接著會(huì)通過(guò)WindowSession最終來(lái)完成Window的添加過(guò)程。

在Session內(nèi)部會(huì)通過(guò)WindowManagerService來(lái)實(shí)現(xiàn)Window的添加:

2.2 Window的刪除過(guò)程
Window的刪除過(guò)程和添加過(guò)程一樣,都是先通過(guò)WindowManagerImpl后,再進(jìn)一步通過(guò)WindowManagerGlobal來(lái)實(shí)現(xiàn)的。下面是WindowManagerGlobal的removeView的實(shí)現(xiàn):

removeView通過(guò)findViewLocked來(lái)查找待刪除的View的索引,這個(gè)查找過(guò)程就是建立的數(shù)組遍歷,然后再調(diào)用removeViewLocked來(lái)做進(jìn)一步的刪除:


removeViewLocked是通過(guò)ViewRootImpl來(lái)完成刪除操作的。在WindowManager中提供了兩種刪除接口removeView和removeViewImmediate,它們分別表示異步刪除和同步刪除。
removeView是由ViewRootImpl的die方法來(lái)完成。而die方法只是發(fā)送了一個(gè)請(qǐng)求刪除的消息后就立刻返回了,這個(gè)時(shí)候View并沒(méi)有完成刪除操作,所以最好會(huì)將其添加到mDyingViews中,mDyingViews表示待刪除的View列表。


在de方法內(nèi)部只是做了簡(jiǎn)單的判斷,如果是異步刪除,那么就發(fā)送一個(gè)MSG_DIE的消息,ViewRootImpl中的Handler會(huì)處理此消息并調(diào)用doDie方法,如果是同步刪除(立即刪除),那么久不乏消息直接調(diào)用doDie方法,這就是兩種刪除方式的區(qū)別。在doDie內(nèi)部會(huì)調(diào)用dispatchDetachedFromWindow方法,這個(gè)方法主要做四件事:
1、垃圾回收相關(guān)的工作,比如清除數(shù)據(jù)和消息、移除回調(diào)
2、通過(guò)Session的remove方法刪除Window:mWindowSession.remove(mWindow),這同樣是一個(gè)IPC過(guò)程,最終會(huì)調(diào)用WindowManagerService的removeWindow方法
3、戴傲勇View的dispatchDetachedFromWindow方法,在內(nèi)部會(huì)調(diào)用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。當(dāng)View從window中移除時(shí),會(huì)調(diào)用onDetachedFromWindow,可在這個(gè)方法內(nèi)部做一些資源回收的工作。
4、調(diào)用WindowManagerGlobal的doRemoveView方法刷新數(shù)據(jù),,包括mRoots、mParams以及mDyingViews,需要將當(dāng)前Window所關(guān)聯(lián)的這三類(lèi)對(duì)象從列表中刪除。
2.3 Window的更新
查看WindowManagerGlobal的updateViewLayout方法:


首先需要更新View的LayoutParams并替換掉老的LayoutParams,接著在更新ViewRootImpl中的LayoutParams。接著再更新ViewRootImpl中的LayoutParams,這一步是通過(guò)ViewRootImpl的setLayoutParams方法來(lái)實(shí)現(xiàn)的。在ViewRootImpl中會(huì)通過(guò)scheduleTraversals方法來(lái)對(duì)View重新布局,包括測(cè)量、布局、重繪這三個(gè)過(guò)程。在通過(guò)WindowSession來(lái)更新Window的視圖,這個(gè)過(guò)程是有WindowManagerService的relayoutWindow來(lái)具體實(shí)現(xiàn)。
三、Window的創(chuàng)建過(guò)程
3.1 Activity的Window創(chuàng)建過(guò)程
在Activity的啟動(dòng)過(guò)程中,會(huì)調(diào)用attach方法,這個(gè)方法會(huì)創(chuàng)建Activity所屬的Window對(duì)象并為其設(shè)置回調(diào)接口,Window對(duì)象的創(chuàng)建時(shí)通過(guò)PolicyManager的makeNewWindow方法實(shí)現(xiàn)的,由于Activity實(shí)現(xiàn)了Window的Callback接口,所以當(dāng)Window接收到外界的狀態(tài)改變時(shí)就會(huì)回調(diào)Activity的方法。代碼如下:

從上面可看出,Activity的Window是通過(guò)PolicyManager的一個(gè)工廠(chǎng)方法來(lái)創(chuàng)建的,但是從PolicyManager的類(lèi)名可以看出,他不是一個(gè)普通類(lèi),它是一個(gè)策略類(lèi)。PolicyManager中實(shí)現(xiàn)的幾個(gè)工廠(chǎng)方法全部在策略接口中IPolicy中聲明,IPolicy的定義如下:

而方法makeNewWindow實(shí)現(xiàn)如下:

分析Activity的視圖是怎么附屬在Window上:
由于Activity的視圖由setContentView方法提供,我們只需要看setContentView方法的實(shí)現(xiàn)即可:

可看出Activity將具體實(shí)現(xiàn)交給了window處理,而Window的具體實(shí)現(xiàn)是PhoneWindow,所以只需看PhoneWindow的相關(guān)邏輯即可,其setContentView方法遵循如下步驟:
1、如果沒(méi)有DecorView,那么就創(chuàng)建它
DecorView是Activity的頂級(jí)View,一般來(lái)說(shuō)它的內(nèi)部包含標(biāo)題欄和內(nèi)部欄,但是這個(gè)會(huì)隨著主題的變換而發(fā)生改變,不管怎么樣,內(nèi)容欄是一定要存在的,并且內(nèi)容來(lái)具體固定的id,那就是“content”,它的完整id是android.R.id.content。DecorView的創(chuàng)建過(guò)程由installDecor方法來(lái)完成,在內(nèi)部會(huì)通過(guò)generateLayout方法來(lái)直接創(chuàng)建DecorView。

為了初始化DecorView的結(jié)構(gòu),PhoneWindow還需通過(guò)generateLayout方法來(lái)加載具體的布局文件到DecorView中,具體的布局文件和系統(tǒng)版本以及主題有關(guān):

其中ID_ANDROID_CONTENT的定義如下,這個(gè)id所對(duì)應(yīng)的ViewGroup就是mContentParent:

2、將View添加到DecorView的mContentParent中
直接將Activity的視圖添加到DecorView的mContentParent中即可:沒(méi)LayoutInflater.inflate(layoutResID,MContentParent)。
3、回調(diào)Activity的onContentChanged方法通知Activity視圖已經(jīng)發(fā)生改變
可以直接在Activity的onContentChanged方法是個(gè)空實(shí)現(xiàn),可在子Activity中處理這個(gè)回調(diào):

經(jīng)過(guò)上面三個(gè)步驟,activity的布局文件已經(jīng)成功添加到了DecorView的mContentParent中,但是這個(gè)時(shí)候DecorView還沒(méi)有被WindowManager正式添加到Window中。只有在ActivityThread的handleResumeActivity方法中,首先會(huì)調(diào)用Aactivity的onResume方法,接著會(huì)調(diào)用Activity的makeVisible(),正是在makeVisible方法中,DecorView真正完成了添加和顯示這兩個(gè)過(guò)程:

3.2 Dialog的Window創(chuàng)建過(guò)程
Dialog的Window創(chuàng)建過(guò)程和Activity類(lèi)似,有如下步驟:
1、創(chuàng)建Window
Dialog中Window的創(chuàng)建同樣是通過(guò)PolicyManager的makeNewWindow方法來(lái)完成的:

2、初始化DecorView并將Dialog的視圖添加到DecorView中

3、將DecorView添加到Window中并顯示
在Dialog的show方法中,會(huì)通過(guò)WindowManager將DecorView添加到Window中,如下所示:

當(dāng)Dialog被關(guān)閉時(shí),它會(huì)通過(guò)WindowManager來(lái)移除DecorView:mWindowManager.removeViewImmediate(mDecor).
普通的Dialog有一個(gè)特殊之處,那就是必須采用Activity的Context,如果采用Application的Context,那么就會(huì)報(bào)錯(cuò)。


3.3 Toast的Window創(chuàng)建過(guò)程
Toast也是基于Window來(lái)實(shí)現(xiàn)的,但是由于Toast具有定時(shí)取消這一功能,所以系統(tǒng)采用了Handler。
Toast內(nèi)部有兩類(lèi)IPC過(guò)程,第一類(lèi)是Toast訪(fǎng)問(wèn)NotificationManagerService(NMS),第二類(lèi)是NotificationManagerService回調(diào)Toast里的TN接口。
Toast屬于系統(tǒng)Window,它內(nèi)部的視圖由兩種方式指定,一種是系統(tǒng)默認(rèn)的樣式,另一種是通過(guò)setView方法來(lái)指定一個(gè)自定義View,不管如何,他們都對(duì)應(yīng)Toast的一個(gè)View類(lèi)型的內(nèi)部成員mNextView。其show與cancel方法實(shí)現(xiàn)如下:

顯示和隱藏Toast都需要通過(guò)NMS來(lái)實(shí)現(xiàn),由于NMS運(yùn)行在系統(tǒng)的進(jìn)程中,所以只能通過(guò)遠(yuǎn)程調(diào)用的方式來(lái)顯示和隱藏Toast。而TN這個(gè)類(lèi),它是一個(gè)Binder類(lèi),在Toast和NMS進(jìn)行IPC的過(guò)程中,當(dāng)NMS處理Toast的顯示或隱藏請(qǐng)求時(shí)會(huì)跨進(jìn)程回調(diào)TN中的方法,這個(gè)時(shí)候由于TN運(yùn)行在Binder線(xiàn)程中,所以需要通過(guò)Handler將其切換到當(dāng)前線(xiàn)程中。
Toast的顯示過(guò)程:

NMS的enqueueToast方法的第一個(gè)參數(shù)表示當(dāng)前應(yīng)用的包名,第二個(gè)參數(shù)tn表示遠(yuǎn)程回調(diào),第三個(gè)參數(shù)表示Toast的時(shí)長(zhǎng)。enqueueToast首先將Toast請(qǐng)求封裝為T(mén)oastRecord對(duì)象并將其添加到一個(gè)名為mToastQueue的隊(duì)列中。
當(dāng)ToastRecord被添加到mToastQueue中后,NMS就會(huì)通過(guò)showNextToastLocked方法來(lái)顯示當(dāng)前的Toast。其中,Toast的顯示是由TsatRecord的callback來(lái)完成的,這個(gè)callback實(shí)際上就是Toast中的TN對(duì)象的遠(yuǎn)程Binder,通過(guò)callback來(lái)訪(fǎng)問(wèn)TN中的方法是需要跨進(jìn)程來(lái)完成的,最終被調(diào)用的TN中的方法虎運(yùn)行在發(fā)起Toast請(qǐng)求的應(yīng)用的Binder線(xiàn)程池中。

Toast顯示以后,NMS還會(huì)通過(guò)scheduleTimeoutLocked方法來(lái)發(fā)送一個(gè)延時(shí)消息,具體的延時(shí)取決于Toast的時(shí)長(zhǎng):

在上面額代碼證,LONG_DELAY是3.5s,而SHORT_DELAY是2s。延遲相應(yīng)的時(shí)間后,NMS會(huì)通過(guò)cancelToastLocked方法來(lái)隱藏Toast并將其從mToastQueue中移除,這個(gè)時(shí)候如果mToastQueue中還有其他Toast,那么NMS就繼續(xù)顯示其他Toast。
Toast的隱藏也是通過(guò)ToastRecord的callback來(lái)完成:

Toast的顯示和隱藏過(guò)程實(shí)際上是通過(guò)Toast中的TN這個(gè)類(lèi)來(lái)實(shí)現(xiàn)的,它有兩個(gè)方法show和hide,分別對(duì)應(yīng)Toast的顯示和隱藏。由于這兩個(gè)方法是被NMS以跨進(jìn)程的方式調(diào)用,因此它們運(yùn)行在線(xiàn)程池中:

mShow和mHide是兩個(gè)Runnable,分別調(diào)用了handleShow和handleHide方法,TN的handleShow中會(huì)將Toast的視圖添加到Window中:

而NT的handleHide中會(huì)將Toast的視圖從Window中移除:
