Window是一個抽象類页眯,它的具體實(shí)現(xiàn)是PhoneWindow瓣窄。創(chuàng)建一個Window是很簡單的事,只需要通過WindowManager即可完成索昂。WindowManager是外界訪問Window的入口,Window的具體實(shí)現(xiàn)位于WindowManagerService中扩借,WindowManager和WindowManagerService的交互是一個IPC過程椒惨。
一、Window和WindowManager
通過WindowManager添加Window:
Flags參數(shù)表示W(wǎng)indow的屬性:
FLAG_NOT_FOCUSABLE
表示W(wǎng)indow不需要獲取焦點(diǎn)潮罪,也不需要接收各種輸入事件康谆,此標(biāo)記會同時(shí)啟用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點(diǎn)的Window。
FLAG_NOT_TOUCH_MODAL
在此模式下嫉到,系統(tǒng)會將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window沃暗,當(dāng)前Window區(qū)域以內(nèi)的單擊事件則自己處理。這個標(biāo)記很重要何恶,一般來說都需要開啟此標(biāo)記孽锥,否則其他Window將無法收到單擊事件。
FLAG_SHOW_WHEN_LOCKED
開啟此模式可以讓W(xué)indow顯示在鎖屏的界面上。
type參數(shù)表示W(wǎng)indow的類型惜辑,Window有三種類型唬涧,分別是應(yīng)用Window、子Window和系統(tǒng)Window盛撑。應(yīng)用類Window對應(yīng)著一個Activity碎节。子Window不能單獨(dú)存在,需要附屬在特定的父Window之中抵卫,比如常見的Dialog狮荔。系統(tǒng)Window是需要聲明權(quán)限在能創(chuàng)建的Window,比如Toast和系統(tǒng)狀態(tài)欄介粘。
Window是分層的殖氏,每個Window都有對應(yīng)的z-ordered,層級大的會覆蓋在層級小的Window的上面碗短。
應(yīng)用Window的層級范圍時(shí)1~99受葛,子Window的層級范圍時(shí)1000~1999,系統(tǒng)Window的層級范圍是2000~2999,這些層級范圍對應(yīng)著WindowManager.LayoutParams的type參數(shù)
WindowManager所提供的功能很簡單偎谁,常用的只有三個方法总滩,即添加View、更新View和刪除View巡雨,這三個方法定義在ViewManager中闰渔,而WindowManager繼承了ViewManager。
二铐望、Window的內(nèi)部機(jī)制
每一個Window都對應(yīng)著一個View和一個ViewRootImpl冈涧,Window和View通過ViewRootImpl來建立聯(lián)系,因此Window并不是實(shí)際存在的正蛙,是以View的形式存在督弓。
2.1 Window的添加過程
Window的添加過程需要通過WindowManager的addView來實(shí)現(xiàn),WindowManager是一個接口乒验,它的真正實(shí)現(xiàn)是WindowManagerImplement類愚隧。其三大操作:
可以發(fā)現(xiàn),WindowManagerImpl并沒有直接實(shí)現(xiàn)Window的三大操作锻全,而是全部交給了WindowManagerGlobal來處理狂塘,WindowManagerGlobal以工廠的形式向外提供自己的實(shí)例。
WindowManagerGlobal的addView方法主要分為如下幾步:
1鳄厌、檢查參數(shù)是否合法荞胡,如果是子Window那么還需要調(diào)整一些布局參數(shù)
2、創(chuàng)建ViewRootImpl并將View添加到列表中
在上面的聲明中了嚎,mViews存儲的是所有Window所對應(yīng)的View泪漂,mRoots所存儲的是所有Window所對應(yīng)的ViewRootImpl,mParams存儲的是所有Window所對應(yīng)的布局參數(shù),而mDyingViews則存儲了那些正在被刪除的View對象窖梁,或者說是那些已經(jīng)被調(diào)用removeView方法但是刪除操作還未完成的Window對象赘风。在addView中通過如下方式將Window的一系列對象添加到列表中:
3、通過ViewRootImpl來更新界面并完成Window的添加過程
這個步驟由ViewRootImpl的setView方法來完成:
接著會通過WindowSession最終來完成Window的添加過程纵刘。
在Session內(nèi)部會通過WindowManagerService來實(shí)現(xiàn)Window的添加:
2.2 Window的刪除過程
Window的刪除過程和添加過程一樣,都是先通過WindowManagerImpl后假哎,再進(jìn)一步通過WindowManagerGlobal來實(shí)現(xiàn)的瞬捕。下面是WindowManagerGlobal的removeView的實(shí)現(xiàn):
removeView通過findViewLocked來查找待刪除的View的索引,這個查找過程就是建立的數(shù)組遍歷舵抹,然后再調(diào)用removeViewLocked來做進(jìn)一步的刪除:
removeViewLocked是通過ViewRootImpl來完成刪除操作的肪虎。在WindowManager中提供了兩種刪除接口removeView和removeViewImmediate,它們分別表示異步刪除和同步刪除惧蛹。
removeView是由ViewRootImpl的die方法來完成扇救。而die方法只是發(fā)送了一個請求刪除的消息后就立刻返回了,這個時(shí)候View并沒有完成刪除操作香嗓,所以最好會將其添加到mDyingViews中迅腔,mDyingViews表示待刪除的View列表。
在de方法內(nèi)部只是做了簡單的判斷靠娱,如果是異步刪除沧烈,那么就發(fā)送一個MSG_DIE的消息,ViewRootImpl中的Handler會處理此消息并調(diào)用doDie方法像云,如果是同步刪除(立即刪除)锌雀,那么久不乏消息直接調(diào)用doDie方法,這就是兩種刪除方式的區(qū)別迅诬。在doDie內(nèi)部會調(diào)用dispatchDetachedFromWindow方法腋逆,這個方法主要做四件事:
1、垃圾回收相關(guān)的工作侈贷,比如清除數(shù)據(jù)和消息闲礼、移除回調(diào)
2、通過Session的remove方法刪除Window:mWindowSession.remove(mWindow)铐维,這同樣是一個IPC過程,最終會調(diào)用WindowManagerService的removeWindow方法
3慎菲、戴傲勇View的dispatchDetachedFromWindow方法嫁蛇,在內(nèi)部會調(diào)用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。當(dāng)View從window中移除時(shí)露该,會調(diào)用onDetachedFromWindow睬棚,可在這個方法內(nèi)部做一些資源回收的工作。
4、調(diào)用WindowManagerGlobal的doRemoveView方法刷新數(shù)據(jù)抑党,包警,包括mRoots、mParams以及mDyingViews底靠,需要將當(dāng)前Window所關(guān)聯(lián)的這三類對象從列表中刪除害晦。
2.3 Window的更新
查看WindowManagerGlobal的updateViewLayout方法:
首先需要更新View的LayoutParams并替換掉老的LayoutParams,接著在更新ViewRootImpl中的LayoutParams暑中。接著再更新ViewRootImpl中的LayoutParams壹瘟,這一步是通過ViewRootImpl的setLayoutParams方法來實(shí)現(xiàn)的。在ViewRootImpl中會通過scheduleTraversals方法來對View重新布局鳄逾,包括測量稻轨、布局、重繪這三個過程雕凹。在通過WindowSession來更新Window的視圖殴俱,這個過程是有WindowManagerService的relayoutWindow來具體實(shí)現(xiàn)。
三枚抵、Window的創(chuàng)建過程
3.1 Activity的Window創(chuàng)建過程
在Activity的啟動過程中线欲,會調(diào)用attach方法,這個方法會創(chuàng)建Activity所屬的Window對象并為其設(shè)置回調(diào)接口俄精,Window對象的創(chuàng)建時(shí)通過PolicyManager的makeNewWindow方法實(shí)現(xiàn)的询筏,由于Activity實(shí)現(xiàn)了Window的Callback接口,所以當(dāng)Window接收到外界的狀態(tài)改變時(shí)就會回調(diào)Activity的方法竖慧。代碼如下:
從上面可看出嫌套,Activity的Window是通過PolicyManager的一個工廠方法來創(chuàng)建的,但是從PolicyManager的類名可以看出圾旨,他不是一個普通類踱讨,它是一個策略類。PolicyManager中實(shí)現(xiàn)的幾個工廠方法全部在策略接口中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、如果沒有DecorView床佳,那么就創(chuàng)建它
DecorView是Activity的頂級View滋早,一般來說它的內(nèi)部包含標(biāo)題欄和內(nèi)部欄,但是這個會隨著主題的變換而發(fā)生改變砌们,不管怎么樣杆麸,內(nèi)容欄是一定要存在的搁进,并且內(nèi)容來具體固定的id,那就是“content”昔头,它的完整id是android.R.id.content饼问。DecorView的創(chuàng)建過程由installDecor方法來完成,在內(nèi)部會通過generateLayout方法來直接創(chuàng)建DecorView揭斧。
為了初始化DecorView的結(jié)構(gòu)莱革,PhoneWindow還需通過generateLayout方法來加載具體的布局文件到DecorView中,具體的布局文件和系統(tǒng)版本以及主題有關(guān):
其中ID_ANDROID_CONTENT的定義如下未蝌,這個id所對應(yīng)的ViewGroup就是mContentParent:
2驮吱、將View添加到DecorView的mContentParent中
直接將Activity的視圖添加到DecorView的mContentParent中即可:沒LayoutInflater.inflate(layoutResID,MContentParent)。
3萧吠、回調(diào)Activity的onContentChanged方法通知Activity視圖已經(jīng)發(fā)生改變
可以直接在Activity的onContentChanged方法是個空實(shí)現(xiàn)左冬,可在子Activity中處理這個回調(diào):
經(jīng)過上面三個步驟,activity的布局文件已經(jīng)成功添加到了DecorView的mContentParent中纸型,但是這個時(shí)候DecorView還沒有被WindowManager正式添加到Window中拇砰。只有在ActivityThread的handleResumeActivity方法中,首先會調(diào)用Aactivity的onResume方法狰腌,接著會調(diào)用Activity的makeVisible()除破,正是在makeVisible方法中,DecorView真正完成了添加和顯示這兩個過程:
3.2 Dialog的Window創(chuàng)建過程
Dialog的Window創(chuàng)建過程和Activity類似琼腔,有如下步驟:
1瑰枫、創(chuàng)建Window
Dialog中Window的創(chuàng)建同樣是通過PolicyManager的makeNewWindow方法來完成的:
2、初始化DecorView并將Dialog的視圖添加到DecorView中
3丹莲、將DecorView添加到Window中并顯示
在Dialog的show方法中光坝,會通過WindowManager將DecorView添加到Window中,如下所示:
當(dāng)Dialog被關(guān)閉時(shí)甥材,它會通過WindowManager來移除DecorView:mWindowManager.removeViewImmediate(mDecor).
普通的Dialog有一個特殊之處盯另,那就是必須采用Activity的Context,如果采用Application的Context洲赵,那么就會報(bào)錯鸳惯。
3.3 Toast的Window創(chuàng)建過程
Toast也是基于Window來實(shí)現(xiàn)的,但是由于Toast具有定時(shí)取消這一功能叠萍,所以系統(tǒng)采用了Handler芝发。
Toast內(nèi)部有兩類IPC過程,第一類是Toast訪問NotificationManagerService(NMS)苛谷,第二類是NotificationManagerService回調(diào)Toast里的TN接口后德。
Toast屬于系統(tǒng)Window,它內(nèi)部的視圖由兩種方式指定抄腔,一種是系統(tǒng)默認(rèn)的樣式瓢湃,另一種是通過setView方法來指定一個自定義View,不管如何赫蛇,他們都對應(yīng)Toast的一個View類型的內(nèi)部成員mNextView绵患。其show與cancel方法實(shí)現(xiàn)如下:
顯示和隱藏Toast都需要通過NMS來實(shí)現(xiàn),由于NMS運(yùn)行在系統(tǒng)的進(jìn)程中悟耘,所以只能通過遠(yuǎn)程調(diào)用的方式來顯示和隱藏Toast落蝙。而TN這個類,它是一個Binder類暂幼,在Toast和NMS進(jìn)行IPC的過程中筏勒,當(dāng)NMS處理Toast的顯示或隱藏請求時(shí)會跨進(jìn)程回調(diào)TN中的方法,這個時(shí)候由于TN運(yùn)行在Binder線程中旺嬉,所以需要通過Handler將其切換到當(dāng)前線程中管行。
Toast的顯示過程:
NMS的enqueueToast方法的第一個參數(shù)表示當(dāng)前應(yīng)用的包名,第二個參數(shù)tn表示遠(yuǎn)程回調(diào)邪媳,第三個參數(shù)表示Toast的時(shí)長捐顷。enqueueToast首先將Toast請求封裝為ToastRecord對象并將其添加到一個名為mToastQueue的隊(duì)列中。
當(dāng)ToastRecord被添加到mToastQueue中后雨效,NMS就會通過showNextToastLocked方法來顯示當(dāng)前的Toast迅涮。其中,Toast的顯示是由TsatRecord的callback來完成的徽龟,這個callback實(shí)際上就是Toast中的TN對象的遠(yuǎn)程Binder叮姑,通過callback來訪問TN中的方法是需要跨進(jìn)程來完成的,最終被調(diào)用的TN中的方法虎運(yùn)行在發(fā)起Toast請求的應(yīng)用的Binder線程池中据悔。
Toast顯示以后传透,NMS還會通過scheduleTimeoutLocked方法來發(fā)送一個延時(shí)消息,具體的延時(shí)取決于Toast的時(shí)長:
在上面額代碼證屠尊,LONG_DELAY是3.5s旷祸,而SHORT_DELAY是2s。延遲相應(yīng)的時(shí)間后讼昆,NMS會通過cancelToastLocked方法來隱藏Toast并將其從mToastQueue中移除托享,這個時(shí)候如果mToastQueue中還有其他Toast,那么NMS就繼續(xù)顯示其他Toast浸赫。
Toast的隱藏也是通過ToastRecord的callback來完成:
Toast的顯示和隱藏過程實(shí)際上是通過Toast中的TN這個類來實(shí)現(xiàn)的闰围,它有兩個方法show和hide,分別對應(yīng)Toast的顯示和隱藏既峡。由于這兩個方法是被NMS以跨進(jìn)程的方式調(diào)用羡榴,因此它們運(yùn)行在線程池中:
mShow和mHide是兩個Runnable,分別調(diào)用了handleShow和handleHide方法运敢,TN的handleShow中會將Toast的視圖添加到Window中:
而NT的handleHide中會將Toast的視圖從Window中移除: