Window是一個抽象類,它的具體實現是PhoneWindow。創建一個Window是很簡單的事,只需要通過WindowManager即可完成。WindowManager是外界訪問Window的入口,Window的具體實現位于WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC過程。
一、Window和WindowManager
通過WindowManager添加Window:
Flags參數表示Window的屬性:
FLAG_NOT_FOCUSABLE
表示Window不需要獲取焦點,也不需要接收各種輸入事件,此標記會同時啟用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的Window。
FLAG_NOT_TOUCH_MODAL
在此模式下,系統會將當前Window區域以外的單擊事件傳遞給底層的Window,當前Window區域以內的單擊事件則自己處理。這個標記很重要,一般來說都需要開啟此標記,否則其他Window將無法收到單擊事件。
FLAG_SHOW_WHEN_LOCKED
開啟此模式可以讓Window顯示在鎖屏的界面上。
type參數表示Window的類型,Window有三種類型,分別是應用Window、子Window和系統Window。應用類Window對應著一個Activity。子Window不能單獨存在,需要附屬在特定的父Window之中,比如常見的Dialog。系統Window是需要聲明權限在能創建的Window,比如Toast和系統狀態欄。
Window是分層的,每個Window都有對應的z-ordered,層級大的會覆蓋在層級小的Window的上面。
應用Window的層級范圍時1~99,子Window的層級范圍時1000~1999,系統Window的層級范圍是2000~2999,這些層級范圍對應著WindowManager.LayoutParams的type參數
WindowManager所提供的功能很簡單,常用的只有三個方法,即添加View、更新View和刪除View,這三個方法定義在ViewManager中,而WindowManager繼承了ViewManager。
二、Window的內部機制
每一個Window都對應著一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯系,因此Window并不是實際存在的,是以View的形式存在。
2.1 Window的添加過程
Window的添加過程需要通過WindowManager的addView來實現,WindowManager是一個接口,它的真正實現是WindowManagerImplement類。其三大操作:
可以發現,WindowManagerImpl并沒有直接實現Window的三大操作,而是全部交給了WindowManagerGlobal來處理,WindowManagerGlobal以工廠的形式向外提供自己的實例。
WindowManagerGlobal的addView方法主要分為如下幾步:
1、檢查參數是否合法,如果是子Window那么還需要調整一些布局參數
2、創建ViewRootImpl并將View添加到列表中
在上面的聲明中,mViews存儲的是所有Window所對應的View,mRoots所存儲的是所有Window所對應的ViewRootImpl,mParams存儲的是所有Window所對應的布局參數,而mDyingViews則存儲了那些正在被刪除的View對象,或者說是那些已經被調用removeView方法但是刪除操作還未完成的Window對象。在addView中通過如下方式將Window的一系列對象添加到列表中:
3、通過ViewRootImpl來更新界面并完成Window的添加過程
這個步驟由ViewRootImpl的setView方法來完成:
接著會通過WindowSession最終來完成Window的添加過程。
在Session內部會通過WindowManagerService來實現Window的添加:
2.2 Window的刪除過程
Window的刪除過程和添加過程一樣,都是先通過WindowManagerImpl后,再進一步通過WindowManagerGlobal來實現的。下面是WindowManagerGlobal的removeView的實現:
removeView通過findViewLocked來查找待刪除的View的索引,這個查找過程就是建立的數組遍歷,然后再調用removeViewLocked來做進一步的刪除:
removeViewLocked是通過ViewRootImpl來完成刪除操作的。在WindowManager中提供了兩種刪除接口removeView和removeViewImmediate,它們分別表示異步刪除和同步刪除。
removeView是由ViewRootImpl的die方法來完成。而die方法只是發送了一個請求刪除的消息后就立刻返回了,這個時候View并沒有完成刪除操作,所以最好會將其添加到mDyingViews中,mDyingViews表示待刪除的View列表。
在de方法內部只是做了簡單的判斷,如果是異步刪除,那么就發送一個MSG_DIE的消息,ViewRootImpl中的Handler會處理此消息并調用doDie方法,如果是同步刪除(立即刪除),那么久不乏消息直接調用doDie方法,這就是兩種刪除方式的區別。在doDie內部會調用dispatchDetachedFromWindow方法,這個方法主要做四件事:
1、垃圾回收相關的工作,比如清除數據和消息、移除回調
2、通過Session的remove方法刪除Window:mWindowSession.remove(mWindow),這同樣是一個IPC過程,最終會調用WindowManagerService的removeWindow方法
3、戴傲勇View的dispatchDetachedFromWindow方法,在內部會調用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。當View從window中移除時,會調用onDetachedFromWindow,可在這個方法內部做一些資源回收的工作。
4、調用WindowManagerGlobal的doRemoveView方法刷新數據,,包括mRoots、mParams以及mDyingViews,需要將當前Window所關聯的這三類對象從列表中刪除。
2.3 Window的更新
查看WindowManagerGlobal的updateViewLayout方法:
首先需要更新View的LayoutParams并替換掉老的LayoutParams,接著在更新ViewRootImpl中的LayoutParams。接著再更新ViewRootImpl中的LayoutParams,這一步是通過ViewRootImpl的setLayoutParams方法來實現的。在ViewRootImpl中會通過scheduleTraversals方法來對View重新布局,包括測量、布局、重繪這三個過程。在通過WindowSession來更新Window的視圖,這個過程是有WindowManagerService的relayoutWindow來具體實現。
三、Window的創建過程
3.1 Activity的Window創建過程
在Activity的啟動過程中,會調用attach方法,這個方法會創建Activity所屬的Window對象并為其設置回調接口,Window對象的創建時通過PolicyManager的makeNewWindow方法實現的,由于Activity實現了Window的Callback接口,所以當Window接收到外界的狀態改變時就會回調Activity的方法。代碼如下:
從上面可看出,Activity的Window是通過PolicyManager的一個工廠方法來創建的,但是從PolicyManager的類名可以看出,他不是一個普通類,它是一個策略類。PolicyManager中實現的幾個工廠方法全部在策略接口中IPolicy中聲明,IPolicy的定義如下:
而方法makeNewWindow實現如下:
分析Activity的視圖是怎么附屬在Window上:
由于Activity的視圖由setContentView方法提供,我們只需要看setContentView方法的實現即可:
可看出Activity將具體實現交給了window處理,而Window的具體實現是PhoneWindow,所以只需看PhoneWindow的相關邏輯即可,其setContentView方法遵循如下步驟:
1、如果沒有DecorView,那么就創建它
DecorView是Activity的頂級View,一般來說它的內部包含標題欄和內部欄,但是這個會隨著主題的變換而發生改變,不管怎么樣,內容欄是一定要存在的,并且內容來具體固定的id,那就是“content”,它的完整id是android.R.id.content。DecorView的創建過程由installDecor方法來完成,在內部會通過generateLayout方法來直接創建DecorView。
為了初始化DecorView的結構,PhoneWindow還需通過generateLayout方法來加載具體的布局文件到DecorView中,具體的布局文件和系統版本以及主題有關:
其中ID_ANDROID_CONTENT的定義如下,這個id所對應的ViewGroup就是mContentParent:
2、將View添加到DecorView的mContentParent中
直接將Activity的視圖添加到DecorView的mContentParent中即可:沒LayoutInflater.inflate(layoutResID,MContentParent)。
3、回調Activity的onContentChanged方法通知Activity視圖已經發生改變
可以直接在Activity的onContentChanged方法是個空實現,可在子Activity中處理這個回調:
經過上面三個步驟,activity的布局文件已經成功添加到了DecorView的mContentParent中,但是這個時候DecorView還沒有被WindowManager正式添加到Window中。只有在ActivityThread的handleResumeActivity方法中,首先會調用Aactivity的onResume方法,接著會調用Activity的makeVisible(),正是在makeVisible方法中,DecorView真正完成了添加和顯示這兩個過程:
3.2 Dialog的Window創建過程
Dialog的Window創建過程和Activity類似,有如下步驟:
1、創建Window
Dialog中Window的創建同樣是通過PolicyManager的makeNewWindow方法來完成的:
2、初始化DecorView并將Dialog的視圖添加到DecorView中
3、將DecorView添加到Window中并顯示
在Dialog的show方法中,會通過WindowManager將DecorView添加到Window中,如下所示:
當Dialog被關閉時,它會通過WindowManager來移除DecorView:mWindowManager.removeViewImmediate(mDecor).
普通的Dialog有一個特殊之處,那就是必須采用Activity的Context,如果采用Application的Context,那么就會報錯。
3.3 Toast的Window創建過程
Toast也是基于Window來實現的,但是由于Toast具有定時取消這一功能,所以系統采用了Handler。
Toast內部有兩類IPC過程,第一類是Toast訪問NotificationManagerService(NMS),第二類是NotificationManagerService回調Toast里的TN接口。
Toast屬于系統Window,它內部的視圖由兩種方式指定,一種是系統默認的樣式,另一種是通過setView方法來指定一個自定義View,不管如何,他們都對應Toast的一個View類型的內部成員mNextView。其show與cancel方法實現如下:
顯示和隱藏Toast都需要通過NMS來實現,由于NMS運行在系統的進程中,所以只能通過遠程調用的方式來顯示和隱藏Toast。而TN這個類,它是一個Binder類,在Toast和NMS進行IPC的過程中,當NMS處理Toast的顯示或隱藏請求時會跨進程回調TN中的方法,這個時候由于TN運行在Binder線程中,所以需要通過Handler將其切換到當前線程中。
Toast的顯示過程:
NMS的enqueueToast方法的第一個參數表示當前應用的包名,第二個參數tn表示遠程回調,第三個參數表示Toast的時長。enqueueToast首先將Toast請求封裝為ToastRecord對象并將其添加到一個名為mToastQueue的隊列中。
當ToastRecord被添加到mToastQueue中后,NMS就會通過showNextToastLocked方法來顯示當前的Toast。其中,Toast的顯示是由TsatRecord的callback來完成的,這個callback實際上就是Toast中的TN對象的遠程Binder,通過callback來訪問TN中的方法是需要跨進程來完成的,最終被調用的TN中的方法虎運行在發起Toast請求的應用的Binder線程池中。
Toast顯示以后,NMS還會通過scheduleTimeoutLocked方法來發送一個延時消息,具體的延時取決于Toast的時長:
在上面額代碼證,LONG_DELAY是3.5s,而SHORT_DELAY是2s。延遲相應的時間后,NMS會通過cancelToastLocked方法來隱藏Toast并將其從mToastQueue中移除,這個時候如果mToastQueue中還有其他Toast,那么NMS就繼續顯示其他Toast。
Toast的隱藏也是通過ToastRecord的callback來完成:
Toast的顯示和隱藏過程實際上是通過Toast中的TN這個類來實現的,它有兩個方法show和hide,分別對應Toast的顯示和隱藏。由于這兩個方法是被NMS以跨進程的方式調用,因此它們運行在線程池中:
mShow和mHide是兩個Runnable,分別調用了handleShow和handleHide方法,TN的handleShow中會將Toast的視圖添加到Window中:
而NT的handleHide中會將Toast的視圖從Window中移除: