《深入理解Android 卷III》第四章 深入理解WindowManagerService
《深入理解Android 卷III》即將公布,作者是張大偉。此書填補了深入理解Android Framework卷中的一個主要空白。即Android Framework中和UI相關的部分。在一個特別講究顏值的時代,本書分析了Android 4.2中WindowManagerService、ViewRoot、Input系統、StatusBar、Wallpaper等重要“顏值繪制/處理”模塊
第4章 ?深入理解WindowManagerService(節選)
本章主要內容:
·??演示樣例最原始最簡單的窗體創建方法
·??研究WMS的窗體管理結構
·??探討WMS布局系統的工作原理
·??研究WMS動畫系統的工作原理
本章涉及的源碼文件名稱及位置:
·??SystemServer.java
frameworks/base/services/java/com/android/server/SystemServer.java
·??WindowManagerService.java
frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
·??ActivityStack.java
frameworks/base/services/java/com/android/server/am/ActivityStack.java
·??WindowState.java
frameworks/base/services/java/com/android/server/wm/WindowState.java
·??PhoneWindowManager.java
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
·??AccelerateDecelerateInterpolator.java
frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
·??Animation.java
frameworks/base/core/java/android/view/animation/Animation.java
·??AlphaAnimation.java
frameworks/base/core/java/android/view/animation/AlphaAnimation.java
·??WindowAnimator.java
frameworks/base/services/java/com/android/server/wm/WindowAnimator.java
·??WindowStateAnimator.java
frameworks/base/services/java/com/android/server/wm/WindowStateAnimator.java
4.1 初識WindowManagerService
WindowManagerService(下面簡稱WMS)是繼ActivityManagerService與PackageManagerService之后又一個復雜卻十分重要的系統服務。
在介紹WMS之前。首先要了解窗體(Window)是什么。
Android系統中的窗體是屏幕上的一塊用于繪制各種UI元素并能夠響應應用戶輸入的一個矩形區域。
從原理上來講,窗體的概念是獨自占有一個Surface實例的顯示區域。
比如Dialog、Activity的界面、壁紙、狀態欄以及Toast等都是窗體。
《卷I》第8章曾具體介紹了一個Activity通過Surface來顯示自己的過程:
·??Surface是一塊畫布,應用能夠隨心所欲地通過Canvas或者OpenGL在其上作畫。
·??然后通過SurfaceFlinger將多塊Surface的內容依照特定的順序(Z-order)進行混合并輸出到FrameBuffer,從而將Android“美麗的臉蛋”顯示給用戶。
既然每一個窗體都有一塊Surface供自己涂鴉。必定須要一個角色對全部窗體的Surface進行協調管理。于是,WMS便應運而生。WMS為全部窗體分配Surface,掌管Surface的顯示順序(Z-order)以及位置尺寸,控制窗體動畫,而且還是輸入系統的一重要的中轉站。
說明一個窗體擁有顯示和響應用戶輸入這兩層含義,本章將側重于分析窗體的顯示,而響應用戶輸入的過程則在第5章進行具體的介紹。
本章將深入分析WMS的兩個基礎子系統的工作原理:
·??布局系統(Layout System)。計算與管理窗體的位置、層次。
·??動畫系統(Animation System),依據布局系統計算的窗體位置與層次渲染窗體動畫。
為了讓讀者對WMS的功能以及工作方式有一個初步地認識,并見識一下WMS的強大,本節將從一個簡單而奇妙的樣例開始WMS的學習之旅。
4.1.1 一個從命令行啟動的動畫窗體
1.SampleWindow的實現
在這一節里將編寫一個最簡單的Java程序SampleWindow。僅使用WMS的接口創建并渲染一個動畫窗體。
此程序將拋開Activity、Wallpaper等UI架構的復雜性,直接了當地揭示WMS的client怎樣申請、渲染并注銷自己的窗體。
同一時候這也初步地反應了WMS的工作方式。
這個樣例非常easy。僅僅有三個文件:
·??SampleWindow.java 主程序源碼。
·??Android.mk 編譯腳本。
·??sw.sh 啟動器。
分別看一下這三個文件的實現:
[-->SampleWindow.java::SampleWindow]
package understanding.wms.samplewindow;
......
public class SampleWindow {
?
??? publicstatic void main(String[] args) {
??? ????try {
??????? ?????//SampleWindow.Run()是這個程序的主入口
????????????new SampleWindow().Run();
??? ????} catch (Exception e) {
???????????e.printStackTrace();
??? ????}
??? }
??? //IWindowSession 是client向WMS請求窗體操作的中間代理,而且是進程唯一的
???IWindowSession mSession = null;
??? //InputChannel 是窗體接收用戶輸入事件的管道。在第5章中將對其進行具體的探討
???InputChannel mInputChannel = new InputChannel();
?
??? // 下面的三個Rect保存了窗體的布局結果。
當中mFrame表示了窗體在屏幕上的位置與尺寸
??? // 在4.4中將具體介紹它們的作用以及計算原理
??? RectmInsets = new Rect();
??? RectmFrame = new Rect();
??? RectmVisibleInsets = new Rect();
?
???Configuration mConfig = new Configuration();
??? // 窗體的Surface,在此Surface上進行的繪制都將在此窗體上顯示出來
??? SurfacemSurface = new Surface();
??? // 用于在窗體上進行畫圖的畫刷
??? PaintmPaint = new Paint();
??? // 加入窗體所需的令牌。在4.2節將會對其進行介紹
??? IBindermToken = new Binder();
?
??? // 一個窗體對象。本例演示了怎樣將此窗體加入到WMS中,并在其上進行繪制操作
??? MyWindowmWindow = new MyWindow();
?
??? //WindowManager.LayoutParams定義了窗體的布局屬性,包括位置、尺寸以及窗體類型等
???LayoutParams mLp = new LayoutParams();
?
???Choreographer mChoreographer = null;
??? //InputHandler 用于從InputChannel接收按鍵事件做出響應
???InputHandler mInputHandler = null;
?
??? booleanmContinueAnime = true;
?
??? publicvoid Run() throws Exception{
??????? Looper.prepare();
??????? // 獲取WMS服務
???????IWindowManager wms = IWindowManager.Stub.asInterface(
????????????????????? ServiceManager.getService(Context.WINDOW_SERVICE));
?
??????? // 通過WindowManagerGlobal獲取進程唯一的IWindowSession實例。它將用于向WMS
??????? // 發送請求。
注意這個函數在較早的Android版本號(如4.1)位于ViewRootImpl類中
??????? mSession= WindowManagerGlobal.getWindowSession(Looper.myLooper());
?
??????? // 獲取屏幕分辨率
???????IDisplayManager dm = IDisplayManager.Stub.asInterface(
??????????????????????? ServiceManager.getService(Context.DISPLAY_SERVICE));
???????DisplayInfo di = dm.getDisplayInfo(Display.DEFAULT_DISPLAY);
???????Point scrnSize = new Point(di.appWidth, di.appHeight);
??????? // 初始化WindowManager.LayoutParams
?????? ?initLayoutParams(scrnSize);
?
??????? // 將新窗體加入到WMS
??????? installWindow(wms);
?
??????? // 初始化Choreographer的實例。此實例為線程唯一。這個類的使用方法與Handler
??????? // 相似。只是它總是在VSYC同步時回調。所以比Handler更適合做動畫的循環器[1]
??????? mChoreographer= Choreographer.getInstance();
?
??????? // 開始處理第一幀的動畫
??????? scheduleNextFrame();
?
??????? // 當前線程陷入消息循環,直到Looper.quit()
??????? Looper.loop();
?
??????? // 標記不要繼續繪制動畫幀
??????? mContinueAnime= false;
?
??????? // 卸載當前Window
??????? uninstallWindow(wms);
??? }
?
??? publicvoid initLayoutParams(Point screenSize) {
??????? // 標記即將安裝的窗體類型為SYSTEM_ALERT。這將使得窗體的ZOrder順序比較靠前
???????mLp.type = LayoutParams.TYPE_SYSTEM_ALERT;
??????? mLp.setTitle("SampleWindow");
??????? // 設定窗體的左上角坐標以及高度和寬度
???????mLp.gravity = Gravity.LEFT | Gravity.TOP;
???????mLp.x = screenSize.x / 4;
???????mLp.y = screenSize.y / 4;
???????mLp.width = screenSize.x / 2;
???????mLp.height = screenSize.y / 2;
??????? // 和輸入事件相關的Flag,希望當輸入事件發生在此窗體之外時,其它窗體也能夠接受輸入事件
???????mLp.flags = mLp.flags | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
??? }
?
??? publicvoid installWindow(IWindowManager wms) throws Exception {
??? ????// 首先向WMS聲明一個Token,不論什么一個Window都須要隸屬與一個特定類型的Token
??????? wms.addWindowToken(mToken,WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
??????? // 設置窗體所隸屬的Token
???????mLp.token = mToken;
??????? // 通過IWindowSession將窗體安裝進WMS,注意,此時僅僅是安裝到WMS。本例的Window
??????? // 眼下仍然沒有有效的Surface。只是,經過這個調用后。mInputChannel已經能夠用來接受
??????? // 輸入事件了
??????? mSession.add(mWindow,0, mLp, View.VISIBLE, mInsets, mInputChannel);
??????? /*通過IWindowSession要求WMS對本窗體進行又一次布局。經過這個操作后。WMS將會為窗體
???????? 創建一塊用于繪制的Surface并保存在參數mSurface中。同一時候。這個Surface被WMS放置在
????????LayoutParams所指定的位置上 */
??????? mSession.relayout(mWindow,0, mLp, mLp.width, mLp.height, View.VISIBLE,
??????????????????????? 0, mFrame, mInsets,mVisibleInsets, mConfig, mSurface);
??????? if(!mSurface.isValid()) {
??????????? thrownew RuntimeException("Failed creating Surface.");
??????? }
??????? // 基于WMS返回的InputChannel創建一個Handler,用于監聽輸入事件
??????? //mInputHandler一旦被創建,就已經在監聽輸入事件了
??????? mInputHandler= new InputHandler(mInputChannel, Looper.myLooper());
??? }
?
??? publicvoid uninstallWindow(IWindowManager wms) throws Exception {
??????? // 從WMS處卸載窗體
??????? mSession.remove(mWindow);
??????? // 從WMS處移除之前加入的Token
??????? wms.removeWindowToken(mToken);
??? }
?
??? publicvoid scheduleNextFrame() {
??????? // 要求在顯示系統刷新下一幀時回調mFrameRender。注意,僅僅回調一次
??????? mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION
???????????????, mFrameRender, null);
??? }
?
??? // 這個Runnable對象用以在窗體上描繪一幀
??? publicRunnable mFrameRender = new Runnable() {
???????@Override
??????? publicvoid run() {
??????????? try{
???????????????// 獲取當期時間戳
???????????????long time = mChoreographer.getFrameTime() % 1000;
?
???????????????// 畫圖
???????????????if (mSurface.isValid()) {
???????????????????Canvas canvas = mSurface.lockCanvas(null);
???????????????????canvas.drawColor(Color.DKGRAY);
???????????????????canvas.drawRect(2 * mLp.width * time / 1000
??????????????????????????? - mLp.width, 0, 2 *mLp.width * time
??????????????????????????? / 1000, mLp.height,mPaint);
???????????????????mSurface.unlockCanvasAndPost(canvas);
???????????????????mSession.finishDrawing(mWindow);
???????????????}
?
??????????? if(mContinueAnime)
???????????????scheduleNextFrame();
???????????} catch (Exception e) {
???????????????e.printStackTrace();
???????????}
??????? }
??? };
?
??? // 定義一個類繼承InputEventReceiver。用以在其onInputEvent()函數中接收窗體的輸入事件
??? classInputHandler extends InputEventReceiver {
???????Looper mLooper = null;
??????? publicInputHandler(InputChannel inputChannel, Looper looper) {
??????????? super(inputChannel,looper);
??????????? mLooper= looper;
??????? }
???????@Override
??????? publicvoid onInputEvent(InputEvent event) {
??????????? if(event instanceof MotionEvent) {
???????????????MotionEvent me = (MotionEvent)event;
?? ?????????????if (me.getAction() ==MotionEvent.ACTION_UP) {
???????????????????// 退出程序
???????????????????mLooper.quit();
???????????????}
???????????}
??????????? super.onInputEvent(event);
??????? }
??? }
?
??? // 實現一個繼承自IWindow.Stub的類MyWindow。
??? classMyWindow extends IWindow.Stub {
??????? // 保持默認的實現就可以
??? }
}
由于此程序使用了大量的隱藏API(即SDK中未定義這些API)。因此須要放在Android源碼環境中進行編譯它。相應的Android.mk例如以下:
[-->Android.mk]
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
?
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := samplewindow
include $(BUILD_JAVA_LIBRARY)
將這兩個文件放在$TOP/frameworks/base/cmds/samplewindow/下,然后用make或mm命令進行編譯。終于生成的結果是samplewindow.jar,文件位置在out/target/<ProductName>/system/framework/下。將該文件通過adb push到手機的/system/framework/下。
提示讀者可使用Android4.2模擬器來運行此程序。
然而,samplewindow.jar不是一個可運行程序,。故。需借助Android的app_process工具來載入并運行它。筆者編寫了一個腳本做為啟動器:
[-->sw.sh]
base=/system
export CLASSPATH=$base/framework/samplewindow.jar
?
exec app_process $base/binunderstanding.wms.samplewindow.SampleWindow "$@"
?
注意app_process事實上就是大名鼎鼎的zygote。只是。僅僅有使用--zygote參數啟動時它才會給改名為zygote[2],否則就像java –jar命令一樣,運行指定類的main靜態函數。
在手機中運行該腳本,其運行結果是一個灰色的方塊不斷地從屏幕左側移動到右側,如圖4-1所看到的。
圖 4-1 SampleWindow在手機中的運行效果
2.初識窗體的創建、繪制與銷毀
SampleWindow的這段代碼盡管簡單,可是卻非常好地提煉了一個窗體的創建、繪制以及銷毀的過程。注意,本例沒有使用不論什么 WMS以外的系統服務。也沒有使用Android系統四大組件的框架,也就是說。假設你愿意,能夠利用WMS實現自己的UI與應用程序框架。這樣就能夠衍生出一個新的平臺了。
總結在client創建一個窗體的步驟:
·??獲取IWindowSession和WMS實例。client能夠通過IWindowSession向WMS發送請求。
·??創建并初始化WindowManager.LayoutParams。注意這里是WindowManager下的LayoutParams,它繼承自ViewGroup.LayoutParams類,并擴展了一些窗體相關的屬性。當中最重要的是type屬性。這個屬性描寫敘述了窗體的類型,而窗體類型正是WMS對多個窗體進行ZOrder排序的依據。
·??向WMS加入一個窗體令牌(WindowToken)。本章興許將分析窗體令牌的概念,眼下讀者僅僅要知道。窗體令牌描寫敘述了一個顯示行為,而且WMS要求每一個窗體必須隸屬于某一個顯示令牌。
·??向WMS加入一個窗體。必須在LayoutParams中指明此窗體所隸屬于的窗體令牌,否則在某些情況下加入操作會失敗。
在SampleWindow中,不設置令牌也可成功?完畢加入操作。由于窗體的類型被設為TYPE_SYSTEM_ALERT,它是系統窗體的一種。
而對于系統窗體。WMS會自己主動為其創建顯示令牌,故無需client擔心。此話題將會在后文進行更具體的討論。
·??向WMS申請對窗體進行又一次布局(relayout)。所謂的又一次布局,就是依據窗體新的屬性去調整其Surface相關的屬性。或者又一次創建一個Surface(比如窗體尺寸變化導致之前的Surface不滿足要求)。向WMS加入一個窗體之后,其僅僅是將它在WMS中進行了注冊而已。僅僅有經過又一次布局之后,窗體才擁有WMS為其分配的畫布。有了畫布,窗體之后就能夠隨時進行繪制工作了。
而窗體的繪制步驟例如以下:
·??通過Surface.lock()函數獲取能夠在其上作畫的Canvas實例。
·??使用Canvas實例進行作畫。
·??通過Surface.unlockCanvasAndPost()函數提交繪制結果。
提示關于Surface的原理與使用方法,請參考《卷 I》第8章“深入理解Surface系統”。
這是對Surface作畫的標準方法。
在client也能夠通過OpenGL進行作畫。只是這超出了本書的討論范圍。另外,在SampleWindow樣例中使用了Choreographer類進行了動畫幀的安排。Choreographer意為編舞指導,是Jelly Bean新增的一個工具類。其使用方法與Handler的post()函數非Z且不會再顯示新的窗體,則須要從WMS將之前加入的顯示令牌一并刪除。
3.窗體的概念
在SampleWindow樣例中,有一個名為mWindow(類型為IWindow)的變量。讀者可能會理所當然地覺得它就是窗體了。
事實上這樣的認識并不全然正確。
IWindow繼承自Binder,而且其Bn端位于應用程序一側(在樣例中IWindow的實現類MyWindow就繼承自IWindow.Stub),于是其在WMS一側僅僅能作為一個回調。以及起到窗體Id的作用。
那么。窗體的本質是什么呢?
是進行繪制所使用的畫布:Surface。
當一塊Surface顯示在屏幕上時,就是用戶所看到的窗體了。client向WMS加入一個窗體的過程,事實上就是WMS為其分配一塊Surface的過程。一塊塊Surface在WMS的管理之下有序地排布在屏幕上,Android才得以呈現出多姿多彩的界面來。所以從這個意義上來講。WindowManagerService被稱之為SurfaceManagerService也說得通的。
于是,依據對Surface的操作類型能夠將Android的顯示系統分為三個層次。如圖4-2所看到的。
圖 4-2 Android顯示系統的三個層次
在圖4-2中:
·??第一個層次是UI框架層,其工作為在Surface上繪制UI元素以及響應輸入事件。
·??第二個層次為WMS,其主要工作在于管理Surface的分配、層級順序等。
·??第三層為SurfaceFlinger。負責將多個Surface混合并輸出。
經過這個樣例的介紹,相信大家對WMS的功能有了一個初步的了解。接下來,我們要進入WMS的內部,通過其啟動過程一窺它的構成。
4.1.2WMS的構成
俗話說,一個好漢三個幫!WMS的強大是由非常多重要的成員互相協調工作而實現的。了解WMS的構成將會為我們深入探索WMS打下良好的基礎,進而分析它的啟動過程。這是再合適只是了。
1.WMS的誕生
和其它的系統服務一樣,WMS的啟動位于SystemServer.java中ServerThread類的run()函數內。
[-->SystemServer.java::ServerThread.run()]
Public void run() {
??? ......
???WindowManagerService wm = null;
??? ......
??? try {
???????......
??????? // ①創建WMS實例
?????? /* 通過WindowManagerService的靜態函數main()創建WindowManagerService的實例。
?????????? 注意main()函數的兩個參數wmHandler和uiHandler。這兩個Handler分別運行于由
?????????? ServerThread所創建的兩個名為“WindowManager”和“UI”的兩個HandlerThread中 */
??????? wm =WindowManagerService.main(context, power, display, inputManager,
??????? uiHandler,wmHandler,
??????? ????????????factoryTest !=SystemServer.FACTORY_TEST_LOW_LEVEL,
???????????????????!firstBoot, onlyCore);
??????? // 加入到ServiceManager中去
??? ????ServiceManager.addService(Context.WINDOW_SERVICE,wm);
???????......
??? catch(RuntimeException e) {
??????? ......
??? }
??? ......
??? try {
??????? //②初始化顯示信息
??????? wm.displayReady();
??? } catch(Throwable e) {......}
??? ......
??? try {
??????? // ③通知WMS,系統的初始化工作完畢
??????? wm.systemReady();
??? } catch(Throwable e) {......}
??? ......
}
由此能夠看出,WMS的創建分為三個階段:
·??創建WMS的實例。
·??初始化顯示信息。
·??處理systemReady通知。
接下來,將通過以上三個階段分析WMS從無到有的過程。
看一下WMS的main()函數的實現:
[-->WindowManagerService.java::WindowManagerSrevice.main()]
public static WindowManagerService main(finalContext context,
??? finalPowerManagerService pm, final DisplayManagerService dm,
??? finalInputManagerService im,
??? finalHandler uiHandler, final Handler wmHandler,
??? finalboolean haveInputMethods, final boolean showBootMsgs,
??? finalboolean onlyCore) {
??? finalWindowManagerService[] holder = new WindowManagerService[1];
??? // 通過由SystemServer為WMS創建的Handler新建一個WindowManagerService對象
??? // 此Handler運行在一個名為WindowManager的HandlerThread中
??? wmHandler.runWithScissors(newRunnable() {
???????@Override
??????? publicvoid run() {
??????????? holder[0]= new WindowManagerService(context, pm, dm, im,
??????????????????????????? uiHandler,haveInputMethods, showBootMsgs, onlyCore);
??????? }
??? }, 0);
??? returnholder[0];
}
注意Handler類在Android 4.2中新增了一個API:runWithScissors()。這個函數將會在Handler所在的線程中運行傳入的Runnable對象。同一時候堵塞調用線程的運行,直到Runnable對象的run()函數運行完畢。
WindowManagerService.main()函數在ServerThread專為WMS創建的線程“WindowManager”上創建了一個WindowManagerService的新實例。WMS中全部須要的Looper對象,比如Handler、Choreographer等。將會運行在“WindowManager”線程中。
接下來看一下其構造函數。看一下WMS定義了哪些重要的組件。
[-->WindowManagerService.java::WindowManagerService.WindowManagerService()]
private WindowManagerService(Context context,PowerManagerService pm,
???????????DisplayManagerService displayManager, InputManagerService inputManager,
???????????Handler uiHandler,
??? booleanhaveInputMethods, boolean showBootMsgs, boolean onlyCore)
??? ......
??? mDisplayManager=
??? ?????????(DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
??? mDisplayManager.registerDisplayListener(this,null);
??? Display[]displays = mDisplayManager.getDisplays();
??? /* 初始化DisplayContent列表。DisplayContent是Android4.2為支持多屏幕輸出所引入的一個
?????? 概念。一個DisplayContent指代一塊屏幕,屏幕能夠是手機自身的屏幕。也能夠是基于Wi-FiDisplay
?????? 技術的虛擬屏幕[3]*/
??? for(Display display : displays) {
??????? createDisplayContentLocked(display);
??? }
??? .....
??? /* 保存InputManagerService。輸入事件終于要分發給具有焦點的窗體。而WMS是窗體管理者,
?????? 所以WMS是輸入系統中的重要一環。關于輸入系統的內容將在第5章中深入探討*/
??? mInputManager= inputManager;
?
??? // 這個看起來其貌不揚的mAnimator,事實上具有非常關鍵的數據。它管理著全部窗體的動畫
??? mAnimator= new WindowAnimator(this, context, mPolicy);
?
??? // 在“UI“線程中將對還有一個重要成員mPolicy,也就是WindowManagerPolicy進行初始化
??? initPolicy(uiHandler);
?
??? // 將自己加入到Watchdog中
??? Watchdog.getInstance().addMonitor(this);
??? ......
}
第二步。displayReady()函數的調用主要是初始化顯示尺寸的信息。其內容比較瑣碎。這里就先不介紹了。只是值得注意的一點是。再displayReady()完畢后。WMS會要求ActivityManagerService進行第一次Configuration的更新。
第三步。在systemReady()函數中。WMS本身將不會再做不論什么操作了,直接調用mPolicy的systemReady()函數。
2.WMS的重要成員
總結一下在WMS的啟動過程中所創建的重要成員,參考圖4-3。
圖 4-3 WMS的重要成員
下面是對圖4-3中重要成員的簡介:
·??mInputManager,InputManagerService(輸入系統服務)的實例。用于管理每一個窗體的輸入事件通道(InputChannel)以及向通道上派發事件。關于輸入系統的具體內容將在本書第5章具體探討。
·??mChoreographer,Choreographer的實例,在SampleWindow的樣例中已經見過了。Choreographer的意思是編舞指導。
它擁有從顯示子系統獲取VSYNC同步事件的能力,從而能夠在合適的時機通知渲染動作,避免在渲染的過程中由于發生屏幕重繪而導致的畫面撕裂。從這個意義上來講,Choreographer的確是指導Android翩翩起舞的大師。WMS使用Choreographer負責驅動全部的窗體動畫、屏幕旋轉動畫、墻紙動畫的渲染。
·??mAnimator。WindowAnimator的實例。它是全部窗體動畫的總管(窗體動畫是一個WindowStateAnimator的對象)。在Choreographer的驅動下。逐個渲染全部的動畫。
·??mPolicy,WindowPolicyManager的一個實現。眼下它僅僅有PhoneWindowManager一個實現類。mPolicy定義了非常多窗體相關的策略,能夠說是WMS的首席顧問。每當WMS要做什么事情的時候,都須要向這個顧問請教應當怎樣做。比如,告訴WMS某一個類型的Window的ZOrder的值是多少,幫助WMS矯正不合理的窗體屬性。會為WMS監聽屏幕旋轉的狀態,還會預處理一些系統按鍵事件(比如HOME、BACK鍵等的默認行為就是在這里實現的)。等等。所以。mPolicy可謂是WMS中最重要的一個成員了。
·??mDisplayContents,一個DisplayContent類型的列表。Android4.2支持基于Wi-fi Display的多屏幕輸出,而一個DisplayContent描寫敘述了一塊能夠繪制窗體的屏幕。
每一個DisplayContent都用一個整型變量作為其ID。當中手機默認屏幕的ID由Display.DEFAULT_DISPLAY常量指定。DisplayContent的管理是由DisplayManagerService完畢的,在本章不會去探討DisplayContent的實現細節。而是關注DisplayContent對窗體管理與布局的影響。
下面的幾個成員的初始化并沒有出如今構造函數中。只是它們的重要性一點也不亞于上面幾個。
·??mTokenMap,一個HashMap,保存了全部的顯示令牌(類型為WindowToken)。用于窗體管理。
在SampleWindow樣例中以前提到過,一個窗體必須隸屬于某一個顯示令牌。在那個樣例中所加入的令牌就被放進了這個HashMap中。從這個成員中還衍生出幾個輔助的顯示令牌的子集。比如mAppTokens保存了全部屬于Activity的顯示令牌(WindowToken的子類AppWindowToken),mExitingTokens則保存了正在退出過程中的顯示令牌等。當中mAppTokens列表是有序的,它與AMS中的mHistory列表的順序保持一致。反映了系統中Activity的順序。
·??mWindowMap,也是一個HashMap。保存了全部窗體的狀態信息(類型為WindowState),用于窗體管理。
在SampleWindow樣例中,使用IWindowSession.add()所加入的窗體的狀態將會被保存在mWindowMap中。與mTokenMap一樣,mWindowMap一樣有衍生出的子集。
比如mPendingRemove保存了那些退出動畫播放完畢并即將被移除的窗體,mLosingFocus則保存了那些失去了輸入焦點的窗體。在DisplayContent中,也有一個windows列表,這個列表存儲了顯示在此DisplayContent中的窗體,而且它是有序的。
窗體在這個列表中的位置決定了其終于顯示時的Z序。
·??mSessions。一個List。元素類型為Session。Session事實上是SampleWindow樣例中的IWindowSession的Bn端。也就是說,mSessions這個列表保存了當前全部想向WMS尋求窗體管理服務的client。
注意Session是進程唯一的。
·??mRotation,僅僅是一個int型變量。
它保存了當前手機的旋轉狀態。
WMS定義的成員一定不止這些,可是它們是WMS每一種功能最核心的變量。讀者在這里能夠線對它們有一個感性的認識。在本章興許的內容里將會具體分析它們在WMS的各種工作中所發揮的核心作用。
4.1.3 初識WMS的小結
這一節通過SampleWindow的樣例向讀者介紹了WMS的client怎樣使用窗體,然后通過WMS的誕生過程簡單剖析了一下WMS的重要成員組成。以期通過本節的學習能夠為興許的學習打下基礎。
從下一節開始,我們將會深入探討WMS的工作原理。
4.2 WMS的窗體管理結構
經過上一節的介紹,讀者應該對WMS的窗體管理有了一個感性的認識。從這一節開將深入WMS的內部去剖析其工作流程。
依據前述內容可知,SampleWindow加入窗體的函數是IWindowSession.add()。
IWindowSession是WMS與client交互的一個代理,add則直接調用到了WMS的addWindow()函數。
我們將從這個函數開始WMS之旅。
本小節僅僅討論它的前半部分。
注意由于篇幅所限,本章不準備討論removeWindow的實現。
[-->WindowManagerService.java::WindowManagerService.addWindow()Part1]
public int addWindow(Session session, IWindowclient, int seq,
???????????WindowManager.LayoutParams attrs, int viewVisibility,int displayId
???????????Rect outContentInsets, InputChannel outInputChannel) {
?
??????? // 首先檢查權限,沒有權限的client不能加入窗體
??????? intres = mPolicy.checkAddPermission(attrs);
?
???????......
?
??????? // 當為某個窗體加入子窗體時,attachedWindow將用來保存父窗體的實例
???????WindowState attachedWindow = null;
??????? //win就是即將被加入的窗體了
???????WindowState win = null;
???????......
??????? finalint type = attrs.type;
??????? synchronized(mWindowMap){
???????????......
???????????//①獲取窗體要加入到的DisplayContent
???????????/* 在加入窗體時,必須通過displayId參數指定加入到哪一個DisplayContent。
??????????????SampleWindow樣例沒有指定displayId參數。Session會替SampleWindow選擇
??????????????DEFAULT_DISPLAY,也就是手機屏幕 */
??????????? finalDisplayContent displayContent = getDisplayContentLocked(displayId);
??????????? if(displayContent == null) {
???????????????return WindowManagerGlobal.ADD_INVALID_DISPLAY;
???????????}
???????????......
???????????// 假設要加入的窗體是還有一個的子窗體,就要求父窗體必須已經存在
??????????? // 注意。 attrs.type表示了窗體的類型,attrs.token則表示了窗體所隸屬的對象
???????????// 對于子窗體來說。attrs.token表示了父窗體
??????????? if(type >= FIRST_SUB_WINDOW &&.type <= LAST_SUB_WINDOW) {
???????????????attachedWindow = windowForClientLocked(null, attrs.token, false);
???????????????if (attachedWindow == null) {
???????????????????return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
???????????????}
???????????????//在這里還能夠看出WMS要求窗體的層級關系最多為兩層
???????????????if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
??????????????????????????????? &&attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
????????? ??????????return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
???????????????}
???????????}
?
??????????? booleanaddToken = false;
???????????// ②WindowToken出場!
依據client的attrs.token取出已注冊的WindowToken
???????????WindowToken token = mTokenMap.get(attrs.token);
???????????// 下面的if語句塊初步揭示了WindowToken和窗體之間的關系
??????????? if(token == null) {
???????????????// 對于下面幾種類型的窗體,必須通過LayoutParams.token成員為其指定一個已經
???????????????// 加入至WMS的WindowToken
???????????????if (type >= FIRST_APPLICATION_WINDOW
??????????????????????????????? && type<= LAST_APPLICATION_WINDOW) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????????if (type == TYPE_INPUT_METHOD) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????????if (type == TYPE_WALLPAPER) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????????if (type == TYPE_DREAM) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????????// 其它類型的窗體則不須要事先向WMS加入WindowToken由于WMS會在這里隱式地創
???????????????// 建一個。注意最后一個參數false,這表示此WindowToken由WMS隱式創建。
???????????????token = new WindowToken(this, attrs.token, -1, false);
? ??????????????addToken = true;
???????????} else if (type >= FIRST_APPLICATION_WINDOW
???????????????????????????????????????????? &&type <= LAST_APPLICATION_WINDOW) {
???????????????// 對于APPLICATION類型的窗體,要求相應的WindowToken的類型也為APPLICATION
???????????????// 而且是WindowToken的子類:AppWindowToken
???????????????AppWindowToken atoken = token.appWindowToken;
???????????????if (atoken == null) {
???????????????????return WindowManagerImpl.ADD_NOT_APP_TOKEN;
???????????????} else if (atoken.removed) {
????????? ??????????returnWindowManagerImpl.ADD_APP_EXITING;
???????????????}
???????????????if (type==TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn){
???????????????????return WindowManagerImpl.ADD_STARTING_NOT_NEEDED;
???????????????}
???????????} else if (type == TYPE_INPUT_METHOD) {
???????????????// 對于其它幾種類型的窗體也有相似的要求:窗體類型必須與WindowToken的類型一致
???????????????if (token.windowType != TYPE_INPUT_METHOD) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????} else if (type == TYPE_WALLPAPER) {
???????????????if (token.windowType != TYPE_WALLPAPER) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????} else if (type == TYPE_DREAM) {
???????????????if (token.windowType != TYPE_DREAM) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????}
?
???????????// ③WMS為要加入的窗體創建了一個WindowState對象
???????????// 這個對象維護了一個窗體的全部狀態信息
??????????? win= new WindowState(this, session, client, token,
??????????? attachedWindow,seq, attrs, viewVisibility, displayContent);
?
???????????......
???????????// WindowManagerPolicy出場了。這個函數的調用會調整LayoutParams的一些成員的取值
??????????? mPolicy.adjustWindowParamsLw(win.mAttrs);
?
??????????? res= mPolicy.prepareAddWindowLw(win, attrs);
??????????? if(res != WindowManagerGlobal.ADD_OKAY) {
???????????????return res;
???????????}
?
???????????// 接下來將剛剛隱式創建的WindowToken加入到mTokenMap中去。通過這行代碼應該
???????????//讀者應該能想到。全部的WindowToken都被放入這個HashTable中
??????????? ......
?
??????????? if(addToken) {
???????????????mTokenMap.put(attrs.token, token);
???????????}
??????????? win.attach();
???????????// 然后將WindowState對象加入到mWindowMap中
??????????? mWindowMap.put(client.asBinder(),win);
???????????// 剩下的代碼稍后再做分析
???????????......
??????? }
??? }
addWindow()函數的前段代碼展示了三個重要的概念,各自是WindowToken、WindowState以及DisplayContent。而且在函數開始處對窗體類型的檢查推斷也初步揭示了它們之間的關系:除子窗體外,加入不論什么一個窗體都必須指明其所屬的WindowToken;窗體在WMS中通過一個WindowState實例進行管理和保管。同一時候必須在窗體中指明其所屬的DisplayContent,以便確定窗體將被顯示到哪一個屏幕上。
4.2.1 理解WindowToken
1.WindowToken的意義
為了搞清晰WindowToken的作用是什么。看一下其位于WindowToken.java中的定義。盡管它未定義不論什么函數,但其成員變量的意義卻非常重要。
·??WindowToken將屬于同一個應用組件的窗體組織在了一起。所謂的應用組件能夠是Activity、InputMethod、Wallpaper以及Dream。在WMS對窗體的管理過程中,用WindowToken指代一個應用組件。比如在進行窗體ZOrder排序時。屬于同一個WindowToken的窗體會被安排在一起,而且在當中定義的一些屬性將會影響全部屬于此WindowToken的窗體。這些都表明了屬于同一個WindowToken的窗體之間的緊密聯系。
·??WindowToken具有令牌的作用,是相應用組件的行為進行規范管理的一個手段。
WindowToken由應用組件或其管理者負責向WMS聲明并持有。應用組件在須要新的窗體時。必須提供WindowToken以表明自己的身份,而且窗體的類型必須與所持有的WindowToken的類型一致。
從上面的代碼能夠看到,在創建系統類型的窗體時不須要提供一個有效的Token,WMS會隱式地為其聲明一個WindowToken,看起來誰都能夠加入個系統級的窗體。難道Android為了內部使用方便而置安全于不顧嗎?非也。addWindow()函數一開始的mPolicy.checkAddPermission()的目的就是如此。它要求client必須擁有SYSTEM_ALERT_WINDOW或INTERNAL_SYSTEM_WINDOW權限才干創建系統類型的窗體。
2.向WMS聲明WindowToken
既然應用組件在創建一個窗體時必須指定一個有效的WindowToken才行,那么WindowToken到底該怎樣聲明呢?
在SampleWindow應用中,使用wms.addWindowToken()函數聲明mToken作為它的令牌,所以在加入窗體時,通過設置lp.token為mToken向WMS進行出示。從而獲得WMS加入窗體的許可。
這說明。僅僅要是一個Binder對象(隨便一個),都能夠作為Token向WMS進行聲明。對于WMS的client來說,Token僅僅是一個Binder對象而已。
為了驗證這一點,來看一下addWindowToken的代碼。例如以下所看到的:
[-->WindowManagerService.java::WindowManagerService.addWindowToken()]
???@Override
??? publicvoid addWindowToken(IBinder token, int type) {
??????? // 須要聲明Token的調用者擁有MANAGE_APP_TOKENS的權限
??????? if(!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
???????????????"addWindowToken()")) {
??????????? thrownew SecurityException("Requires MANAGE_APP_TOKENS permission");
??????? }
?
??????? synchronized(mWindowMap){
??????????? ......
???????????// 注意其構造函數的參數與addWindow()中不同。最后一個參數為true,表明這個Token
???????????// 是顯式申明的
??????????? wtoken= new WindowToken(this, token, type, true);
??????????? mTokenMap.put(token,wtoken);
??????????? ......
??????? }
??? }
使用addWindowToken()函數聲明Token,將會在WMS中創建一個WindowToken實例。并加入到mTokenMap中。鍵值為client用于聲明Token的Binder實例。與addWindow()函數中隱式地創建WindowToken不同。這里的WindowToken被聲明為顯式的。隱式與顯式的差別在于,當隱式創建的WindowToken的最后一個窗體被移除后,此WindowToken會被一并從mTokenMap中移除。顯式創建的WindowToken僅僅能通過removeWindowToken()顯式地移除。
addWindowToken()這個函數告訴我們。WindowToken事實上有兩層含義:
·??對于顯示組件(client)而言的Token。是隨意一個Binder的實例,對顯示組件(client)來說僅僅是一個創建窗體的令牌,沒有其它的含義。
·??對于WMS而言的WindowToken這是一個WindowToken類的實例,保存了相應于client一側的Token(Binder實例)。并以這個Token為鍵。存儲于mTokenMap中。
client一側的Token是否已被聲明,取決于其相應的WindowToken是否位于mTokenMap中。
注意在普通情況下,稱顯示組件(client)一側Binder的實例為Token,而稱WMS一側的WindowToken對象為WindowToken。可是為了敘述方便,在沒有歧義的前提下不會過分細致地區分這兩個概念。
接下來,看一下各種顯示組件是怎樣聲明WindowToken的。
(1)??? Wallpaper和InputMethod的Token
Wallpaper的Token聲明在WallpaperManagerService中。
參考下面代碼:
[-->WallpaperManagerService.java::WallpaperManagerService.bindWallpaperComponentLocked()]
BooleanbindWallpaperComponentLocked(......) {
??? ......
???WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
??? ......
??? mIWindowManager.addWindowToken(newConn.mToken,
?????????????????????????WindowManager.LayoutParams.TYPE_WALLPAPER);
??? ......
}
WallpaperManagerService是Wallpaper管理器,它負責維護系統已安裝的全部的Wallpaper并在它們之間進行切換。而這個函數的目的是準備顯示一個Wallpaper。
newConn.mToken與SampleWindow樣例一樣。是一個簡單的Binder對象。
這個Token將在即將顯示出來的Wallpaper被連接時傳遞給它,之后Wallpaper就可以通過這個Token向WMS申請創建繪制壁紙所需的窗體了。
注意 WallpaperManagerService向WMS聲明的Token類型為TYPE_WALLPAPER,所以,Wallpaper僅能本分地創建TYPE_WALLPAPER類型的窗體。
相應的。WallpaperManagerService會在detachWallpaperLocked()函數中取消對Token的聲明:
[-->WallpaperManagerService.java::WallpaperManagerService.detachWallpaperLocked()]
booleandetachWallpaperLocked(WallpaperData wallpaper){
??? ......
??? mIWindowManager.removeWindowToken(wallpaper.connection.mToken);
??? ......
}
再此之后。假設這個被detach的Wallpaper想再要創建窗體便不再可能了。
WallpaperManagerService使用WindowToken對一個特定的Wallpaper做出了例如以下限制:
·??Wallpaper僅僅能創建TYPE_WALLPAPER類型的窗體。
·??Wallpaper顯示的生命周期由WallpaperManagerService牢牢地控制著。僅有當前的Wallpaper才干創建窗體并顯示內容。其它的Wallpaper由于沒有有效的Token,而無法創建窗體。
InputMethod的Token的來源與Wallpaper相似,其聲明位于InputMethodManagerService的startInputInnerLocked()函數中,取消聲明的位置在InputmethodManagerService的unbindCurrentMethodLocked()函數。InputMethodManagerService通過Token限制著每一個InputMethod的窗體類型以及顯示生命周期。
(2)??? Activity的Token
Activity的Token的使用方式與Wallpaper和InputMethod相似。可是其包括很多其它的內容。
畢竟,對于Activity。不管是其組成還是操作都比Wallpaper以及InputMethod復雜得多。
對此。WMS專為Activity實現了一個WindowToken的子類:AppWindowToken。
既然AppWindowToken是為Activity服務的。那么其聲明自然在ActivityManagerService中。具體位置為ActivityStack.startActivityLocked(),也就是啟動Activity的時候。相關代碼例如以下:
[-->ActivityStack.java::ActivityStack.startActivityLocked()]
private final void startActivityLocked(......) {
??? ......
??? mService.mWindowManager.addAppToken(addPos,r.appToken, r.task.taskId,
???????????????????????????????r.info.screenOrientation, r.fullscreen);
??? ......
}
startActivityLocked()向WMS聲明r.appToken作為此Activity的Token,這個Token是在ActivityRecord的構造函數中創建的。隨然后在realStartActivityLocked()中將此Token交付給即將啟動的Activity。
[-->ActivityStack.java::ActivityStack.realStartActivityLocked()]
final boolean realStartActivityLocked(......) {
??? ......
??? app.thread.scheduleLaunchActivity(newIntent(r.intent), r.appToken,
????? ??????????System.identityHashCode(r), r.info,
?????? ?????????newConfiguration(mService.mConfiguration),
???? ???????????r.compat, r.icicle, results, newIntents,!andResume,
??? mService.isNextTransitionForward(),profileFile, profileFd,
????? ??????????profileAutoStop);
??? ......
}
啟動后的Activity就可以使用此Token創建類型為TYPE_APPLICATION的窗體了。
取消Token的聲明則位于ActivityStack.removeActivityFromHistoryLocked()函數中。
Activity的Token在client是否和Wallpaper一樣,僅僅是一個主要的Binder實例呢?事實上不然。看一下r.appToken的定義能夠發現。這個Token的類型是IApplicationToken.Stub。當中定義了一系列和窗體相關的一些通知回調,它們是:
·?? windowsDrawn()。當窗體完畢初次繪制后通知AMS。
·?? windowsVisible(),當窗體可見時通知AMS。
·?? windowsGone(),當窗體不可見時通知AMS。
·?? keyDispatchingTimeout(),窗體沒能按時完畢輸入事件的處理。這個回調將會導致ANR。
·?? getKeyDispatchingTimeout(),從AMS處獲取界定ANR的時間。
AMS通過ActivityRecord表示一個Activity。而ActivityRecord的appToken在其構造函數中被創建,所以每一個ActivityRecord擁有其各自的appToken。
而WMS接受AMS對Token的聲明。并為appToken創建了唯一的一個AppWindowToken。
因此,這個類型為IApplicationToken的Binder對象appToken粘結了AMS的ActivityRecord與WMS的AppWindowToken。僅僅要給定一個ActivityRecord。都能夠通過appToken在WMS中找到一個相應的AppWindowToken,從而使得AMS擁有了操縱Activity的窗體繪制的能力。比如,當AMS覺得一個Activity須要被隱藏時。以Activity相應的ActivityRecord所擁有的appToken作為參數調用WMS的setAppVisibility()函數。此函數通過appToken找到其相應的AppWindowToken,然后將屬于這個Token的全部窗體隱藏。
注意每當AMS由于某些原因(如啟動/結束一個Activity,或將Task移到前臺或后臺)而調整ActivityRecord在mHistory中的順序時,都會調用WMS相關的接口移動AppWindowToken在mAppTokens中的順序。以保證兩者的順序一致。在后面解說窗體排序規則時會介紹到,AppWindowToken的順序對窗體的順序影響非常大。
4.2.2 理解WindowState
從WindowManagerService.addWindow()函數的實現中能夠看出,當向WMS加入一個窗體時,WMS會為其創建一個WindowState。
WindowState表示一個窗體的全部屬性,所以它是WMS中事實上的窗體。
這些屬性將在后面遇到時再做介紹。
相似于WindowToken。WindowState在顯示組件一側也有個相應的類型:IWindow.Stub。IWindow.Stub提供了非常多與窗體管理相關通知的回調。比如尺寸變化、焦點變化等。
另外,從WindowManagerService.addWindow()函數中看到新的WindowState被保存到mWindowMap中,鍵值為IWindow的Bp端。mWindowMap是整個系統全部窗體的一個全集。
說明對照一下mTokenMap和mWindowMap。
這兩個HashMap維護了WMS中最重要的兩類數據:WindowToken及WindowState。
它們的鍵都是IBinder,差別是: mTokenMap的鍵值可能是IAppWindowToken的Bp端(使用addAppToken()進行聲明)。或者是其它隨意一個Binder的Bp端(使用addWindowToken()進行聲明);而mWindowToken的鍵值一定是IWindow的Bp端。
關于WindowState的很多其它細節將在后面的講述中進行介紹。只是經過上面的分析,不難得到WindowToken和WindowState之間的關系,參考圖4-4。
圖 4-4 WindowToken與WindowState的關系
更具體一些,以一個正在回放視頻并彈出兩個對話框的Activity為例。WindowToken與WindowState的意義如圖4-5所看到的。
圖 4-5WindowState與WindowToken的從屬關系
4.2.3理解DisplayContent??????????????
假設說WindowToken依照窗體之間的邏輯關系將其分組。那么DisplayContent則依據窗體的顯示位置將其分組。隸屬于同一個DisplayContent的窗體將會被顯示在同一個屏幕中。每一個DisplayContent都相應這一個唯一的ID。在加入窗體時能夠通過指定這個ID決定其將被顯示在那個屏幕中。
DisplayContent是一個非常具有隔離性的一個概念。
處于不同DisplayContent的兩個窗體在布局、顯示順序以及動畫處理上不會產生不論什么耦合。因此,就這幾個方面來說,DisplayContent就像一個孤島,全部這些操作都能夠在其內部獨立運行。因此,這些本來屬于整個WMS全局性的操作,變成了DisplayContent內部的操作了。
4.3 理解窗體的顯示次序
在addWindow()函數的前半部分中,WMS為窗體創建了用于描寫敘述窗體狀態的WindowState。接下來便會為新建的窗體確定顯示次序。
手機屏幕是以左上角為原點,向右為X軸方向。向下為Y軸方向的一個二維空間。為了方便管理窗體的顯示次序,手機的屏幕被擴展為了一個三維的空間。即多定義了一個Z軸,其方向為垂直于屏幕表面指向屏幕外。多個窗體依照其前后順序排布在這個虛擬的Z軸上,因此窗體的顯示次序又被稱為Z序(Z order)。在這一節中將深入探討WMS確定窗體顯示次序的過程以及其影響因素。
4.3.1 主序、子序和窗體類型
看一下WindowState的構造函數:
[-->WindowState.java::WindowState.WindowState()]
WindowState(WindowManagerService service, Sessions, IWindow c, WindowToken token,
??????WindowState attachedWindow, int seq, WindowManager.LayoutParams a,
??? intviewVisibility, final DisplayContent displayContent) {
??? ......
??? // 為子窗體分配ZOrder
??? if((mAttrs.type >= FIRST_SUB_WINDOW &&
???????????mAttrs.type <= LAST_SUB_WINDOW)) {
??????? // 這里的mPolicy即是WindowManagerPolicy
??????? mBaseLayer= mPolicy.windowTypeToLayerLw(
???????????????attachedWindow.mAttrs.type)
???????????????* WindowManagerService.TYPE_LAYER_MULTIPLIER
???????????????+ WindowManagerService.TYPE_LAYER_OFFSET;
??????? mSubLayer= mPolicy.subWindowTypeToLayerLw(a.type);
???????......
??? } else {// 為普通窗體分配ZOrder
??????? mBaseLayer= mPolicy.windowTypeToLayerLw(a.type)
???????????????* WindowManagerService.TYPE_LAYER_MULTIPLIER
???????????????+ WindowManagerService.TYPE_LAYER_OFFSET;
??????? mSubLayer= 0;
???????......
??? }
??? ......
}
窗體的顯示次序由兩個成員字段描寫敘述:主序mBaseLayer和子序mSubLayer。
主序用于描寫敘述窗體及其子窗體在全部窗體中的顯示位置。
而子序則描寫敘述了一個子窗體在其兄弟窗體中的顯示位置。
·??主序越大,則窗體及其子窗體的顯示位置相對于其它窗體的位置越靠前。
·??子序越大,則子窗體相對于其兄弟窗體的位置越靠前。對于父窗體而言,其主序取決于其類型。其子序則保持為0。而子窗體的主序與其父窗體一樣。子序則取決于其類型。從上述代碼能夠看到,主序與子序的分配工作是由WindowManagerPolicy的兩個成員函數windowTypeToLayerLw()和subWindowTypeToLayerLw()完畢的。
表4-1與表4-2列出了全部可能的窗體類型以及其主序與子序的值。
表 4-1 窗體的主序
窗體類型 | 主序 | 窗體類型 | 主序 |
TYPE_UNIVERSE_BACKGROUND | 11000 | TYPE_WALLPAPER | 21000 |
TYPE_PHONE | 31000 | TYPE_SEARCH_BAR | 41000 |
TYPE_RECENTS_OVERLAY | 51000 | TYPE_SYSTEM_DIALOG | 51000 |
TYPE_TOAST | 61000 | TYPE_PRIORITY_PHONE | 71000 |
TYPE_DREAM | 81000 | TYPE_SYSTEM_ALERT | 91000 |
TYPE_INPUT_METHOD | 101000 | TYPE_INPUT_METHOD_DIALOG | 111000 |
TYPE_KEYGUARD | 121000 | TYPE_KEYGUARD_DIALOG | 131000 |
TYPE_STATUS_BAR_SUB_PANEL | 141000 | 應用窗體與未知類型的窗體 | 21000 |
?
表 4-2 窗體的子序
子窗體類型 | 子序 |
TYPE_APPLICATION_PANEL | 1 |
TYPE_APPLICATION_ATTACHED_DIALOG | 1 |
TYPE_APPLICATION_MEDIA | -2 |
TYPE_APPLICATION_MEDIA_OVERLAY | -1 |
TYPE_APPLICATION_SUB_PANEL | 2 |
?
注意表4-2中的MEDIA和MEDIA_OVERLAY的子序為負值,這表明它們的顯示次序位于其父窗體的后面。這兩個類型的子窗體是SurfaceView控件創建的。SurfaceView被實例化后。會向WMS加入一個類型為MEDIA的子窗體。它的父窗體就是承載SurfaceView控件的窗體。
這個子窗體的Surface將被用于視頻回放、相機預覽或游戲繪制。為了不讓這個子窗體覆蓋住全部的父窗體中承載的其它控件(如拍照button,播放器控制button等)。它必須位于父窗體之后。
從表4-1所描寫敘述的主序與窗體類型的相應關系中能夠看出。WALLPAPER類型的窗體的主序竟和APPLICATION類型的窗體主序同樣。這看似有點不合常理。WALLPAPER不是應該顯示在全部Acitivity之下嗎?事實上WALLPAPER類型的窗體是一個非常不安分的角色,須要在全部的APPLICATION窗體之間跳來跳去。
這是由于,有的Activity指定了android:windowShowWallpaper為true。則表示窗體要求將用戶當前壁紙作為其背景。對于WMS來說,最簡單的辦法就是將WALLPAPER窗體放置到緊鄰擁有這個式樣的窗體的下方。在這樣的需求下。為了保證主序決定窗體順序的原則,WALLPAPER使用了與APPLICATION同樣的主序。另外。輸入法窗體也是一個非常特殊的情況。輸入法窗體會選擇輸入目標窗體,并將自己放置于其上。在本章中不討論這兩個特殊的樣例,WALLPAPER的排序規則將在第7章中進行介紹。而輸入法的排序則留給讀者自行研究。
盡管知道了窗體的主序與子序是怎樣分配的,只是我們仍然存有疑問:假設有兩個同樣類型的窗體,那么它們的主序與子序豈不是全然同樣?怎樣確定它們的顯示順序呢?事實上。表4-1和表4-2中所描寫敘述的主序和子序僅僅是排序的依據之中的一個,WMS須要依據當前全部同類型窗體的數量為每一個窗體計算終于的現實次序。
4.3.2 通過主序與子序確定窗體的次序
回到WMS的addWindow()函數中。繼續往下看:
[-->WindowManagerService.java::WindowManagerService.addWindow()]
public int addWindow(Session session, IWindowclient, int seq,
???WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
??? RectoutContentInsets, InputChannel outInputChannel) {
??? ......
??? synchronized(mWindowMap){
??????? //在前面的代碼中,WMS驗證了加入窗體的令牌的有效性。并為新窗體創建了新的WindowState對象
??????? // 新的WindowState對象在其構造函數中依據窗體類型初始化了其主序mBaseLayer和mSubLayer
???????......
??????? // 接下來,將新的WindowState依照顯示次序插入到當前DisplayContent的mWindows列表中
??????? // 為了代碼結構的清晰。不考慮輸入法窗體和壁紙窗體的處理
??? if (type== TYPE_INPUT_METHOD) {
??????? ......
??????? }else if (type == TYPE_INPUT_METHOD_DIALOG) {
??????? }else {
???????????// 將新的WindowState按顯示次序插入到當前DisplayContent的mWindows列表中
??????????? addWindowToListInOrderLocked(win,true);
??????????? if(type == TYPE_WALLPAPER) {
???????????????......
???????????}
??????? }
???????......
??????? // 依據窗體的排序結果,為DisplayContent的全部窗體分配終于的顯示次序
??????? assignLayersLocked(displayContent.getWindowList());
??????? ......
??? }
??? ......
??? returnres;
}
這里有兩個關鍵點:
·??addWindowToListInOrderLocked()將新建的WindowState依照一定的順序插入到當前DisplayContent的mWindows列表中。
在分析WMS的重要成員時提到過這個列表。它嚴格地依照顯示順序存儲了全部窗體的WindowState。
·??assignLayersLocked()將依據mWindows的存儲順序對全部的WindowState的主序和子序進行調整。
接下來分別分析一下這兩個函數。
1.addWindowToListInOrderLocked()分析
addWindowToListInOrderLocked()的代碼非常長。只是其排序原則卻比較清晰。這里直接給出其處理原則。感興趣的讀者可依據這些原則自行深究相關代碼。
注意再次強調一下,mWindows列表是依照主序與子序的升序進行排序的。所以顯示靠前的窗體放在列表靠后的位置,而顯示靠前的窗體,則位于列表的前面。也就是說,列表順序與顯示順序是相反的。
這點在閱讀代碼時要牢記。以免混淆。
在后面的敘述中,非特別強調,所謂的前后都是指顯示順序而不是在列表的存儲順序。
子窗體的排序規則:子窗體的位置計算是相對父窗體的。并依據其子序進行排序。由于父窗體的子序為0,所以子序為負數的窗體會放置在父窗體的后面,而子序為正數的窗體會放置在父窗體的前面。
假設新窗體與現有窗體子序相等,則正數子序的新窗體位于現有窗體的前面,負數子序的新窗體位于現有窗體的后面。
非子窗體的排序則是依據主序進行的,可是其規則較為復雜,分為應用窗體和非應用窗體兩種情況。之所以要差別處理應用窗體是由于全部的應用窗體的初始主序都是21000。而且應用窗體的位置應該與它所屬的應用的其它窗體放在一起。比如應用A顯示于應用B的后方。當應用A由于某個動作打開一個新的窗體時,新窗體應該位于應用A其它窗體的前面,可是不得覆蓋應用B的窗體。僅僅依據主序進行排序是無法實現這個管理邏輯的,還須要依賴Activity的順序。在WindowToken一節的解說中。以前簡單分析了mAppTokens列表的性質。它所保存的AppWindowToken的順序與AMS中ActivityRecord的順序時刻保持一致。因此,AppWindowToken在mAppTokens的順序就是Activity的順序。
非應用窗體的排序規則:依照主序進行排序,主序高者排在前面,當現有窗體的主序與新窗體同樣時,新窗體位于現有窗體的前面。
應用窗體的排序規則:如上所述,同一個應用的窗體的顯示位置必須相鄰。
假設當前應用已有窗體在顯示(當前應用的窗體存儲在其WindowState.appWindowToken.windows中),新窗體將插入到其所屬應用其它窗體的前面。可是保證STARTING_WINDOW永遠位于最前方,BASE_APPLICATION永遠位于最后方。假設新窗體是當前應用的第一個窗體。則參照其它應用的窗體順序,將新窗體插入到位于前面的最后一個應用的最后一個窗體的后方,或者位于后面的第一個應用的最前一個窗體的前方。
假設當前沒有其它應用的窗體能夠參照。則直接依據主序將新窗體插入到列表中。
窗體排序的總結例如以下:
·??子窗體依據子序相對于其父窗體進行排序。同樣子序的窗體,正子序則越新越靠前,負子序則越新越靠后。
·??應用窗體參照本應用其它窗體或相鄰應用的窗體進行排序。
假設沒有不論什么窗體能夠參照。則依據主序進行排序。
·??非應用窗體依據主序進行排序。
經過addWindowToListInOrderLocked()函數的處理之后。當前DisplayContent的窗體列表被插入了一個新的窗體。
然后等待assignLayersLocked()的進一步處理。
2.assignLayersLocked分析
assignLayersLocked()函數將依據每一個窗體的主序以及它們在窗體列表中的位置又一次計算終于的顯示次序mLayer。
[-->WindowManagerService.java::WindowManagerService.assignLayersLocked()]
privatefinal void assignLayersLocked(WindowList windows) {
??? int N = windows.size();
??? int curBaseLayer = 0;
??? // curLayer表示當前分配到的Layer序號
??? int curLayer = 0;
??? int i;
?
??? // 遍歷列表中的全部的窗體,逐個分配顯示次序
??? for (i=0; i<N; i++) {
??????? final WindowState w = windows.get(i);
??????? final WindowStateAnimator winAnimator =w.mWinAnimator;
??????? boolean layerChanged = false;
??????? int oldLayer = w.mLayer;
??????? if (w.mBaseLayer == curBaseLayer ||w.mIsImWindow
??????????????? || (i > 0 &&w.mIsWallpaper)) {
??????????? // 為具有同樣主序的窗體在curLayer上添加一個偏移量,并將curLayer作為終于的顯示次序
??????????? curLayer +=WINDOW_LAYER_MULTIPLIER;
??????????? w.mLayer = curLayer;
??????? } else {
??????????? // 此窗體擁有不同的主序,直接將主序作為其顯示次序并更新curLayer
??????????? curBaseLayer = curLayer =w.mBaseLayer;
??????????? w.mLayer = curLayer;
??????? }
??????? // 假設現實次序發生了變化則進行標記
??????? if (w.mLayer != oldLayer) {
??????????? layerChanged = true;
??????????? anyLayerChanged = true;
??????? }
??????? ......
??? }
??? ......
??? // 向當前DisplayContent的監聽者通知顯示次序的更新
??? if (anyLayerChanged) {
??????? scheduleNotifyWindowLayersChangedIfNeededLocked(
??????? getDefaultDisplayContentLocked());
??? }
}
assignLayersLocked()的工作原理比較繞。簡單來說,假設某個窗體在整個列表中擁有唯一的主序。則該主序就是其終于的顯示次序。假設若干個窗體擁有同樣的主序(注意經過addWindowToListInOrderLocked()函數的處理后,擁有同樣主序的窗體都是相鄰的),則第i個同樣主序的窗體的顯示次序為在主序的基礎上添加i * WINDOW_LAYER_MULTIPLIER的偏移。
經過assignLayersLocked()之后,一個擁有9個窗體的系統的現實次序的信息如表4-3所看到的。
表4- 3 窗體終于的顯示次序信息
? | 窗體1 | 窗體2 | 窗體3 | 窗體4 | 窗體5 | 窗體6 | 窗體7 | 窗體8 | 窗體9 |
主序mBaseLayer | 11000 | 11000 | 21000 | 21000 | 21000 | 21000 | 71000 | 71000 | 101000 |
子序mSubLayer | 0 | 0 | 0 | -1 | 0 | 0 | 0 | 0 | 0 |
顯示次序mLayer | 11000 | 11005 | 21000 | 21005 | 21010 | 21015 | 71000 | 71005 | 101000 |
?
在確定了終于的顯示次序mLayer后。又計算了WindowStateAnimator還有一個屬性:mAnimLayer。
例如以下所看到的:
[-->WindowManagerService.java::assignLayersLocked()]
??? finalWindowStateAnimator winAnimator = w.mWinAnimator;
??? ......
? ??if (w.mTargetAppToken != null) {
??????? // 輸入目標為Activity的輸入法窗體,其mTargetAppToken是其輸入目標所屬的AppToken
???????winAnimator.mAnimLayer =
???????????????w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment;
??? } elseif (w.mAppToken != null) {
??????? // 屬于一個Activity的窗體
???????winAnimator.mAnimLayer =
???????????????w.mLayer + w.mAppToken.mAppAnimator.animLayerAdjustment;
??? } else {
???????winAnimator.mAnimLayer = w.mLayer;
??? }
?? ?......
對于絕大多數窗體而言,其相應的WindowStateAnimator的mAnimLayer就是mLayer。而當窗體附屬為一個Activity時,mAnimLayer會加入一個來自AppWindowAnimator的矯正:animLayerAdjustment。
WindowStateAnimator和AppWindowAnimator是動畫系統中的兩員大將,它們負責渲染窗體動畫以及終于的Surface顯示次序的改動。回想一下4.1.2中的WMS的組成結構圖,WindowState屬于窗體管理體系的類。因此其所保存的mLayer的意義偏向于窗體管理。
WindowStateAnimator/AppWindowAnimator則是動畫體系的類,其mAnimLayer的意義偏向于動畫。而且由于動畫系統維護著窗體的Surface,因此mAnimLayer是Surface的實際顯示次序。
在沒有動畫的情況下,mAnimLayer與mLayer是相等的。而當窗體附屬為一個Activity時,則會依據AppTokenAnimator的須要適當地添加一個矯正值。這個矯正值來自AppTokenAnimator所使用的Animation。當Animation要求動畫對象的ZOrder必須位于其它對象之上時(Animation.getZAdjustment()的返回值為Animation.ZORDER_TOP),這個矯正是一個正數WindowManagerService.TYPE_LAYER_OFFSET(1000),這個矯正值非常大,于是窗體在動畫過程中會顯示在其它同主序的窗體之上。相反。假設要求ZOrder必須位于其它對象之下時,矯正為-WindowManagerService.TYPE_LAYER_OFFSET(-1000)。于是窗體會顯示在其它同主序的窗體之下。在動畫完結后,mAnimLayer會被又一次賦值為WindowState.mLayer。使得窗體回到其應有的位置。
動畫系統的工作原理將在4.5節具體探討。
注意矯正值為常數1000,也就出現一個隱藏的bug:當同主序的窗體的數量大于200時。APPLICATION窗體的mLayer值可能超過22000。此時,在對于mLayer值為21000的窗體應用矯正后,仍然無法保證動畫窗體位于同主序的窗體之上。只是超過200個應用窗體的情況非常少見,而且僅在動畫過程中才會出現bug,所以google貌似也懶得解決問題。
4.3.3 更新顯示次序到Surface
再回到WMS的addWindow()函數中。發現再沒有可能和顯示次序相關的代碼了。mAnimLayer是怎樣發揮自己的作用呢?不要著急,事實上。新建的窗體眼下尚無Surface。
回想一下SimpleWindow樣例,在運行session.relayout()后,WMS才為新窗體分配了一塊Surface。也就是說,僅僅有運行relayout()之后才會為新窗體的Surface設置新的顯示次序。
為了不中斷對顯示次序的調查進展。就直接開門見山地告訴大家,設置顯示次序到Surface的代碼位于WindowStateAnimator. prepareSurfaceLocked()函數中,是通過Surface.setLayer()完畢的。
在4.5節會深入為大家揭開WMS動畫子系統的面紗。
4.3.4 關于顯示次序的小結
這一節討論了窗體類型對窗體顯示次序的影響。
窗體依據自己的類型得出其主序及子序,然后addWindowToListInOrderLocked()依據主序、子序以及其所屬的Activity的順序。依照升序排列在DisplayContent的mWindows列表中。
然后assignLayersLocked()為mWindows中的全部窗體分配終于的顯示次序。之后,WMS的動畫系統將終于的顯示次序通過Surface.setLayer()設置進SurfaceFlinger。
[1]關于Choreographer。請參考鄧凡平的博客《Android Project Butter分析》(http://blog.csdn.net/innost/article/details/8272867)。
[2]讀者可閱讀《深入理解Android 卷I》第4章“深入理解Zygote”來了解和zygote相關的知識
[3]關于Wi-Fi Display的具體信息,請讀者參考http://blog.csdn.net/innost/article/details/8474683的介紹。
轉載于:https://www.cnblogs.com/wzjhoutai/p/6873790.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的《深入理解Android 卷III》第四章 深入理解WindowManagerService的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: System.arraycopy用法
- 下一篇: C++ 函数模版