圖解Android - Android GUI 系統 (1) - 概論

風靈使發表於2018-11-06

Android的GUI系統是Android最重要也最複雜的系統之一。它包括以下部分:

  1. 視窗和圖形系統 - Window and View Manager System.
  2. 顯示合成系統 - Surface Flinger
  3. 使用者輸入系統 - InputManager System
  4. 應用框架系統 - Activity Manager System.

它們之間的關係如下圖所示
在這裡插入圖片描述

只有對這些系統的功能和工作原理有基本的瞭解,我們才能夠解答一些經常縈繞在腦海裡的問題,比如說:

  1. Activity啟動過程是怎樣的?Activity的onXXX()在後臺都做了什麼工作?Activity的show()和hide()是如何控制的?
  2. Surface是什麼時候被誰建立的?
  3. Android是如何把一個個的控制元件畫到Surface上的?然後顯示到手機螢幕上的?
  4. 應用程式視窗是如何獲取焦點的?使用者的按鍵(觸控式螢幕或鍵盤)是怎樣傳遞到當前的視窗?
  5. Android是一個多視窗的系統嗎?
  6. Android是怎麼支援多屏互動的?(Wifi Display)
  7. 輸入法視窗到底屬於哪個程式?為什麼不管什麼應用,只要有輸入框的地方都能彈出它?

本文將從框架和流程角度出發,試圖對Android的GUI系統做一個簡要但全面的介紹,希望藉此能夠幫助大家找到上述問題的答案。

所有的內容可以濃縮在下面這張圖裡,裡面有很多的名稱和概念我們需要事先解釋一下:

1. Window, PhoneWindow 和 Activity

  • Activity 是 Android 應用的四大元件之一(Activity, Service, Content Provider, Broadcast Receiver), 也是唯一一個與使用者直接互動的元件。
  • Window 在 不同的地方有著不同的含義。在Activity裡,Window是一個抽象類,代表了一個矩形的不可見的容器,裡面佈局著若干個可視的區域(View).每個Activity都會有一個Window類成員變數,mWindow.而在WindowManagerService裡,Window指的是WindowState物件,從圖中可以看出,WindowState與一個ViewRootImpl裡的mWindow物件相對應。所以說,WindowManagerService裡管理的Window其實是Acitivity的ViewRoot。我們下面提到的Window,如果沒有做特殊說明,均指的是WindowManagerService裡的‘Window’概念,即一個特定的顯示區域。從使用者角度來看,Android是個多視窗的作業系統,不同尺寸的視窗區域根據尺寸,位置,z-order及是否透明等引數疊加起來一起並最終呈現給使用者。這些視窗既可以是來自一個應用,也可以來自與多個應用,這些視窗既可以顯示在一個平面,也可以是不同的平面。總而言之,視窗是有層次的顯示區域,每個視窗在底層最終體現為一個個的矩形Buffer,這些Buffer經過計算合成為一個新的Buffer,最終交付Display系統進行顯示。為了輔助最後的視窗管理,Android定義了一些不同的視窗型別:
    • 應用程式視窗 (Application Window): 包括所有應用程式自己建立的視窗,以及在應用起來之前系統負責顯示的視窗。
    • 子視窗(Sub Window):比如應用自定義的對話方塊,或者輸入法視窗,子視窗必須依附於某個應用視窗(設定相同的token)。
    • 系 統視窗(System Window): 系統設計的,不依附於任何應用的視窗,比如說,狀態列(Status Bar),
      導航欄(Navigation Bar), 桌布(Wallpaper), 來電顯示視窗(Phone),鎖屏視窗(KeyGuard),
      資訊提示視窗(Toast), 音量調整視窗,滑鼠游標等等。
  • PhoneWindow 是Activity Window的擴充套件,是為手機或平板裝置專門設計的一個視窗布局方案,就像大家在手機上看到的,一個PhoneWindow的佈局大致如下:

2. View, DecorView, ViewGroup, ViewRoot

View 是一個矩形的可見區域。
ViewGroup 是一種特殊的View, 它可以包含其他View並以一定的方式進行佈局。Android支援的佈局有FrameLayout, LinearLayout, RelativeLayout 等。
DecorViewFrameLayout的子類,FrameLayout 也叫單幀佈局,是最簡單的一種佈局,所有的子View在垂直方向上按照先後順序依次疊加,如果有重疊部分,後面的View將會把前面的View擋住。我們經常看到的彈出框,把後面的視窗擋住一部分,就是用的FrameLayout佈局。Android的視窗基本上用的都是FrameLayout佈局, 所以DecorView也就是一個Activity Window的頂級View, 所有在視窗裡顯示的View都是它的子View.

ViewRoot . 我們可以定義所有被addView()呼叫的ViewViewRoot, 因為介面將會生成一個ViewRootImpl 物件,並儲存在WindowManagerGlobalmRoots[] 陣列裡。一個程式可能有很多了ViewRoot(只要多次呼叫addView()), 在WindowManagerService端看來,就是多個Window。但在Activity的預設實現裡,只有mDecorView 通過addView 新增到WindowManagerService裡( 見如下程式碼)

 //frameworks/base/core/java/android/app/Activity.java
 void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
  }

因此一般情況下,我們可以說,一個應用可以有多個Activity,每個 Activity 一個Window(PhoneWindow), 每個Window 有一個DecorView, 一個ViewRootImpl, 對應在WindowManagerService 裡有一個Window(WindowState).

3. ViewRootImple, WindowManagerImpl, WindowManagerGlobals

WindowManagerImpl: 實現了WindowManagerViewManager的介面,但大部分是呼叫WindowManagerGlobals的介面實現的。

WindowManagerGlobals: 一個SingleTon物件,物件裡維護了三個陣列:

  • mRoots[ ]: 存放所有的ViewRootImpl
  • mViews[ ]: 存放所有的ViewRoot
  • mParams[ ]: 存放所有的LayoutParams.

同時,它還維護了兩個全域性IBinder物件,用於訪問WindowManagerService 提供的兩套介面:

  • IWindowManager: 主要介面是OpenSession(), 用於在WindowManagerService內部建立和初始化Session, 並返回IBinder物件。
  • ISession: 是Activity WindowWindowManagerService 進行對話的主要介面.

ViewRootImpl:
ViewRootImpl 在整個Android的GUI系統中佔據非常重要的位置,如果把ActivityView 看作 ‘MVC’ 中的V, 把各種後臺服務看作ModalViewRootImpl 則是’MVC’ 中的’C’ - Controller. Controller在MVC架構裡承擔著承上啟下的作用,一般來說它的邏輯最為複雜。從下圖可以看到,ViewRootImpl 與 使用者輸入系統(接收使用者按鍵,觸控式螢幕輸入), 視窗系統(複雜視窗的佈局,重新整理,動畫),顯示合成系統(包括定時器Choreograph, SurfaceFlinger), 乃至Audio系統(音效輸出)等均有密切的關聯。研究ViewRootImpl 是研究Android整個視窗系統的核心和切入點,我們將在後面詳細討論ViewRootImpl的實現和作用。
在這裡插入圖片描述
三 者( ViewRootImpl, WindowManagerImpl, WindowManagerGlobal) 都存在於應用(有Activity)的程式空間裡,一個Activity對應一個WindowManagerImpl, 一個DecorView(ViewRoot),以及一個ViewRootImpl (上面說過,實際一個Activity只有一個DecorView),而WindowManagerGlobals是一個全域性物件,一個應用永遠只有一 個。

注意的是,在某些情況下,一個應用可能會有幾個ViewRootImpl物件,比如說ANR是彈出的對話方塊,或是網頁裡面一個視訊視窗 (SurfaceView), 在WindowManagerService看來,它們也是一個視窗。同時,SystemServer的程式空間也有自己的 WindowManagerGlobals 和若干個ViewRoot, 因為WindowManagerService 內部也會管理某些系統視窗,如手機頂部的StatusBar, 手機底部的NavigationBar, 以及 鎖屏(KeyGuard)視窗,這些視窗不屬於某個特定的Activity

4. WindowManager, WindowManagerServiceWindowManagerPolicyService

WindowManager: 是一個介面類,定義了一些介面來管理Acitivity裡的視窗。WindowManager 是Android應用程式空間裡的一個物件,不提供IPC服務。

WindowManagerService: 是SystemServer程式裡的一個Service,它的主要功能有

  • 窗 口的顯示重新整理。這裡的’Window’ 其實是ViewRoot, 和上面WindowManager管理的’Window'是不一樣的,前者是實實在在要進行顯示的‘視窗’,而後者只是一個View的容器,並不會顯示出來。大部分情況下,Android同時只有一個Activity工作,但這並不意思著只有一個Window被顯示,Android可能會同時顯示來自相同或不同應用的多個Window,比如說,螢幕的上方有一個狀態列,最下方有一個導航欄,有時會彈出一些對話方塊,背景可能會顯示牆紙,在應用啟動過程中,會有動畫效果,這個時候兩個Activity的視窗會有所變形且同時顯示出來,這一切都需要WindowManager來控制何時,何地,以何種方式將所有的視窗整合在一起顯示。
  • 預處理使用者輸入時間(GlobalKey? SystemKey), 並分發給合適的視窗進行處理。
  • 輸出顯示(Display)管理。包括WifiDisplay.

WindowManagerServiceAndroid Framework裡最為龐大複雜的模組之一,我們後面會從各個方面對它進行儘可能詳細的分析。

5. Token, WindowToken, AppWindowToken, ApplicationToken, appToken

Token在英語中表示標記,信物的意思,在程式碼中,有點類似HandleCookie, ID, 用來標識某個特定的物件。在Android的視窗系統中,有很多的’Token’, 它們代表著不同的含義。

WindowToken: 是在WindowManagerService 中定義的一個基類,顧名思義,它是用來標識某一個視窗。和下面的appWindowToken相比, 它不屬於某個特定的Activity, 比如說輸入法視窗,狀態列視窗等等。

appWindowToken: 顧名思義,它是用來標識app, 跟準確的說法,是用來標識某個具體的Activity.

ApplicationToken: 指的是ActivityRecord 類裡的Token子類。appWindowToken裡的appToken也就是它。

appToken: 和applicationToken是一個意思。

下圖描繪了各個Token之間的關係。一個Token下面帶一個WindowList佇列,裡面存放著隸屬與這個Token的所有視窗。當一個Window 加入WindowManagerService 管理時,必須指定他的Token值,WindowManagerService維護著一個TokenWindowState的鍵值Hash表。
在這裡插入圖片描述

通過 ‘dumpsys window tokens’ 我們可以列出WindowManagerService當前所有的Token 和 視窗。比如,

WINDOW MANAGER TOKENS (dumpsys window tokens)
  All tokens:
  WindowToken{4ea639c4 null}: //token = NULL
    windows=[Window{4ea7670c u0 Application Not Responding: jackpal.androidterm}, Window{4ea63a08 u0 Keyguard}]
    windowType=-1 hidden=false hasVisible=true
  AppWindowToken{4eb29760 token=Token{4eb289d4 ActivityRecord{4ea87a20 u0 com.android.launcher/com.android.launcher2.Launcher}}}: //Launcher2
    windows=[Window{4ea837c8 u0 com.android.launcher/com.android.launcher2.Launcher}]
    windowType=2 hidden=true hasVisible=true
    ...
  WindowToken{4eb1fd48 android.os.BinderProxy@4eae8a5c}:
    windows=[Window{4ea92b78 u0 PopupWindow:4ea0240c}]  //對話方塊
    windowType=-1 hidden=false hasVisible=false
  AppWindowToken{4eb5d6c0 token=Token{4ea35074 ActivityRecord{4ea68590 u0 jackpal.androidterm/.Term}}}:
    windows=[Window{4eb314e4 u0 jackpal.androidterm/jackpal.androidterm.Term}]
    windowType=2 hidden=false hasVisible=true
    app=true

6. Surface, LayerCanvas, SurfaceFlinger, Region,LayerStack

在Android中,WindowSurface一一對應。 如果說Window關心的是層次和佈局,是從設計者角度定義的類,Surface則從實現角度出發,是工程師關係和考慮的類。Window的內容是變化 的,Surface需要有空間來記錄每個時刻Window的內容。在Android的SurfaceFlinger實現裡,通常一個Surface有兩塊 Buffer, 一塊用於繪畫,一塊用於顯示,兩個Buffer按照固定的頻率進行交換,從而實現Window的動態重新整理。

LayerSurfaceFlinger 進行合成的基本操作單元。Layer在應用請求建立Surface的時候在SurfaceFlinger內部建立,因此一個Surface對應一個 Layer, 但注意,Surface不一定對應於WindowAndroid中有些Surface並不跟某個Window相關,而是有程式直接建立,比如說 StrictMode, 一塊紅色的背景,用於提示示Java程式碼中的一些異常, 還有SurfaceView, 用於顯示有硬體輸出的視訊內容等。

當多個Layer進行合成的時候,並不是整個Layer的空間都會被完全顯示,根據這個Layer最終的顯示效果,一個Layer可以被劃分成很多的Region, Android SurfaceFlinger 定義了以下一些Region型別:

  • TransparantRegion: 完全透明的區域,在它之下的區域將被顯示出來。
  • OpaqueRegion: 完全不透明的區域,是否顯示取決於它上面是否有遮擋或是否透明。
  • VisibleRegion: 可見區域,包括完全不透明無遮擋區域或半透明區域。 visibleRegion = Region -above OpaqueRegion.
  • CoveredRegion: 被遮擋區域,在它之上,有不透明或半透明區域。
  • DirtyRegion: 可見部分改變區域,包括新的被遮擋區域,和新的露出區域。

Android 系統支援多種顯示裝置,比如說,輸出到手機螢幕,或者通過WiFi 投射到電視螢幕。AndroidDisplay類來表示這樣的裝置。不是所有的Layer都會輸出到所有的Display, 比如說,我們可以只將Video Layer投射到電視, 而非整個螢幕。LayerStack 就是為此設 計,LayerStack 是一個Display 物件的一個數值, 而類Layer裡也有成員變數mLayerStack, 只有兩者的mLayerStack 值相同,Layer才會被輸出到給該Display裝置。所以LayerStack 決定了每個Display裝置上可以顯示的Layer數目。

SurfaceFlinger的工作內容,就是定期檢查所有Layer的引數更新(LayerStack等),計算新的DirtyRegion, 然後將結果推送給底層顯示驅動進行顯示。這裡面有很多的細節,我們將在另外的章節專門研究。

上面描述的幾個概念,均是針對於顯示這個層面,更多是涉及到中下層模組,應用層並不參與也無需關心。對於應用而言,它關心的是如何將內容畫出來。Canvas 是Java層定義的一個類,它對應與Surface上的某個區域並提供了很多的2D繪製函式(藉助於底層的SkiaOpenGL)。應用只需通過 LockCanvas() 來獲取一個Canvas物件,並呼叫它的繪畫方法,然後 unLockCanvasAndPost()來通知底層將更新內容進行顯示。當然,並不是所有應用程式都需要直接操作Canva, 事實上只有少量應用需要直接操作Canvas, Android提供了很多封裝好的控制元件 Widget,應用只需提供素材,如文字,圖片,屬性等等,這些控制元件會呼叫Canvas提供的介面幫使用者完成繪製工作。

7. SurfaceFlinger, HWComposer, OpenGLDisplay

SurfaceFlinger 是一個獨立的Service, 它接收所有WindowSurface作為輸入,根據ZOrder, 透明度,大小,位置等引數,計算出每個Surface在最終合成影象中的位置,然後交由HWComposerOpenGL生成最終的顯示Buffer, 然後顯示到特定的顯示裝置上。

HWComposer 是 Andrid 4.0後推出的新特性,它定義一套HAL層介面,然後各個晶片廠商根據各種硬體特點來實現。它的主要工作是將SurfaceFlinger計算好的Layer的顯示引數最終合成到一個顯示Buffer上。注意的是,Surface Flinger 並非是HWComposer的唯一輸入,有的Surface 不由Android的WindowManager 管理,比如說攝像頭的預覽輸入Buffer, 可以有硬體直接寫入,然後作為HWComposer的輸入之一與SurfaceFlinger的輸出做最後的合成。

OpenGL 是一個2D/3D圖形庫,需要底層硬體(GPU)和驅動的支援。在Android 4.0後,它取代Skia成為Android 的2D 繪圖圖形庫,大部分的控制元件均改用它來實現,應用程式也可以直接呼叫OpenGl函式來實現複雜的圖形介面。

Display 是Android對輸出顯示裝置的一個抽象,傳統的Display 裝置是手機上的LCD屏,在Andrid 4.1 後,AndroidSurfaceFlinger 進行了大量的改動,從而支援其他外部輸入裝置,比如HDMIWifi Display 等等。Display的輸入是根據上面的LayerStack值進行過濾的所有WindowSurface, 輸出是和顯示裝置尺寸相同的Buffer, 這個Buffer 最終送到了硬體的FB裝置,或者HDMI裝置,或者遠處的Wifi Display Sink裝置進行顯示。輸入到輸出這條路徑上有SurfaceFlinger, OpenGLHWComposer

有了上述概念的解析,對Android的GUI 系統應該有了一些模糊的認識,接下來我們將按下面的順序將一步步深入其中的細節。

相關文章