圖解Android - Android GUI 系統 (2) - 窗口管理 (View, Canvas, Window Manager)


 

Android 的窗口管理系統 (View, Canvas, WindowManager)

圖解Android - Zygote 和 System Server 啟動分析一 文里,我們已經知道Android 應用程序是怎么創建出來的,大概的流程是 ActivityManagerService -> Zygote -> Fork App, 然后應用程序在ActivityThread 中的進入loop循環等待處理來自AcitivyManagerService的消息。如果一個Android的應用有Acitivity, 那它起來后的第一件事情就是將自己顯示出來,這個過程是怎樣的? 這就是本章節要討論的話題。

 

Android 中跟窗口管理相關(不包括顯示和按鍵處理)主要有兩個進程,Acitivty所在進程 和 WndowManagerService 所在進程(SystemServer).  上圖中用不同顏色區分這兩個進程,黃色的模塊運行在Activity的進程里,綠色的模塊則在System Server內部,本文主要討論的是WindowManager Service。它們的分工是,Activity進程負責窗口內View的管理,而WindowManager Service 管理來自與不同Acitivity以及系統的的窗口。

1. Acitivty顯示前的准備工作

圖解Android - Zygote, System Server 啟動分析中我們已經知道,一個新的應用被fork完后,第一個調用的方法就是 ActivityThread的main(),這個函數主要做的事情就是創建一個ActivityThread線程,然后調用loop()開始等待。當收到來自 ActivityManager 的 LAUNCH_ACTIVITY 消息后,Activity開始了他的顯示之旅。下圖描繪的是Activity在顯示前的准備流程。


 

圖分為三部分, 右上角是Acitivity應用的初始化。中間部分是Acitivity 與WindowManager Service的交互准備工作,左下角是window顯示的開始。本文主要描述后兩部分,而Activity的啟動會放在圖解Android - Android GUI 系統 (4) - Activity的生命周期里講解。

  1. Activity內部的准備過程,這里面有一個重要對象,ContextImpl 生成,它是Context類的具體實現,它里面封裝了應用程序訪問系統資源的一些基本API, 比如說,連接某一個服務並獲取其IBinder,發送Intent, 獲取應用程序的信息,訪問數據庫等等,在應用看來,它就是整個AndroidSDK的入口。ContextImpl 除了實現函數,里面還維護成員變量,其中有一個mDisplay,代表當前應用輸出的顯示設備,如果應用沒有特別指定,一般指向系統的默認顯示輸出,比如手機的液晶屏。
  2. 圖解Android - Android GUI 系統 (1) - 概論中我們已經介紹過ViewRootImpl 的地位相當與MVC架構中的C,Controller是連接View和Modal的關鍵,所以需要首先創建它。當addView(view, param)被調用的時候,一個ViewRoot就被創建出來,addView()的實現如下:
    public void addView(View view, ViewGroup.LayoutParams params) {
            mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow){
           ...
           root = new ViewRootImpl(view.getContext(), display);
           ...
    }

    這里的參數View是想要添加到WindowManagerService 的“window", 一般一個Activity只需要一個’Window', 所以,Acitivy的默認實現是將DecorView作為”Window" 交給Window Manager Service 進行管理。Params是Layout相關的參數,里面包含有長,寬,邊緣尺寸(Margin)等信息,mDisplay就是這個窗口想要輸出的Display設備編號,由ContextImpl傳遞過來。mParentWindow 就是Activity的成員變量mWindow,從最上面的類圖可以很容易看出來,對於手機而言,就是一個PhoneWindow對象,對於GoogleTV,就是TVWindow對象。

  3. ViewRootImpl 在構造過程成初始化一些重要的成員變量,包括一個Surface對象(注意這是一個空的Surface對象,沒有賦給任何有效的值,后面會通過CopyFromParcel來填充),還有mChoreophaer定時器(Singleton對象,每個進程只有一個),與此同時,ViewRootImp通過WindowManagerGlobal創建了一個和WindowManagerService 的通話通道,接下來會利用這條通道做進一步的初始化工作。
  4. 還是在addView()里,WindowManagerImpl拿到ViewRoot對象后調用它的setView方法,將view, layout參數交給ViewRootImpl開始接管。在setView()里ViewRootImpl做進一步的初始化工作,包括創建一個InputChannel接收用戶按鍵輸入,enable圖形硬件加速,請求第一次的Layout等等,這里只介紹跟WindowManagerService 有關系的一件事,就是向WindowManager service 報道,加入到WindowManager的窗口管理隊列中。這個函數是 addToDisplay(),
    int addToDisplay(in IWindow window,  //提供給WMS的回調接口
         in int seq, 
         in windowManager.LayoutParams attrs,  // layout參數
         in int viewVisibility, in int layerStackId,     // display ID
         out Rect outContentInsets,                      // WMS計算后返回這個View在顯示屏上的位置
         out InputChannel outInputChannel);       // 用戶輸入通道Handle

    addToDisplay() 最終會調到WindowManager Service的addWindow() 接口。

  5. addWindow() 里首先生成了一個WindowState對象,它是ViewRootImpl 在WindowManager Service端的代表。在它的構造函數里,WindowState 會生成IWindowId.Stub 對象和DeathRecipient對象來分別監聽Focus和窗口死亡的信息,根據用戶傳進來的Window Type計算出窗口的mBaseLayer,mSubLayer和mLastLayer值,分別對應於主窗口,主窗口上彈出的子窗口(如輸入法),以及動畫時分別對應的ZOrder值,(在本文后面會具體介紹),生成一個WindowStateAnimation 負責整個Window的動畫,並在內部將windowToken, appWindowToken等關聯起來。
  6. WindowManager Service 調用openInputChannelPair() and RegisterInputChannel(), 創建用於通信的SocketPair , 將其傳給InputManagerService, 用於接下來的用戶輸入事件對應的響應窗口(參考Android的用戶輸入處理),
  7. 最后,WindowManagerService 調用WindowState的attach(),創建了一個Surface Session 並將Surface Session,WindowSession 還有WindowState 三者關聯起來.
  8. WindowManager Service 調用 assignLayersLocked()計算所有Window的Z-Order。
  9. addToDisplay() 返回,ViewRootImpl 和 WindowManager Service 內部的准備工作就緒。ActivityThread會發送ACTIVITY_RESUMED消息告訴Activity顯示開始。可以是圖還沒有畫,不是嗎?對的,此刻Surface還沒有真正初始化(我們前面說過ViewRootImpl只是New了一個空的對象,需要有人往里面填東西)。底層存放繪制結果的Buffer也沒有創建,但是最多16ms以后這一切就會開始。

2. Choreographer 和 Surface的創建

所有的圖像顯示輸出都是由時鍾驅動的,這個驅動信號稱為VSYNC。這個名詞來源於模擬電視時代,在那個年代,因為帶寬的限制,每一幀圖像都有分成兩次傳輸,先掃描偶數行(也稱偶場)傳輸,再回到頭部掃描奇數行(奇場),掃描之前,發送一個VSYNC同步信號,用於標識這個這是一場的開始。場頻,也就是VSYNC 頻率決定了幀率(場頻/2). 在現在的數字傳輸中,已經沒有了場的概念,但VSYNC這一概念得於保持下來,代表了圖像的刷新頻率,意味着收到VSYNC信號后,我們必須將新的一幀進行顯示。

VSYNC一般由硬件產生,也可以由軟件產生(如果夠准確的話),Android 中VSYNC來着於HWComposer,接收者沒錯,就是Choreographer。Choreographer英文意思是編舞者,跳舞很講究節奏不是嗎,必須要踩准點。Choreographer 就是用來幫助Android的動畫,輸入,還是顯示刷新按照固定節奏來完成工作的。看看Chroreographer 和周邊的類結構。

 

 

從圖中我們可以看到, Choreographer 是ViewRootImpl 創建的(Choreographer是一個sigleton類,第一個訪問它的ViewRootImpl創建它),它擁有一個Receiver, 用來接收外部傳入的Event,它還有一個Callback Queue, 里面存放着若干個CallbackRecord, 還有一個FrameHandler,用來handleMessage, 最后,它還跟Looper有引用關系。再看看下面這張時序圖,一切就清楚了,

 

 

首先Looper調用loop() 后,線程進入進入睡眠,直到收到一個消息。Looper也支持addFd()方法,這樣如果某個fd上發生了IO操作(read/write), 它也會從睡眠中醒來。Choreographer的實現用到了這兩種方式,首先他通過某種方式獲取到SurfaceFlinger 進程提供的fd,然后將其交給Looper進行監聽,只要SurfaceFlinger往這個fd寫入VSync事件,looper便會喚醒。Lopper喚醒后,會執行onVsync()時間,這里面沒有做太多事情,而是調用Handler接口 sendMessageAtTime() 往消息隊列里又送了一個消息。這個消息最終調到了Handler (實際是FrameHandler)的handleCallback來完成上層安排的工作。為什么要繞這么大個圈?為什么不在onVSync里直接handleCallback()? 畢竟onVSync 和 handleCallback() 都在一個線程里。這是因為MessageQueue 不光接收來自SurfaceFlinger 的VSync 事件,還有來自上層的控制消息。VSync的處理是相當頻繁的,如果不將VSync信號送人MessageQueue進行排隊,MessageQueue里的事件就有可能得不到及時處理,嚴重的話會導致溢出。當然了,如果因為VSync信號排隊而導致處理延遲,這就是設計的問題了,這也是為什么Android文檔里反復強調在Activity的onXXX()里不要做太耗時的工作,因為這些回調函數和Choreographer運行在同一個線程里,這個線程就是所謂的UI線程。

言歸正傳,繼續往前,VSync事件最終在doFrame()里調了三次doCallbacks()來完成不同的功能, 分別處理用戶輸入事件,動畫刷新(動畫就是定時更新的圖片), 最后執行performTraversals(),這個函數里面主要是檢查當前窗口當前狀態,比如說是否依然可見,尺寸,方向,布局是否發生改變(可能是由前面的用戶輸入觸發的),分別調用performMeasure(), performLayout, performDraw()完成測量,布局和繪制工作。我們會在后面詳細學習這三個函數,這里我們主要看一下第一次進入performTraversals的情況,因為第一次會做些初始化的工作,最重要的一件就是如本節標題,創建Surface對象。

回看圖2,我們可以看到Surface的創建不是在Activity進程里,而是在WindowManagerService完成的(粉顏色)。當一個Activity第一次顯示的時候,Android顯示切換動畫,因此Surface是在動畫的准備過程中創建的,具體發生在類WindowStateAnimator的createSurfaced()函數。它最終創建了一個SurfaceControl 對象。SurfaceControl是Android 4.3 里新引進的類,Google從之前的Surface類里拆出部分接口,變成SurfaceControl,為什么要這樣? 為了讓結構更清晰,WindowManagerService 只能對Surface進行控制,但並不更新Surface里的內容,分拆之后,WindowManagerService 只能訪問SurfaceControl,它主要控制Surface的創建,銷毀,Z-order,透明度,顯示或隱藏,等等。而真正的更新者,View會通過Canvas的接口將內容畫到Surface上。那View怎么拿到WMService創建的Surface,答案是下面的代碼里,surfaceControl 被轉換成一個Surface對象,然后傳回給ViewRoot, 前面創建的空的Surface現在有了實質內容。Surface通過這種方式被創建出來,Surface對應的Buffer 也相應的在SurfaceFlinger內部通過HAL層模塊(GRAlloc)分配並維護在SurfaceFlinger 內部,Canvas() 通過dequeueBuffer()接口拿到Surface的一個Buffer,繪制完成后通過queueBuffer()還給SurfaceFlinger進行繪制。

                    SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
                    if (surfaceControl != null) {
                        outSurface.copyFrom(surfaceControl);
                        if (SHOW_TRANSACTIONS) Slog.i(TAG,
                                "  OUT SURFACE " + outSurface + ": copied");
                    } else {
                        outSurface.release();
                    }

到這里,我們知道了Activity的三大工作,用戶輸入響應,動畫,和繪制都是由一個定時器驅動的,Surface在Activity第一次啟動時由WindowManager Service創建。接下來我們具體看一下View是如何畫在Surface Buffer上的,而Surface Buffer的顯示則交由圖解Android - Android GUI 系統 (3) - Surface Flinger 來討論。

3. View的Measure, Layout 和 Draw

直接從前面提到的performMeasure()函數開始.

 因為遞歸調用,實際的函數調用棧比這里顯示的深得很多,這個函數會從view的結構樹頂(DecorView), 一直遍歷到葉節點。中間會經過三個基類,DecorView, ViewGroup 和 View, 它們的類結構如下圖所示:

 

所有可見的View(不包括DecorView 和 ViewGroup)都是一個矩形,Measure的目的就是算出這個矩形的尺寸, mMeasuredWidth 和 mMeasuredHeight (注意,這不是最終在屏幕上顯示的尺寸),這兩個尺寸的計算受其父View的尺寸和類型限制,這些信息存放在 MeasureSpec里。MeasureSpec 里定義了三種constraints,

  •         /*父View對子View尺寸沒有任何要求,其可以設任意尺寸*/
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
            /* 父View為子View已經指定了大小*/
            public static final int EXACTLY     = 1 << MODE_SHIFT;
    
            /*父View沒有指定子View大小,但其不能超過父View的邊界 */
            public static final int AT_MOST     = 2 << MODE_SHIFT;

widthMeasureSpec 和 heightMeasureSpec 作為 onMeasure的參數出入,子View根據這兩個值計算出自己的尺寸,最終調用 setMeasuredDimension() 更新mMeasuredWidth 和 mMeasuredHeight.

performMeasure() 結束后,所有的View都更新了自己的尺寸,接下來進入performLayout().

performLayout() 的流程和performMeasure基本上一樣,可以將上面圖中的measure() 和 onMeasure 簡單的換成 layout() 和 onLayout(), 也是遍歷整課View樹,根據之前算出的大小將每個View的位置信息計算出來。這里不做太多描述,我們把重心放到performDraw(), 因為這塊最復雜,也是最為重要的一塊。

很早就玩過Android手機的同學應該能體會到Android2.3 到 Android 4.0 (其實Android3.0就有了,只是這個版本只在平板上有)的性能的巨大提升,UI界面的滑動效果一下變得順滑很多,到底是framework的什么改動帶來的?我們馬上揭曉。。。(這塊非本人工作領域,網上相關的資料也很少,所以純憑個人磚研,歡迎拍磚指正)

Android Graphics Hardware Acceleration

OK, 如標題所述,最根本的原因就是引入了硬件加速, GPU是專門優化圖形繪制的硬件單元,很多GPU(至少手機上的)都支持OpenGL,一種開放的跨平台的3D繪圖API。Android3.0以前,幾乎所有的圖形繪制都是由Skia完成,Skia是一個向量繪圖庫,使用CPU來進行運算, 所以它的performance是一個問題(當然,Skia也可以用GPU進行加速,有人在研究,但好像GPU對向量繪圖的提升不像對Opengl那么明顯),所以從Android3.0 開始,Google用hwui取代了Skia,准確的說,是推薦取代,因為Opengl的支持不完全,有少量圖形api仍由Skia完成,另外還要考慮到兼容性,硬件加速的功能並不是默認打開,需要程序在AndroidManifests.xml 或代碼里控制開關。當然,大部分Canvas的基本操作都通過hwui重寫了,hwui下面就是Opengl和后面的GPU,這也是為什么Android 4.0的launcher變得異常流暢的緣故。OK,那我們接下來的重點就是要分析HWUI的實現了。

在此之前,簡單的介紹一下OpenGL的一些概念,否則很難理解。要想深入理解Opengl,請必讀經典的紅包書:http://www.glprogramming.com/red/

Opengl說白了,就是一組圖形繪制的API。 這些API都是一些非常基本的命令,通過它,你可以構造出非常復雜的圖形和動畫,同時,它又是跟硬件細節無關的,所以無需改動就可以運行在不同的硬件平台上(前提是硬件支持所需特性)。OpenGL的輸入是最基本幾何元素(geometric primitives), 點(points), 線(lines), 多邊形(polygons), 以及bitmap和pixle data, 他的輸出是一個或兩個Framebuffer(真3D立體). 輸入到輸出的流程(rendering pipeline)如下圖所示:

 這里有太多的概念,我們只描述跟本文相關的幾個:

 vertex data
     
所有的幾何元素(點線面)都可以用點(vertics)來描述, 每個點都對應三維空間中的一個坐標(x,y,z), 如下圖所示,改變若干點的位置,我們便可以構造出一個立體的圖形。    

Triangles

     OpenGL只能畫非凹(nonconvex)的多邊形,可是現實世界中存在太多的凹性的物體,怎么辦呢?通過連線可以將凹的物體分成若干個三角形,三角形永遠都是凸(convex)的。同時三角形還有一個特性,三個點可以唯一確定一個平面,所以用盡可能多的三角形就可以逼近現實世界中復雜的曲線表面,比如下圖的例子,三角形的數目越多,球體的表示就越逼真。這也是為什么我們經常看到顯卡的性能評測都以三角形的生成和處理作為一個非常重要的指標。

 Display List

      所有的Vertex和Pixel信息均可以存在Display List 里面,用於后續處理,換句話說,Display List 就是OpenGL命令的緩存。Display List的使用對OpenGL的性能提升有很大幫助。這個很容易理解,想象一個復雜的物體,需要大量的OpenGL命令來描繪,如果畫一次都需要重新調用OpenGL API,並把它轉換成Vertex data,顯然是很低效的,如果把他們緩存在Display List里,需要重繪的時候,發一個個命令通知OpenGL直接從Display List 讀取緩存的Vertex Data,那勢必會快很多,如果考慮到Opengl是基於C/S架構,可以支持遠程Client,這個提升就更大了。Display也可以緩存BitMap 或 Image, 舉個例子,假設要顯示一篇文章,里面有很多重復的字符,如果每個字符都去字庫讀取它的位圖,然后告訴Opengl去畫,那顯然是很慢的。但如果將整個字庫放到Display List里,顯示字符時候只需要告訴Opengl這個字符的偏移量,OpenGL直接訪問Display List,那就高效多了。

Pixel Data

      Pixle data 包括位圖(bitmap), Image, 和任何用於繪制的Pixel數據(比如Fonts)。通常是以矩陣的形式存放在內存當中。通過Pxiel data, 我們避免大量的圖形繪制命令。同時通過現實世界中獲取的紋理圖片,可以將最終的物體渲染得更逼真。比如說畫一堵牆,如果沒有pixel data,我們需要將每塊磚頭都畫出來,也就是說需要大量的Vertex。可是如果通過一張現實生活中拍攝的磚牆的圖片,只需要4個點畫出一個大矩形,然后上面貼上紋理,顯然,速度和效果都要好得多。

FrameBuffer

      Framebuffer就是Opengl用來存儲結果的buffer。Opengl的frameBuffer類型有幾種。Front Buffer 和 Back Buffer, 分別用於顯示和繪制,兩者通過swapBuffer 進行交換。Left Buffer 和 Right buffer, 用於真立體(需要帶眼鏡的那種) 圖像的左右眼Buffer,Stencil buffer, 用於禁止在某些區域上進行繪制,想像一下如果在一件T恤上印上圖案,你是不是需要一個鏤空的紙板?這個紙板就是stencil buffer.

OK, 對Opengl rendering pipeline簡單介紹到此,有興趣的同學可以閱讀opengl的紅包書或運行一些簡單的例子來深入理解Opengl。回到主題,僅僅使用Opengl 和 GPU 取代Skia 就能夠大幅提升性能?答案當然不是,性能的優化很大程度上取決於應用,應用必須正確的使用Opengl命令才能發揮其最大效能。Android從pipeline 角度提供了兩種機制來提升性能,一個就是我們剛才說到的Display List,另一個叫 Hardware Layer, 其實就是緩存的FrameBuffer, 比如說Android的牆紙,一般來說,他是不會發生變化的,因此我們可以將它緩存在Hardware Layer里,這張就不需要每次進行拷貝和重繪,從而大幅提升性能。

說白了,優化圖形性能的核心在於 1)用硬件來減少CPU的參與,加速圖形計算。 2)從軟件角度,通過Display List 和 Hardware Layer, 將已經完成的工作盡可能的緩存起來,只做必須要做的事情,盡可能的減少運算量。

接下來看實現吧。

Canvas, Renderer, DisplayList, HardwareLayer 實現

這塊代碼相當的復雜,花了兩天時間才把下面的圖整理出來,但還是沒有把細節完全吃透,簡單的介紹一下框架和流程吧,如果有需要大家可以下來細看代碼。  

 
 

 

圖中上半部為Java 代碼,下半部為Native層。先介紹里面出現的一些概念:

 Canvas

Canvas是Java層獨有的概念,它為View提供了大部分圖形繪制的接口。這個類主要用於純軟件的繪制,硬件加速的圖形繪制則由HardwareCanvas取代。

HardwareCanvas,GLES20Canvas, GLES20RecordingCanvas

hardwareCanvas是一個抽象類,如果系統屬性和應用程序指定使用硬件加速(現已成為默認),它將會被View(通過AttachInfo,如 下圖所示) 引用來完成所有的圖形繪制工作。GLES20Canvas 則是hardwareCanvas的實現,但它也只是一層封裝而已,真正的實現在Native 層,通過jni (andriod_view_gles20Canvas.cpp)接口來訪問底層的Renderer, 進而執行OpenGL的命令。 此外,GLES20Canvas還提供了一些靜態接口,用於創建各類Renderer對象。

GLES20RecordingCanvas 繼承GLES20Canvas, 通過它調用的OpenGL命令將會存儲在DisplayList里面,而不會立即執行。

HardwareRenderer, GLRender, GL20Renderer

這三個類都是Java的Wrapper類,通過訪問各種Canvas來控制繪制流程。詳見下面兩張時序圖。

OpenGLRenderer, DisplayListRenderer, HardwareLayerRenderer

Java的HardwareCanvas 和 HardwareRenderer在底層的對應實現。OpenGLRenderer是基類,只有它直接訪問底層OpenGL庫。DisplayListRenderer 將View通過GLES20Canvas傳過來的OpenGL 命令存在OpenGL的DisplayList中。而HardwareLayerRenderer 管理HardwareLayer的資源。

GLES20
      就是OpenGL ES 2.0 的API。它的實現一般由GPU的設計廠家提供,可以在設備的/system/lib/egl/ 找到它的so,名字為 libGLES_xxx.so, xxx 就是特定設備的代號,比如說,libGLES_gc.so 就是Vivante公司的GPU實現,libGLESv2_mali.so 就是ARM公司提供的Mali GPU的實現。它也可以由軟件實現,比如說 libGLES_android.so, 是Google提供的軟件實現。

EGL
      雖然對於上層應用來說OpenGL接口是跨平台的,但是它的底層(GPU)實現和平台(SoC)是緊密相關的,於是OpenGL組織定義一套接口用來訪問平台本地的窗口系統(native platform window system),這套接口就是EGL,比如說 eglCreateDisplay(), eglCreateSurface(), eglSwapBuffer()等等。EGL的實現一般明白libEGL.so, 放在/system/lib/egl/ 下面。

View, Canvas, Renderer, DisplayList, HardwareLayer 的關系如下圖所示:

每個View都對應一個DisplayList, 在Native層代碼里管理。每個View通過GLESRecordingCanvas 以及Native層對應的DisplayRenderer 將OpenGL命令存入DisplayList.最后View 通過GLES20Canvas 通知OpenGLRenderer 執行這些DisplayList 里面的OpenGL 命令。

 

 他們的生命周期如下圖所示 (粉紅代表 New, 黑色代表 Delete, 黃色代表Java類,藍色代表C++, 綠色代表JNI).

 

  1. 如果系統支持硬件加速,ViewRootImpl首先創建一個GL20Renderer, 存在成員變量 mHardwareRenderer 里。
  2. Surafce是繪畫的基礎,如果不存在,HardwareRenderer會調用GLRenderer->createEglSurface() 創建一個新的Surface。
  3. Surface創建好后,接着生成Canvas,因為大部分應用程序不會直接操作Surface。
  4. 在Canvas的構造函數里,會依次創建Native層對應的OpenGLRenderer對象,它會直接訪問庫 libGLES_xxx提供的OpenGL ES API,來負責整個View樹的繪制工作。
  5. 與此同時,一個CanvasFinalizer 成員對象也會被創建,它保存剛剛創建出來的OpenGLRenderer 指針,當它的finalizer()函數被調用是,負責將其銷毀,回收資源。
  6. 在運行過程中,如果窗口死掉或者不在可見,ViewRootImpl 會調用DestroyHardwareResource() 來釋放資源。這里會最終將底層創建的Hardware Layer回收。
  7. 同時Java端的GLES20Layer 對象也會因為被賦值 NULL 被GC在將來回收。
  8. 接下來,ViewRootImpl調用 destroyHardwareRenderer() 將之前創建的Native Renderer(DisplayListRenderer,OpenGLRenderer)依次回收。
  9. 最后將Java 層的mHardwareRenderer 賦空,GC將會回收最開始創建的GL20Renderer對象。支持,一個View樹的生命周期完成,所有資源清楚干凈。

等等!好像少了點什么,怎么沒有DisplayList? 前面不是說它是性能優化的幫手之一嗎?對了,上面只介紹了繪制的開始和結尾,在View的生命周期中,還有最重要的一步,Draw 還沒有被介紹,DisplayList 相關的操作就是在Draw()里面完成的。

 Draw 流程

  繞了好大一圈,終於回到最初的話題,Android是怎樣將View畫出來的? 讓我們按照圖中的序號一一進行講解。(黃色:Java, 綠色:C++,藍色:JNI,粉色:New, 黑色:Delete).

 

  1. 首先,ViewRootImpl直接訪問的HardwareRenderer 對象,首先在BeginFrame() 里獲取EGLDisplay(用於顯示) 和 初始化一個EGLSurface,OpenGL將在這個Surface上進行繪圖。關於EGLDisplay 和 EGLSurface 將在 圖解Android - Android GUI 系統 (3) - Surface Flinger 里詳細描述。
  2. 我們前面說過,Android 4.0 帶來的圖形性能的提升,有很大程度是DisplayList 帶來的,所以,HardwareRenderer接下來就通過buildDisplayList() 創建整個View樹的DisplayList. 最開始,GL20Renderer為View生成了一個GLES20DisplayList, 這是一個影子類,沒有實際用途,主要用來管理Native層的DisplayList的銷毀。
  3. View 調用剛剛生成的GLES20DisplayList的 start() 方法,真正開始構建DisplayList的上下文。obtain() 函數里會判斷是否mCanvas 已經存在,如果沒有則New 一個新的GLES20RecordingCanvas對象。
  4. 創建與GLES20RecordingCanvas 一一對應的Native層的 DisplayListRenderer。
  5. 3,4是一個遞歸的過程,直到所有的View的DisplayList上下文生成,View才真正開始Draw(), 這里,OpenGL命令(Op)不會被立即執行,而是被存儲到剛剛生成的DisplayListRenderer里。
  6. 接着View調用GLESDisplayList的 end() 方法,這里Native層的DisplayList 對象才真正被創建出來,Java 和 Native 的 DisplayList 對象一一對應起來。
  7. end() 方法最后,調用GLES20RecordingCanvas.recycle() 方法,首先將Native的DisplayListRender 進行重新初始化,然后將剛才創建出來的臨時對象賦NULL值(GLES20RecordingCanvas 和 GLES20DisplayList), 因為它們的使命已經完成,將View的OpenGL命令存儲在Native層的DisplayList對象里。
  8. GC() 被調用后,GLES20RecordingCanvas 和 GLES20DisplayList 被釋放,相應的Finalizer 對象方法會被調用,進而調用Natice層的deleteDisplayListDefered(), 將之前不用的DisplayList 送入 Caches的Gabage 隊列中,等待回收。(這是在Native層實現的類似Java GC的機制)
  9. 到此,所有的DisplayList 上下文准備就緒。進入preDraw 狀態,在GL20Renderer的 onPreDraw() 方法里,最終調用到底層的clearGarbage將上一次繪圖操作的DisplayList在底層釋放。
  10. HardwareRenderer調用 GLES20Canvas 的drawDisplayList(), 通知 Native層的 OpenGLRenderer,其最終調用每個DisplayList的Replay() 方法執行OpenGL命令。
  11. 所有OpenGL命令執行完后, 圖形被繪制到第一步生成的EGLSurface, hardwareRender 調用 GLES20Canvas 的 eglSwapBuffers() 交換Buffer,交由SurfaceFlinger 在下一個VSync到來時進行顯示。

Hardware Layer

即便是使用了DisplayList, 對於復雜的圖形,仍然需要執行大量的OpenGL命令,如果需要對這一部分進行優化,就需要使用到 HardwareLayer對繪制的圖形進行緩存,如果圖形不發生任何變化,就不需要執行任何OpenGL命令,而是將之前緩存在GPU內存的Buffer 直接與其他View進行合成,從而大大的提高性能。這些存儲在GPU內部的Buffer就稱為 Hardware Layer。除了Hardware Layer, Android 還支持Software Layer,和Hardware Layer 不同之處在於,它不存在於GPU內部,而是存在CPU的內存里,因此它不經過前面所說的 Hardware Render Pipeline, 而是走Android最初的軟件Render pipeline。不管是Hardware Layer 還是 Software Layer, 在Draw() 內部均稱為Cache,只要有Cache的存在,相對應的View將不用重繪,而是使用已有的Cache。Hardware Layer, Software Layer 和 Display List 是互斥的,同時只能有一種方法生效(當然,Hardware Layer的第一次繪制還是通過Display List 完成),下表總結了它們的差別, 從中可以看到,Hardware Layer 對性能的提升是最大的,唯一的問題是占用GPU的內存(這也是為什么顯卡的內存變得越來越大的原因之一),所以一般來說,Hardware Layer使用在那些圖片較為復雜,但不經常改變,有動畫操作或與其他窗口有合成的場景,比如說WallPaper, Animation 等等。

 

  Enabled if GPU accelerated? Cached in

Performance(from Google I/O 2001)

Usage
Display List Hardware Accelerated = True Y GPU DisplayList 2.1 Complex View
Hardware Layer LayerType = HARDWARE Y GPU Memory 0.009 Complex View, Color Filter(顏色過濾), Alpha blending (透明度設置), etc. 
Software Layer LayerType = SOFTWARE N CPU Memory 10.3 No Hardware Accelerated, Color filter, Alpha blending, etc.

 

 

 

 

 

 

 

重繪 - Invaliate

前面介紹了View的第一次繪制的過程。但是一個View在運行中終究是要發生變化的,比如說,用戶在TextView的文字發生了改變,或者動畫導致View的尺寸發生變化,再或者說一個對話框彈出然后又消失,被遮擋的部分重新露了出來,這些都需要對View進行重繪。在介紹重繪之前,我們先了解一下View內部的一些Flag 定義。

 

Flags  == 1 Set at Clear at
PFFLAG_HAS_BOUNDS

1: View has size and Position set.
0: setFrame() was not called yet

View::setFrame()  
PFFLAG_DRAWN

1: Has been drawn, only after which, invalidate() is valid.
0: not drawn, need to draw() later to show it.

ViewGroup::addViewInLayout
ViewGroup::attachViewToParent
View::draw()
View::buildDrawingCache()
View::getHardwareLayer()
View::invalidate()
View::setLeft/Right/Top/Bottom()

View::invalidate()

PFFLAG_DRAWING_CACHE_VALID

1: Has DisplayList / Hardware Layer / Software Layer  
0: No Cache, using Software rendering path.

View::GetHardwareLayer()
View::GetDisplayList()

View::invalidate(true)
View::invalidate(rect)
View::invalidateChild()

PFFLAG_INVALIDATED   View is specifically invalidated, not just dirty(child for instance).
DisplayList will be recreated if set. 
ViewGroup::addViewInLayout
ViewGroup::attachViewToParent
View::force/requestLayout()
View::invalidate()

View::draw()
PFLAG_DIRTY   Set to indicate that the view need to be redrawn.
(use displaylist cache if PFFLAG_INVALIDATED flag is false)
View::invalidate()

ViewGroup::addViewInLayout
ViewGroup::attachViewToParent
View::draw()
View::buildDrawingCache()
View::getHardwareLayer()
View::invalidate()
View::setLeft/Right/Top/Bottom()
View::getDisplayList()

PFLAG_DIRTY_OPAQUE DIRTY because of OPAQUE (hidden by others). View::invalidateChild()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

從上表可以看出,View通過內部這些Flag來控制重繪。基本上重繪分兩種情況,一種是需要重新生成DisplayList, 另外一種是使用之前已有的Cache,包括DisplayList或者是Hardware Layer。使用哪種重繪方式由當前View的Flags,以及應用程序傳入的參數決定。控制它的就是一組Invalidate() 函數。

 

void invalidate(boolean invalidateCache) {
        if (skipInvalidate()) { //如果View不可見,並且不再動畫退出過程中(fade out),將不執行Invalidate().
            return;
        }
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || //DRAWN -> 已經被Draw()過
            (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||  //有Cache,且被要求重新刷新Cache
            (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) //沒有正在Invalidate()中
        {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
            mPrivateFlags |= PFLAG_DIRTY;
            if (invalidateCache) { 
                mPrivateFlags |= PFLAG_INVALIDATED; 
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; //標記將來清除Cache,如果為false,則有系統根據Dirty Region決定是否需要重新生成DisplayList。
            }
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (!HardwareRenderer.RENDER_DIRTY_REGIONS) { //系統不支持Dirty Region,必須重繪整個區域, 基本不會進去
            }
            if (p != null && ai != null) {
                final Rect r = ai.mTmpInvalRect;
                r.set(0, 0, mRight - mLeft, mBottom - mTop);
                p.invalidateChild(this, r); //通知兄弟view(有共同的ViewParent(ViewGroup 或者 ViewRoot)進行 Invalidate. 
            }
        }
    }

假如所有的條件都支持重繪,便會調用到ViewParent的invalidateChild()方法。(ViewParent是一個接口類,它的實現類是ViewGroup 和 ViewRootImpl。)這個方法會從當前View開始,向上遍歷到ViewRoot 或者 到某個ViewGroup的區域與當前View的Dirty區域沒有重疊為止。途中的每個ViewGroup都會被標記上Dirty。在接下來VSYNC的performDraw()里,ViewRootImpl 會遍歷所有標記Dirty的ViewGroup,然后找到里面標記Dirty的View,只有這些View的DisplayList 被重建,而其他實際上沒有變化的View(雖然它們在同一個ViewGroup里面),如果沒有Hardware Layer, 只需重新執行對應Display List 里面的OpenGL 命令。通過這種方式,Android只重繪需要重繪的View,從軟件層面將GPU的輸入最小化,從而優化圖形性能。

 

4. Windows 的管理

到此,我們已經了解了一個Acitivty(Window)是如何畫出來的,讓我們在簡要重溫一下這個過程:

  1. Acitivity創建, ViewRootImpl將窗口注冊到WindowManager Service,WindowManager Service 通過SurfaceFlinger 的接口創建了一個Surface Session用於接下來的Surface 管理工作。
  2. 由Surface Flinger 傳上來的VSYNC事件到來,Choreographer 會運行ViewRootImpl 注冊的Callback函數, 這個函數會最終調用 performTraversal 遍歷View樹里的每個View, 在第一個VSYNC里,WindowManager Service 會創建一個SurafceControll 對象,ViewRootImpl 根據Parcel返回的該對象生成了Window對應的Surface對象,通過這個對象,Canvas 可以要求Sruface Flinger 分配OpenGL繪圖用的Buffer。
  3. View樹里的每個View 會根據需要依次執行 measure(),layout() 和 draw() 操作。Android 在3.0之后引入了硬件加速機制,為每個View生成DisplayList,並根據需要在GPU內部生成Hardware Layer,從而充分利用GPU的功能提升圖形繪制速度。
  4. 當某個View發生變化,它會調用invalidate() 請求重繪,這個函數從當前View 出發,向上遍歷找到View Tree中所有Dirty的 View 和 ViewGroup, 根據需要重新生成DisplayList, 並在drawDisplayList() 函數里執行OpenGL命令將其繪制在某個Surface Buffer上。
  5. 最后,ViewRootImpl 調用 eglSwapBuffer 通知OpenGL 將繪制的Buffer 在下一個VSync點進行顯示。

注意的是,上面討論的只是一個窗口的流程,而Android是個多窗口的系統,窗口之間可能會有重疊,窗口切換會有動畫產生,窗口的顯示和隱藏都有可能會導致資源的分配和釋放,這一切需要有一個全局的服務進行統一的管理,這個服務就是我們大名鼎鼎的Window Manager Service (簡寫 WMS).

其實Window Manager Service 的工作不僅僅是管理窗口,還會跟很多其他服務打交道,如 InputManager Service, AcitivityManager Service 等等,但本章只討論它在Window Manager 方面的工作,下圖中紅色標記部分。

 Layout

 首先來看Layout。Layout 是Window Manager Service 重要工作之一,它的流程如下圖所示:

 

  • 每個View將期望窗口尺寸交給WMS(WindowManager Service).
  • WMS 將所有的窗口大小以及當前的Overscan區域傳給WPM (WindowPolicy Manager).
  • WPM根據用戶配置確定每個Window在最終Display輸出上的位置以及需要分配的Surface大小。
  • 返回這些信息給每個View,他們將在給會的區域空間里繪圖。

Android里定義了很多區域,如下圖所示 

                                          

Overscan:
    Overscan 是電視特有的概念,上圖中黃色部分就是Overscan區域,指的是電視機屏幕四周某些不可見的區域(因為電視特性,這部分區域的buffer內容顯示時被丟棄),也意味着如果窗口的某些內容畫在這個區域里,它在某些電視上就會看不到。為了避免這種情況發生,通常要求UI不要畫在屏幕的邊角上,而是預留一定的空間。因為Overscan的區域大小隨着電視不 同而不同,它一般由終端用戶通過UI指定,(比如說GoogleTV里就有確定Overscan大小的應用)。

OverscanScreen, Screen: 
   
OverscanScreen 是包含Overscan區域的屏幕大小,而Screen則為去除Overscan區域后的屏幕區域, OverscanScreen > Screen.

Restricted and Unrestricted:
    某些區域是被系統保留的,比如說手機屏幕上方的狀態欄(如圖紙綠色區域)和下方的導航欄,根據是否包括這些預留的區域,Android把區域分為Unrestricted Area 和 Resctrited Aread, 前者包括這部分預留區域,后者則不包含, Unrestricted area > Rectricted area。

mFrame, mDisplayFrame, mContainingFrame
   
Frame指的是一片內存區域, 對應於屏幕上的一塊矩形區域. mFrame的大小就是Surface的大小, 如上上圖中的藍色區域. mDisplayFrame 和 mContainingFrame 一般和mFrame 大小一致. mXXX 是Window(ViewRootImpl, Windowstate) 里面定義的成員變量.

mContentFrame, mVisibleFrame
   
一個Surface的所有內容不一定在屏幕上都得到顯示, 與Overscan重疊的部分會被截掉, 系統的其他窗口也會遮擋掉部分區域 (比如短信窗口,ContentFrame是800x600(沒有Status Bar), 但當輸入法窗口彈出是,變成了800x352), 剩下的區域稱為Visible Frame, UI內容只有畫在這個區域里才能確保可見. 所以也稱為Content Frame. mXXX也是Window(ViewRootImpl, WindowState) 里面定義的成員變量.

Insects
   
insets的定義如上圖所示, 用了表示某個Frame的邊緣大小.

Layout 在WMS 內部的時序如下圖所示,外部調整Overscan參數或View內部主動調用requestLayout() 都會觸發WMS的重新layout,layout完成后,WMS會通過IWindow的resized()接口通知ViewRoot, 最終會調用requestLayout(), 並在下一個VSYNC 事件到來時更新。

 計算Layout主要有圖中三個紅色的函數完成,它們代碼很多,涉及到很多計算,但只要對着我們上面給的三個圖來看,不難看出它的意思,本文將不詳細深入。

Animation

Animation的原理很簡單,就是定時重繪圖形。下面的類圖中給出了Android跟Animation相關的類。

Animation:
    Animation抽象類,里面最重要的一個接口就是applyTranformation, 它的輸入是當前的一個描述進度的浮點數(0.0 ~ 1.0), 輸出是一個Transformation類對象,這個對象里有兩個重要的成員變量,mAlpha 和 mMatrix, 前者表示下一個動畫點的透明度(用於灰度漸變效果),后者則是一個變形矩陣,通過它可以生成各種各樣的變形效果。Android提供了很多Animation的具體實現,比如RotationAnimation, AlphaAnimation 等等,用戶也可以實現自己的Animation類,只需要重載applyTransform 這個接口。注意,Animation類只生成繪制動畫所需的參數(alpha 或 matrix),不負責完成繪制工作。完成這個工作的是Animator.

Animator:
   
控制動畫的‘人’, 它通常通過向定時器Choreographer 注冊一個Runnable對象來實現定時觸發,在回調函數里它要做兩件事情:1. 從Animation那里獲取新的Transform, 2. 將Transform里的值更新底層參數,為接下來的重繪做准備。動畫可以發生在Window上,也可以發生在某個具體的View。前者的動畫會通過SurfaceControl直接在某個Surface上進行操作(會在SurfaceFlinger里詳細描述),比如設置Alpha值。后者則通過OpenGL完成(生成我們前面提過的DisplayList).

WindowStateAnimator, WindowAnimator,  AppWindowAnimator:
   
針對不同對象的Animator. WindowAnimator, 負責整個屏幕的動畫,比如說轉屏,它提供Runnable實現。WindowStateAnimator, 負責ViewRoot,即某一個窗口的動畫。AppWindowAnimator, 負責應用啟動和退出時候的動畫。這幾個Animator都會提供一個函數,stepAnimationLocked(), 它會完成一個動畫動作的一系列工作,從計算Transformation到更新Surface的Matrix.

 

具體來看一下Window的Animation和View的Animation

  1. WindowManagerService 的 scheduleAnimationLocked() 將windowAnimator的mAnimationRunnable 注冊到定時器 Choreographer.
  2. 如果應用程序的res/anim/下有xml文件定義animation,在layout過程中,會通過appTransition類的loadAnimation() 函數將XML轉換成 Animation_Set 對象,它里面可以包含多個Animation。
  3. 當下一個VSYNC事件到來,剛才注冊的Callback函數被調用,即WindowAnimator的mAnimationRunnable,里面調用 animateLocked(), 首先,打開一個SurfaceControl的動畫會話,animationSession。
  4. 首先執行的動畫是 appWindowAnimator, 如果剛才loadAnimation() 返回的animation不為空,便會走到Animation的getTransform() 獲取動畫的參數,這里可能會同時有多個動畫存在,通過Transform的compose()函數將它們最終合為一個。
  5. 接下來上場的是DisplayContentsAnimator, 它主要用來實現灰度漸變和轉屏動畫。同樣,首先通過stepAnimation() 獲取動畫變形參數,然后通過SurfaceControl將其更新到SrufaceFlinger內部對應的Layer. 這里首先完成的是轉屏的動畫
  6. 然后就是每個窗口的動畫。后面跟着的 perpareSurfaceLocked() 則會更新參數。
  7. Wallpaper的動畫。
  8. 接下來,就是上面提到的DisplayContentsAnimator的第二部分,通過DimLayer實現漸變效果。
  9. Surface的控制完成后,關閉對話。然后scheduleAnimationLocked() 規划下一步動畫。
  10. 接下來的performDraw()會把所有更新參數的View,或Surface交給OpenGL或HWcomposer進行處理,於是我們就看到了動畫效果。

 View 的動畫實現步驟與Windows 類似,有興趣的同學可以去看View.java 的 drawAnimation() 函數。

 管理窗口

WMS 里面管理着各式各樣的窗口, 如下表所示(在WindowManagerService.java 中定義)

   類型 用途
mAnimatingAppToken   ArrayList<AppWindowToken> 正在動畫中的應用
mExistingAppToken ArrayList<AppWindowToken> 退出但退出動畫還沒有完成的應用。
mResizingWindows ArrayList<WindowState> 尺寸正在改變的窗口,當改變完成后,需要通知應用。
mFinishedStarting ArrayList<AppWindowToken> 已經完成啟動的應用。
mPendingRemove ArrayList<WindowState> 動畫結束的窗口。
mLosingFocus  ArrayList<WindowState>  失去焦點的窗口,等待獲得焦點的窗口進行顯示。
mDestorySurface    ArrayList<WindowState> 需要釋放Surface的窗口。
mForceRemoves  ArrayList<WindowState> 需要強行關閉的窗口,以釋放內存。
mWaitingForDrawn ArrayList<Pair<WindowState, IRemoteCallback>> 等待繪制的窗口
mRelayoutWhileAnimating ArrayList<WindowState> 請求relayout但此時仍然在動畫中的窗口。
mStrictModeFlash StrictModeFlash 一個紅色的背景窗口,用於提示可能存在的內存泄露。
mCurrentFocus   WindowState 當前焦點窗口
mLastFocus WindowState 上一焦點窗口
mInputMethodTarget WindowState 輸入法窗口下面的窗口。
mInputMethodWindow  WindowState 輸入法窗口
mWallpaperTarget WindowState 牆紙窗口
mLowerWallpaperTarget WindowState 牆紙切換動畫過程中Z-Order 在下面的窗口
mHigherWallpaperTarget WindowState 牆紙切換動畫過程中Z-Order 在上面的窗口
     
     

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

可以看到這里大量的用到了隊列,不同的窗口,或同一窗口在不同的階段,可能會出現在不同的隊列里。另外因為WindowManager Service 的服務可能被很多個線程同時調用,在這種復雜的多線程環境里,通過鎖來實現線程安全非常難以實現,一不小心就可能導致死鎖,所以在 WindowManager 內專門有一個執行線程(WM Thread)來將所有的服務請求通過消息進行異步處理,實現調用的序列化。隊列是實現異步處理的常用手段。隊列加Looper線程是Android 應用常用的設計模型。

此外,WindowManager還根據Window的類型進行了分類(在WindowManager.java),如下表,

類型 常量范圍 子類 常量值 說明 例子
APPLICATION_WINDOW 1~99 TYPE_BASE_APPLICATION 1     
    TYPE_APPLICATION 2  應用窗口  大部分的應用程序窗口
    TYPE_APPLICATION_STARTING 3  應用程序的Activity顯示之前由系統顯示的窗口  
    LAST_APPLICATION_WINDOW  99    
SUB_WINDOW 1000~1999 FIRST_SUB_WINDOW 1000    
    TYPE_APPLICATION_PANEL 1000  顯示在母窗口之上,遮擋其下面的應用窗口。  
    TYPE_APPLICATION_MEDIA 1001  顯示在母窗口之下,如果應用窗口不挖洞,即不可見。 SurfaceView,在小窗口顯示時設為MEDIA, 全屏顯示時設為PANEL
    TYPE_APPLICATION_SUB_PANEL 1002         
    TYPE_APPLICATION_ATTACHED_DIALOG 1003    
    TYPE_APPLICATION_MEIDA_OVERLAY 1004  用於兩個SurfaceView的合成,如果設為MEDIA,
則上面的SurfaceView 擋住下面的SurfaceView
 
SYSTEM_WINDOW   2000~2999 TYPE_STATUS_BAR   2000 頂部的狀態欄  
    TYPE_SEARCH_BAR 2001 搜索窗口,系統中只能有一個搜索窗口  
    TYPE_PHONE 2002  電話窗口  
    TYPE_SYSTEM_ALERT 2003 警告窗口,在所有其他窗口之上顯示   電量不足提醒窗口
    TYPE_KEYGUARD 2004 鎖屏界面  
    TYPE_TOAST 2005 短時的文字提醒小窗口  
    TYPE_SYSTEM_OVERLAY 2006 沒有焦點的浮動窗口  
    TYPE_PRIORITY_PHONE 2007 緊急電話窗口,可以顯示在屏保之上  
    TYPE_SYSTEM_DIALOG   2008 系統信息彈出窗口  比如SIM插上后彈出的運營商信息窗口
    TYPE_KEYGUARD_DIALOG 2009 跟KeyGuard綁定的彈出對話框 鎖屏時的滑動解鎖窗口
    TYPE_SYSTEM_ERROR 2010 系統錯誤提示窗口  ANR 窗口
    TYPE_INPUT_METHOD 2011 輸入法窗口,會擠占當前應用的空間  
    TYPE_INPUT_METHOD_DIALOG 2012 彈出的輸入法窗口,不會擠占當前應用窗口空間,在其之上顯示  
    TYPE_WALLPAPER 2013  牆紙  
    TYPE_STATUS_BAR_PANEL 2014 從狀態條下拉的窗口  
    TYPE_SECURE_SYSTEM_OVERLAY 2015 只有系統用戶可以創建的OVERLAY窗口  
    TYPE_DRAG 2016 浮動的可拖動窗口 360安全衛士的浮動精靈
    TYPE_STATUS_BAR_PANEL 2017    
    TYPE_POINTER 2018 光標  
    TYPE_NAVIGATION_BAR 2019    
    TYPE_VOLUME_OVERLAY 2020 音量調節窗口  
    TYPE_BOOT_PROGRESS 2021 啟動進度,在所有窗口之上  
    TYPE_HIDDEN_NAV_CONSUMER 2022 隱藏的導航欄  
    TYPE_DREAM 2023 屏保動畫  
    TYPE_NAVIGATION_BAR_PANEL 2024 Navigation bar 彈出的窗口 比如說應用收集欄
    TYPE_UNIVERSAL_BACKGROUND   2025    
    TYPE_DISPLAY_OVERLAY 2026 用於模擬第二顯示設備  
    TYPE_MAGNIFICATION 2027 用於放大局部  
    TYPE_RECENTS_OVERLAY 2028 當前應用窗口,多用戶情況下只顯示在用戶節目  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

windowManager Service 會根據窗口的類型值來決定Z-Order (於常量值無關,值大說明是后面Android版本添加的,比如說2025~2028就是4.3 新加的)。比如說SurfaceView.java 里的一個函數,

    public void setZOrderOnTop(boolean onTop) {
        if (onTop) {
            mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; //PANEL在上面
            // ensures the surface is placed below the IME
            mLayout.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
        } else {
            mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; //MEDIA類型窗口在應用窗口之下,應用必需挖洞(設Alpha值)才能露出它。
            mLayout.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
        }
    }

這些類型最終在WindowManager 內部轉換成幾個Z-Order 值,mBaseLayer, mSubLayer, mAnimationLayer, 分別表明主窗口,子窗口(附加在主窗口之上),和動畫窗口的Z-Order值(越大越在上邊)。不同的窗口類型在不同的硬件產品上有不同的定義,因此它是實現在WindowManagerPolicy里的windowTypeToLayerLw(), 舉PhoneWindowManager 為例,它的ZOrder 順序是:

Univese background < Wallpaper < Phone < Search Bar < System Dialog < Input Method Window < Keyguard < Volume < System Overlay < Navigation < System Error <  < Display Overlay< Drag < Pointer < Hidden NAV consumer, 

所以,我們如果要在手機鎖屏時顯示歌曲播放進度,就必須給這個窗口分配一個大於Keyguard的type,如 system overlay 等。

一個Window可以有若干個Sub Window, 他們和主窗口的ZOrder關系是

Media Sublayer(-2) < Media Overlay sublayer (-1) < Main Layer(0) < Attached Dialog (1) < Sub panel Sublayer (2)

通過 "adb shell dumpsys window" 可以查看系統當前運行的窗口的ZOrder 和 Visibility, 比如下面就是在短信輸入界面下運行“dumpsys" 獲得的結果,

   1  Window #0 Window{4ea4e178 u0 Keyguard}:
   2     mBaseLayer=121000 mSubLayer=0 mAnimLayer=121000+0=121000 mLastLayer=121000
   3     mViewVisibility=0x8 mHaveFrame=true mObscured=false
   4   Window #1 Window{4ea4aa7c u0 InputMethod}:
   5     mBaseLayer=101000 mSubLayer=0 mAnimLayer=21020+0=21020 mLastLayer=21020
   6     mViewVisibility=0x0 mHaveFrame=true mObscured=false
   7   Window #2 Window{4ec1a150 u0 com.android.mms/com.android.mms.ui.ComposeMessageActivity}:
   8     mBaseLayer=21000 mSubLayer=0 mAnimLayer=21015+0=21015 mLastLayer=21015
   9     mViewVisibility=0x0 mHaveFrame=true mObscured=false
  10   Window #3 Window{4ea7c714 u0 com.android.mms/com.android.mms.ui.ConversationList}:
  11     mBaseLayer=21000 mSubLayer=0 mAnimLayer=21010+0=21010 mLastLayer=21015
  12     mViewVisibility=0x8 mHaveFrame=true mObscured=true
  13   Window #4 Window{4eaedefc u0 com.android.launcher/com.android.launcher2.Launcher}:
  14     mBaseLayer=21000 mSubLayer=0 mAnimLayer=21005+0=21005 mLastLayer=21010
  15     mViewVisibility=0x8 mHaveFrame=true mObscured=true
  16   Window #5 Window{4ea17064 u0 jackpal.androidterm/jackpal.androidterm.Term}:
  17     mBaseLayer=21000 mSubLayer=0 mAnimLayer=21000+0=21000 mLastLayer=22000
  18     mViewVisibility=0x8 mHaveFrame=true mObscured=true

可以看到:

  1. 當前只有兩個窗口可見, InputMethod 和 com.android.mms/com.android.mms.ui.ComposeMessageActivity, mViewVisibility = 0 (0在View.java定義是可見), 而其他 mViewVisibility=0x8 (定義在View.java里, 意思是”GONE").
  2. InputMethod(mLastLayer=21020) 在 ComposeMessageActivity(mLastLayer=21015) 之上. 細心的同學可能會發現,InputMethod的mBaseLayer = 101000,為什么mLastLayer小那么多?因為mLastLayer才是真正的z-order, 它經過了WidowManager的調整。當用戶點擊輸入框,View會通過InputMethodManager 發送一個showSoftInput命令,經過InputManagerService的處理,輸入法窗口(KeyboardView)會被加入到WndowManager Service里,WindowManager Service 會尋找它的目標窗口, 即需要輸入的窗口,(遍歷WindowList 然后根據窗口的Flags判斷),然后將輸入法窗口的mLayer值改為 目標窗口的mLayer + 5,這樣,輸入法窗口就顯示在了目標窗口之上。在這里,輸入法窗口存在於InputMethodManagerService 的上下文里,而不是某個Activity,所以他可以跟任何需要輸入法的Activity綁定。其他一些應用,比如說PiP(三星的Galaxy S3可以在一個浮動的小窗口里顯示視頻)也是運用了類似的方法來實現的。Android的輸入法是一個非常值得研究的模塊,留到后面探討。

所以,WindowManager Service 是通過調整窗口的mViewVisibility 和 mLayer 值來實現窗口重疊。最后給出跟Z-order相關的類圖。

 

 圖中序號表示輸入法窗口找到它的目標窗口的過程:

  1. WindowManagerService 找到默認輸出(Default Display) 的DisplayContents成員變量。
  2. 里面有一個數組WindowList-mWindows, 按照Z-Order順序保存了當前在這個Display上輸出的所有窗口WindowState。
  3. 遍歷所有的WindowState,判斷它的mAppToken是否和輸入法窗口的mAppToken一致。呼起輸入法窗口的窗口會將自己的mAppToken拷貝給它。
  4. 相同的Token下,可能有多個窗口,通過WindowToken.windows 或者 AppWindowToken.allAppWindows, 可以找到他們。

 

WindowManager Service的介紹暫告一段落,它與其他重要的Service,SurfaceFlinger, ActivityManager, InputManager, PowerManager, WatchDog 之間的關系將在其他文章介紹。

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM