<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 第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的客戶端如何申請、渲染并注銷自己的窗口。同時這也初步地反應了WMS的工作方式。 這個例子很簡單,只有三個文件: + 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 是客戶端向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/&lt;ProductName&gt;/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所示。 ![](https://box.kancloud.cn/2016-03-01_56d567b21bda8.png) 圖 4-1 SampleWindow在手機中的運行效果 #### 2.初識窗口的創建、繪制與銷毀 SampleWindow的這段代碼雖然簡單,但是卻很好地提煉了一個窗口的創建、繪制以及銷毀的過程。注意,本例沒有使用任何 WMS以外的系統服務,也沒有使用Android系統四大組件的框架,也就是說,如果你愿意,可以利用WMS實現自己的UI與應用程序框架,這樣就可以衍生出一個新的平臺了。 總結在客戶端創建一個窗口的步驟: + 獲取IWindowSession和WMS實例。客戶端可以通過IWindowSession向WMS發送請求。 + 創建并初始化WindowManager.LayoutParams。注意這里是WindowManager下的LayoutParams,它繼承自ViewGroup.LayoutParams類,并擴展了一些窗口相關的屬性。其中最重要的是type屬性。這個屬性描述了窗口的類型,而窗口類型正是WMS對多個窗口進行ZOrder排序的依據。 + 向WMS添加一個窗口令牌(WindowToken)。本章后續將分析窗口令牌的概念,目前讀者只要知道,窗口令牌描述了一個顯示行為,并且WMS要求每一個窗口必須隸屬于某一個顯示令牌。 + 向WMS添加一個窗口。必須在LayoutParams中指明此窗口所隸屬于的窗口令牌,否則在某些情況下添加操作會失敗。在SampleWindow中,不設置令牌也可成功?完成添加操作,因為窗口的類型被設為TYPE_SYSTEM_ALERT,它是系統窗口的一種。而對于系統窗口,WMS會自動為其創建顯示令牌,故無需客戶端操心。此話題將會在后文進行更具體的討論。 + 向WMS申請對窗口進行重新布局(relayout)。所謂的重新布局,就是根據窗口新的屬性去調整其Surface相關的屬性,或者重新創建一個Surface(例如窗口尺寸變化導致之前的Surface不滿足要求)。向WMS添加一個窗口之后,其僅僅是將它在WMS中進行了注冊而已。只有經過重新布局之后,窗口才擁有WMS為其分配的畫布。有了畫布,窗口之后就可以隨時進行繪制工作了。 而窗口的繪制過程如下: + 通過Surface.lock()函數獲取可以在其上作畫的Canvas實例。 + 使用Canvas實例進行作畫。 + 通過Surface.unlockCanvasAndPost()函數提交繪制結果。 **提示**關于Surface的原理與使用方法,請參考《卷 I》第8章“深入理解Surface系統”。 這是對Surface作畫的標準方法。在客戶端也可以通過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顯示在屏幕上時,就是用戶所看到的窗口了。客戶端向WMS添加一個窗口的過程,其實就是WMS為其分配一塊Surface的過程,一塊塊Surface在WMS的管理之下有序地排布在屏幕上,Android才得以呈現出多姿多彩的界面來。所以從這個意義上來講,WindowManagerService被稱之為SurfaceManagerService也說得通的。 于是,根據對Surface的操作類型可以將Android的顯示系統分為三個層次,如圖4-2所示。 ![](https://box.kancloud.cn/2016-03-01_56d567b234c79.png) 圖 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。 ![](https://box.kancloud.cn/2016-03-01_56d567b24eaed.png) 圖 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尋求窗口管理服務的客戶端。注意Session是進程唯一的。 + mRotation,只是一個int型變量。它保存了當前手機的旋轉狀態。 WMS定義的成員一定不止這些,但是它們是WMS每一種功能最核心的變量。讀者在這里可以線對它們有一個感性的認識。在本章后續的內容里將會詳細分析它們在WMS的各種工作中所發揮的核心作用。 ### 4.1.3 初識WMS的小結 這一節通過SampleWindow的例子向讀者介紹了WMS的客戶端如何使用窗口,然后通過WMS的誕生過程簡單剖析了一下WMS的重要成員組成,以期通過本節的學習能夠為后續的學習打下基礎。 從下一節開始,我們將會深入探討WMS的工作原理。 ## 4.2 WMS的窗口管理結構 經過上一節的介紹,讀者應該對WMS的窗口管理有了一個感性的認識。從這一節開將深入WMS的內部去剖析其工作流程。 根據前述內容可知,SampleWindow添加窗口的函數是IWindowSession.add()。IWindowSession是WMS與客戶端交互的一個代理,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) { ??????? // 首先檢查權限,沒有權限的客戶端不能添加窗口 ??????? 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 &gt;= FIRST_SUB_WINDOW &&.type &lt;= LAST_SUB_WINDOW) { ???????????????attachedWindow = windowForClientLocked(null, attrs.token, false); ???????????????if (attachedWindow == null) { ???????????????????return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; ???????????????} ???????????????//在這里還可以看出WMS要求窗口的層級關系最多為兩層 ???????????????if (attachedWindow.mAttrs.type &gt;= FIRST_SUB_WINDOW ??????????????????????????????? &&attachedWindow.mAttrs.type &lt;= LAST_SUB_WINDOW) { ????????? ??????????return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; ???????????????} ???????????} ??????????? booleanaddToken = false; ???????????// **②WindowToken出場!**根據客戶端的attrs.token取出已注冊的WindowToken ???????????WindowToken token = mTokenMap.get(attrs.token); ???????????// 下面的if語句塊初步揭示了WindowToken和窗口之間的關系 ??????????? if(token == null) { ???????????????// 對于以下幾種類型的窗口,必須通過LayoutParams.token成員為其指定一個已經 ???????????????// 添加至WMS的WindowToken ???????????????if (type &gt;= FIRST_APPLICATION_WINDOW ??????????????????????????????? && type&lt;= 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 &gt;= FIRST_APPLICATION_WINDOW ???????????????????????????????????????????? &&type &lt;= 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()的目的就是如此。它要求客戶端必須擁有SYSTEM_ALERT_WINDOW或INTERNAL_SYSTEM_WINDOW權限才能創建系統類型的窗口。 #### 2.向WMS聲明WindowToken 既然應用組件在創建一個窗口時必須指定一個有效的WindowToken才行,那么WindowToken究竟該如何聲明呢? 在SampleWindow應用中,使用wms.addWindowToken()函數聲明mToken作為它的令牌,所以在添加窗口時,通過設置lp.token為mToken向WMS進行出示,從而獲得WMS添加窗口的許可。這說明,只要是一個Binder對象(隨便一個),都可以作為Token向WMS進行聲明。**對于WMS的客戶端來說,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中,鍵值為客戶端用于聲明Token的Binder實例。與addWindow()函數中隱式地創建WindowToken不同,這里的WindowToken被聲明為顯式的。隱式與顯式的區別在于,當隱式創建的WindowToken的最后一個窗口被移除后,此WindowToken會被一并從mTokenMap中移除。顯式創建的WindowToken只能通過removeWindowToken()顯式地移除。 addWindowToken()這個函數告訴我們,WindowToken其實有兩層含義: + 對于顯示組件(客戶端)而言的Token,是任意一個Binder的實例,對顯示組件(客戶端)來說僅僅是一個創建窗口的令牌,沒有其他的含義。 + 對于WMS而言的WindowToken這是一個WindowToken類的實例,保存了對應于客戶端一側的Token(Binder實例),并以這個Token為鍵,存儲于mTokenMap中。客戶端一側的Token是否已被聲明,取決于其對應的WindowToken是否位于mTokenMap中。 **注意** 在一般情況下,稱顯示組件(客戶端)一側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在客戶端是否和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。 ![](https://box.kancloud.cn/2016-03-01_56d567b283919.png) 圖 4-4 WindowToken與WindowState的關系 更具體一些,以一個正在回放視頻并彈出兩個對話框的Activity為例,WindowToken與WindowState的意義如圖4-5所示。 ![](https://box.kancloud.cn/2016-03-01_56d567b2b5a72.png) 圖 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 &gt;= FIRST_SUB_WINDOW && ???????????mAttrs.type &lt;= 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將被用于視頻回放、相機預覽或游戲繪制。為了不讓這個子窗口覆蓋住所有的父窗口中承載的其他控件(如拍照按鈕,播放器控制按鈕等),它必須位于父窗口之后。 從表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&lt;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 &gt; 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](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](http://blog.csdn.net/innost/article/details/8474683)的介紹。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看