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

風靈使發表於2018-11-07

圖解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完後,第一個呼叫的方法就是 ActivityThreadmain(),這個函式主要做的事情就是建立一個ActivityThread執行緒,然後呼叫loop()開始等待。當收到來自 ActivityManagerLAUNCH_ACTIVITY 訊息後,Activity開始了他的顯示之旅。下圖描繪的是Activity在顯示前的準備流程。
在這裡插入圖片描述
圖分為三部分, 右上角是Acitivity應用的初始化。中間部分是AcitivityWindowManager 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是連線ViewModal的關鍵,所以需要首先建立它。當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 進行管理。ParamsLayout相關的引數,裡面包含有長,寬,邊緣尺寸(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 ServiceaddWindow() 介面。

  5. addWindow() 裡首先生成了一個WindowState物件,它是ViewRootImplWindowManager Service端的代表。在它的建構函式裡,WindowState 會生成IWindowId.Stub物件和DeathRecipient物件來分別監聽Focus和視窗死亡的資訊,根據使用者傳進來的Window Type計算出視窗的mBaseLayermSubLayermLastLayer值,分別對應於主視窗,主視窗上彈出的子視窗(如輸入法),以及動畫時分別對應的ZOrder值,(在本文後面會具體介紹),生成一個WindowStateAnimation負責整個Window的動畫,並在內部將windowToken, appWindowToken等關聯起來。

  6. WindowManager Service 呼叫openInputChannelPair() and RegisterInputChannel(), 建立用於通訊的SocketPair ,將其傳給InputManagerService, 用於接下來的使用者輸入事件對應的響應視窗(參考Android的使用者輸入處理),

  7. 最後,WindowManagerService 呼叫WindowStateattach(),建立了一個Surface Session並將Surface SessionWindowSession 還有WindowState 三者關聯起來.

  8. WindowManager Service 呼叫 assignLayersLocked()計算所有Window的Z-Order

  9. addToDisplay() 返回,ViewRootImplWindowManager Service內部的準備工作就緒。ActivityThread會傳送ACTIVITY_RESUMED訊息告訴Activity顯示開始。可以是圖還沒有畫,不是嗎?對的,此刻Surface還沒有真正初始化(我們前面說過ViewRootImpl只是New了一個空的物件,需要有人往裡面填東西)。底層存放繪製結果的Buffer也沒有建立,但是最多16ms以後這一切就會開始。

2. ChoreographerSurface的建立

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

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

從圖中我們可以看到, ChoreographerViewRootImpl 建立的(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()? 畢竟onVSynchandleCallback() 都在一個執行緒裡。這是因為MessageQueue 不光接收來自SurfaceFlingerVSync 事件,還有來自上層的控制訊息。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是在動畫的準備過程中建立的,具體發生在類WindowStateAnimatorcreateSurfaced()函式。它最終建立了一個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的三大工作,使用者輸入響應,動畫,和繪製都是由一個定時器驅動的,SurfaceActivity第一次啟動時由WindowManager Service建立。接下來我們具體看一下View是如何畫在Surface Buffer上的,而Surface Buffer的顯示則交由圖解Android - Android GUI 系統 (3) - Surface Flinger 來討論。

3. View的Measure, LayoutDraw

直接從前面提到的performMeasure()函式開始.
在這裡插入圖片描述
因為遞迴呼叫,實際的函式呼叫棧比這裡顯示的深得很多,這個函式會從view的結構樹頂(DecorView), 一直遍歷到葉節點。中間會經過三個基類,DecorViewViewGroupView, 它們的類結構如下圖所示:
在這裡插入圖片描述
所有可見的View(不包括DecorViewViewGroup)都是一個矩形,Measure的目的就是算出這個矩形的尺寸, mMeasuredWidthmMeasuredHeight (注意,這不是最終在螢幕上顯示的尺寸),這兩個尺寸的計算受其父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;

widthMeasureSpecheightMeasureSpec 作為 onMeasure的引數出入,子View根據這兩個值計算出自己的尺寸,最終呼叫 setMeasuredDimension() 更新mMeasuredWidthmMeasuredHeight.

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), 以及bitmappixle data, 他的輸出是一個或兩個Framebuffer(真3D立體). 輸入到輸出的流程(rendering pipeline)如下圖所示:
在這裡插入圖片描述
這裡有太多的概念,我們只描述跟本文相關的幾個:

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

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

Display List
所有的VertexPixel資訊均可以存在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 BufferBack 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的HardwareCanvasHardwareRenderer在底層的對應實現。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 物件也會因為被賦值 NULLGC在將來回收。
  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(用於顯示) 和初始化一個EGLSurfaceOpenGL將在這個Surface上進行繪圖。關於EGLDisplayEGLSurface 將在 圖解Android - Android GUI 系統 (3) - Surface Flinger裡詳細描述。
  2. 我們前面說過,Android 4.0 帶來的圖形效能的提升,有很大程度是DisplayList帶來的,所以,HardwareRenderer接下來就通過buildDisplayList()建立整個View樹的DisplayList.最開始,GL20Renderer為View生成了一個GLES20DisplayList,這是一個影子類,沒有實際用途,主要用來管理Native層的DisplayList的銷燬。
  3. View 呼叫剛剛生成的GLES20DisplayListstart()方法,真正開始構建DisplayList的上下文。obtain() 函式裡會判斷是否mCanvas 已經存在,如果沒有則New一個新的GLES20RecordingCanvas物件。
  4. 建立與GLES20RecordingCanvas 一一對應的Native層的 DisplayListRenderer
  5. 3,4是一個遞迴的過程,直到所有的ViewDisplayList上下文生成,View才真正開始Draw(),這裡,OpenGL命令(Op)不會被立即執行,而是被儲存到剛剛生成的DisplayListRenderer裡。
  6. 接著View呼叫GLESDisplayListend() 方法,這裡Native層的DisplayList物件才真正被建立出來,Java 和 Native 的 DisplayList 物件一一對應起來。
  7. end() 方法最後,呼叫GLES20RecordingCanvas.recycle()方法,首先將Native的DisplayListRender進行重新初始化,然後將剛才建立出來的臨時物件賦NULL值(GLES20RecordingCanvasGLES20DisplayList),因為它們的使命已經完成,將View的OpenGL命令儲存在Native層的DisplayList物件裡。
  8. GC() 被呼叫後,GLES20RecordingCanvasGLES20DisplayList 被釋放,相應的Finalizer物件方法會被呼叫,進而呼叫Natice層的deleteDisplayListDefered(), 將之前不用的DisplayList送入 CachesGabage 佇列中,等待回收。(這是在Native層實現的類似Java GC的機制)
  9. 到此,所有的DisplayList 上下文準備就緒。進入preDraw 狀態,在GL20RendereronPreDraw()方法裡,最終呼叫到底層的clearGarbage將上一次繪圖操作的DisplayList在底層釋放。
  10. HardwareRenderer呼叫 GLES20CanvasdrawDisplayList(), 通知 Native層的OpenGLRenderer,其最終呼叫每個DisplayListReplay() 方法執行OpenGL命令。
  11. 所有OpenGL命令執行完後, 圖形被繪製到第一步生成的EGLSurface, hardwareRender 呼叫GLES20CanvaseglSwapBuffers() 交換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 LayerDisplay 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。使用哪種重繪方式由當前ViewFlags,以及應用程式傳入的引數決定。控制它的就是一組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. 
            }
        }
    }

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

4. Windows 的管理

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

  1. Acitivity建立, ViewRootImpl將視窗註冊到WindowManager ServiceWindowManager 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 ServiceAcitivityManager Service 等等,但本章只討論它在Window Manager 方面的工作,下圖中紅色標記部分。

Layout

首先來看LayoutLayoutWindow 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 AreaResctrited Aread, 前者包括這部分預留區域,後者則不包含, Unrestricted area > Rectricted area

mFrame, mDisplayFrame, mContainingFrame
Frame指的是一片記憶體區域, 對應於螢幕上的一塊矩形區域. mFrame的大小就是Surface的大小, 如上上圖中的藍色區域. mDisplayFramemContainingFrame 一般和mFrame 大小一致. mXXXWindow(ViewRootImpl, Windowstate) 裡面定義的成員變數.

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

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

LayoutWMS 內部的時序如下圖所示,外部調整Overscan引數或View內部主動呼叫requestLayout() 都會觸發WMS的重新layoutlayout完成後,WMS會通過IWindowresized()介面通知ViewRoot, 最終會呼叫requestLayout(), 並在下一個VSYNC 事件到來時更新。

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

Animation

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

Animation:
Animation抽象類,裡面最重要的一個介面就是applyTranformation, 它的輸入是當前的一個描述進度的浮點數(0.0 ~ 1.0), 輸出是一個Transformation類物件,這個物件裡有兩個重要的成員變數,mAlphamMatrix, 前者表示下一個動畫點的透明度(用於灰度漸變效果),後者則是一個變形矩陣,通過它可以生成各種各樣的變形效果。Android提供了很多Animation的具體實現,比如RotationAnimation, AlphaAnimation 等等,使用者也可以實現自己的Animation類,只需要過載applyTransform 這個介面。注意,Animation類只生成繪製動畫所需的引數(alphamatrix),不負責完成繪製工作。完成這個工作的是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到更新SurfaceMatrix.
在這裡插入圖片描述
具體來看一下Window的AnimationViewAnimation
在這裡插入圖片描述

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

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

管理視窗

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,

所以,我們如果要在手機鎖屏時顯示歌曲播放進度,就必須給這個視窗分配一個大於Keyguardtype,如 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” 可以檢視系統當前執行的視窗的ZOrderVisibility, 比如下面就是在簡訊輸入介面下執行“dumpsys" 獲得的結果,

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

可以看到:

  1. 當前只有兩個視窗可見, InputMethodcom.android.mms/com.android.mms.ui.ComposeMessageActivity,mViewVisibility = 0 (0在View.java定義是可見), 而其他 mViewVisibility=0x8(定義在View.java裡, 意思是”GONE").
  2. InputMethod(mLastLayer=21020)ComposeMessageActivity(mLastLayer=21015) 之上.細心的同學可能會發現,InputMethodmBaseLayer =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 是通過調整視窗的mViewVisibilitymLayer 值來實現視窗重疊。最後給出跟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的介紹暫告一段落,它與其他重要的ServiceSurfaceFlinger, ActivityManagerInputManager, PowerManager, WatchDog 之間的關係將在其他文章介紹。

相關文章