圖解Android - Android GUI 系統 (2) - 視窗管理 (View, Canvas, Window Manager)
在圖解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的生命週期裡講解。
-
Activity內部的準備過程,這裡面有一個重要物件,
ContextImpl
生成,它是Context
類的具體實現,它裡面封裝了應用程式訪問系統資源的一些基本API
,比如說,連線某一個服務並獲取其IBinder
,傳送Intent
,獲取應用程式的資訊,訪問資料庫等等,在應用看來,它就是整個AndroidSDK
的入口。ContextImpl
除了實現函式,裡面還維護成員變數,其中有一個mDisplay
,代表當前應用輸出的顯示裝置,如果應用沒有特別指定,一般指向系統的預設顯示輸出,比如手機的液晶屏。 -
在
圖解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
物件。 -
ViewRootImpl
在構造過程成初始化一些重要的成員變數,包括一個Surface
物件(注意這是一個空的Surface
物件,沒有賦給任何有效的值,後面會通過CopyFromParcel
來填充),還有mChoreophaer
定時器(Singleton
物件,每個程式只有一個),與此同時,ViewRootImp
通過WindowManagerGlobal
建立了一個和WindowManagerService
的通話通道,接下來會利用這條通道做進一步的初始化工作。 -
還是在
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()
介面。 -
addWindow()
裡首先生成了一個WindowState
物件,它是ViewRootImpl
在WindowManager Service
端的代表。在它的建構函式裡,WindowState
會生成IWindowId.Stub
物件和DeathRecipient
物件來分別監聽Focus
和視窗死亡的資訊,根據使用者傳進來的Window Type
計算出視窗的mBaseLayer
,mSubLayer
和mLastLayer
值,分別對應於主視窗,主視窗上彈出的子視窗(如輸入法),以及動畫時分別對應的ZOrder
值,(在本文後面會具體介紹),生成一個WindowStateAnimation
負責整個Window
的動畫,並在內部將windowToken
,appWindowToken
等關聯起來。 -
WindowManager Service
呼叫openInputChannelPair() and RegisterInputChannel()
, 建立用於通訊的SocketPair
,將其傳給InputManagerService
, 用於接下來的使用者輸入事件對應的響應視窗(參考Android的使用者輸入處理), -
最後,
WindowManagerService
呼叫WindowState
的attach()
,建立了一個Surface Session
並將Surface Session
,WindowSession
還有WindowState
三者關聯起來. -
WindowManager Service
呼叫assignLayersLocked()
計算所有Window的Z-Order
。 -
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).
- 如果系統支援硬體加速,
ViewRootImpl
首先建立一個GL20Renderer
, 存在成員變數mHardwareRenderer
裡。 Surafce
是繪畫的基礎,如果不存在,HardwareRenderer
會呼叫GLRenderer
->createEglSurface()
建立一個新的Surface
。Surface
建立好後,接著生成Canvas
,因為大部分應用程式不會直接操作Surface
。- 在
Canvas
的建構函式裡,會依次建立Native
層對應的OpenGLRenderer
物件,它會直接訪問庫libGLES_xxx
提供的OpenGL ES API
,來負責整個View
樹的繪製工作。 - 與此同時,一個
CanvasFinalizer
成員物件也會被建立,它儲存剛剛建立出來的OpenGLRenderer
指標,當它的finalizer()
函式被呼叫是,負責將其銷燬,回收資源。 - 在執行過程中,如果視窗死掉或者不在可見,
ViewRootImpl
會呼叫DestroyHardwareResource()
來釋放資源。這裡會最終將底層建立的Hardware Layer
回收。 - 同時Java端的
GLES20Layer
物件也會因為被賦值NULL
被GC
在將來回收。 - 接下來,
ViewRootImpl
呼叫destroyHardwareRenderer()
將之前建立的Native Renderer(DisplayListRenderer,OpenGLRenderer)
依次回收。 - 最後將Java 層的
mHardwareRenderer
賦空,GC
將會回收最開始建立的GL20Renderer
物件。支援,一個View
樹的生命週期完成,所有資源清楚乾淨。
等等!好像少了點什麼,怎麼沒有DisplayList
? 前面不是說它是效能優化的幫手之一嗎?對了,上面只介紹了繪製的開始和結尾,在View的生命週期中,還有最重要的一步,Draw
還沒有被介紹,DisplayList
相關的操作就是在Draw()
裡面完成的。
Draw 流程
繞了好大一圈,終於回到最初的話題,Android是怎樣將View畫出來的? 讓我們按照圖中的序號一一進行講解。(黃色:Java, 綠色:C++,藍色:JNI,粉色:New, 黑色:Delete).
- 首先,
ViewRootImpl
直接訪問的HardwareRenderer
物件,首先在BeginFrame()
裡獲取EGLDisplay
(用於顯示) 和初始化一個EGLSurface
,OpenGL
將在這個Surface
上進行繪圖。關於EGLDisplay
和EGLSurface
將在 圖解Android - Android GUI 系統 (3) - Surface Flinger裡詳細描述。 - 我們前面說過,Android 4.0 帶來的圖形效能的提升,有很大程度是
DisplayList
帶來的,所以,HardwareRenderer
接下來就通過buildDisplayList()
建立整個View樹的DisplayList.最開始,GL20Renderer
為View生成了一個GLES20DisplayList,這是一個影子類,沒有實際用途,主要用來管理Native層的DisplayList的銷燬。 - View 呼叫剛剛生成的
GLES20DisplayList
的start()
方法,真正開始構建DisplayList
的上下文。obtain()
函式裡會判斷是否mCanvas
已經存在,如果沒有則New
一個新的GLES20RecordingCanvas
物件。 - 建立與
GLES20RecordingCanvas
一一對應的Native
層的DisplayListRenderer
。 - 3,4是一個遞迴的過程,直到所有的
View
的DisplayList
上下文生成,View才真正開始Draw()
,這裡,OpenGL命令(Op)不會被立即執行,而是被儲存到剛剛生成的DisplayListRenderer
裡。 - 接著View呼叫
GLESDisplayList
的end()
方法,這裡Native層的DisplayList
物件才真正被建立出來,Java 和 Native 的DisplayList
物件一一對應起來。 end()
方法最後,呼叫GLES20RecordingCanvas.recycle()
方法,首先將Native的DisplayListRender
進行重新初始化,然後將剛才建立出來的臨時物件賦NULL值(GLES20RecordingCanvas
和GLES20DisplayList
),因為它們的使命已經完成,將View的OpenGL命令儲存在Native層的DisplayList物件裡。- GC() 被呼叫後,
GLES20RecordingCanvas
和GLES20DisplayList
被釋放,相應的Finalizer
物件方法會被呼叫,進而呼叫Natice
層的deleteDisplayListDefered()
, 將之前不用的DisplayList
送入Caches
的Gabage
佇列中,等待回收。(這是在Native
層實現的類似Java GC的機制) - 到此,所有的
DisplayList
上下文準備就緒。進入preDraw
狀態,在GL20Renderer
的onPreDraw()
方法裡,最終呼叫到底層的clearGarbage
將上一次繪圖操作的DisplayList在底層釋放。 HardwareRenderer
呼叫GLES20Canvas
的drawDisplayList()
, 通知 Native層的OpenGLRenderer
,其最終呼叫每個DisplayList
的Replay()
方法執行OpenGL命令。- 所有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)
是如何畫出來的,讓我們在簡要重溫一下這個過程:
- Acitivity建立,
ViewRootImpl
將視窗註冊到WindowManager Service
,WindowManager Service
通過SurfaceFlinger
的介面建立了一個Surface Session
用於接下來的Surface
管理工作。 - 由
Surface Flinger
傳上來的VSYNC
事件到來,Choreographer
會執行ViewRootImpl
註冊的Callback
函式, 這個函式會最終呼叫performTraversal
遍歷View樹裡的每個View
,在第一個VSYNC
裡,WindowManager Service
會建立一個SurafceControll
物件,ViewRootImpl
根據Parcel
返回的該物件生成了Window對應的Surface
物件,通過這個物件,Canvas
可以要求Sruface Flinger
分配OpenGL繪圖用的Buffer
。 View
樹裡的每個View
會根據需要依次執行measure(),layout()
和draw()
操作。Android在3.0之後引入了硬體加速機制,為每個View
生成DisplayList
,並根據需要在GPU內部生成Hardware Layer
,從而充分利用GPU的功能提升圖形繪製速度。- 當某個View發生變化,它會呼叫
invalidate()
請求重繪,這個函式從當前View
出發,向上遍歷找到View Tree
中所有Dirty
的 View 和ViewGroup
, 根據需要重新生成DisplayList
,並在drawDisplayList()
函式裡執行OpenGL命令將其繪製在某個Surface Buffer
上。 - 最後,
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
WindowManagerService
的scheduleAnimationLocked()
將windowAnimator
的mAnimationRunnable
註冊到定時器Choreographer
.- 如果應用程式的
res/anim/
下有xml
檔案定義animation
,在layout
過程中,會通過appTransition
類的loadAnimation()
函式將XML
轉換成Animation_Set
物件,它裡面可以包含多個Animation
。 - 當下一個
VSYNC
事件到來,剛才註冊的Callback
函式被呼叫,即WindowAnimator
的mAnimationRunnable
,裡面呼叫animateLocked()
, 首先,開啟一個SurfaceControl
的動畫會話,animationSession
。 - 首先執行的動畫是
appWindowAnimator
, 如果剛才loadAnimation()
返回的animation
不為空,便會走到Animation
的getTransform()
獲取動畫的引數,這裡可能會同時有多個動畫存在,通過Transform
的compose()
函式將它們最終合為一個。 - 接下來上場的是
DisplayContentsAnimator
,它主要用來實現灰度漸變和轉屏動畫。同樣,首先通過stepAnimation()
獲取動畫變形引數,然後通過SurfaceControl
將其更新到SrufaceFlinger
內部對應的Layer
.這裡首先完成的是轉屏的動畫 - 然後就是每個視窗的動畫。後面跟著的
perpareSurfaceLocked()
則會更新引數。 Wallpaper
的動畫。- 接下來,就是上面提到的
DisplayContentsAnimator
的第二部分,通過DimLayer
實現漸變效果。 Surface
的控制完成後,關閉對話。然後scheduleAnimationLocked()
規劃下一步動畫。- 接下來的
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
" 獲得的結果,
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
可以看到:
- 當前只有兩個視窗可見,
InputMethod
和com.android.mms/com.android.mms.ui.ComposeMessageActivity,mViewVisibility = 0
(0在View.java
定義是可見), 而其他mViewVisibility=0x8
(定義在View.java裡, 意思是”GONE"). 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
相關的類圖。
圖中序號表示輸入法視窗找到它的目標視窗的過程:
WindowManagerService
找到預設輸出(Default Display
) 的DisplayContents
成員變數。- 裡面有一個陣列
WindowList-mWindows
,按照Z-Order
順序儲存了當前在這個Display
上輸出的所有視窗WindowState
。 - 遍歷所有的
WindowState
,判斷它的mAppToken
是否和輸入法視窗的mAppToken
一致。呼起輸入法視窗的視窗會將自己的mAppToken
拷貝給它。 - 相同的
Token
下,可能有多個視窗,通過WindowToken.windows
或者AppWindowToken.allAppWindows
, 可以找到他們。
WindowManager Service
的介紹暫告一段落,它與其他重要的Service
,SurfaceFlinger
, ActivityManager
, InputManager
, PowerManager
, WatchDog
之間的關係將在其他文章介紹。
相關文章
- 圖解Android - Android GUI 系統 (1) - 概論圖解AndroidGUI
- Cisdem Window Manager for Mac 視窗管理工具Mac
- Android View 系統 1 - View樹AndroidView
- Android 懸浮窗 System Alert WindowAndroid
- Android系統架構詳解(2)--Android RuntimeAndroid架構
- Android自定義View之Canvas的使用AndroidViewCanvas
- Android顯示框架:Android應用檢視的管理者WindowAndroid框架
- Android之Window和彈窗問題Android
- Android View體系(4)AndroidView
- View繪製01-Android渲染系統中的ViewViewAndroid
- Android自定義View之Window、ViewRootImpl和View的三大流程AndroidView
- 探究Android View 繪製流程,Canvas 的由來AndroidViewCanvas
- Android日常學習:Android檢視動畫-View AnimationAndroid動畫View
- Android 8.0 原始碼分析 (十) WindowManagerService 的視窗管理Android原始碼
- 圖解 Android 系列(一)揭祕 Android 系統啟動過程圖解Android
- Android View 的事件體系AndroidView事件
- Android通用業務彈窗管理方案PopLayerV2Android
- Android圖解建立外部lib庫及自定義ViewAndroid圖解View
- Android系統架構圖Android架構
- android--Android Studio使用terminal終端(命令視窗)Android
- Android XML佈局報錯:android/view/View$OnUnhandledKeyEventListenerAndroidXMLView
- android自定義View——座標系AndroidView
- 懸浮窗的一種實現 | Android懸浮窗Window應用Android
- Android截圖和指定View生成截圖分享AndroidView
- Android自定義View:View(二)AndroidView
- Android系統原始碼分析--View繪製流程之-inflateAndroid原始碼View
- Android系統原始碼分析–View繪製流程之-setContentViewAndroid原始碼View
- Android系統原始碼分析--View繪製流程之-setContentViewAndroid原始碼View
- Android原生繪圖之讓你瞭解View的運動Android繪圖View
- android View 繪圖雙緩衝技術AndroidView繪圖
- Android View的生命週期詳解AndroidView
- Android 自定義View 滑動解鎖AndroidView
- Android 中的 WindowAndroid
- Android視窗系統第四篇—Activity動畫的設定過程Android動畫
- Android-Window(一)——初識WindowAndroid
- Flutter 115: 圖解自定義 View 之 Canvas (四Flutter圖解ViewCanvas
- Android View post 方法AndroidView
- 【Android系統】Android系統架構簡介Android架構