聊聊 Android 的 GUI 系統

你長得辣麼好看,我想着要更詳細地瞭解你。今天,讓咱們一塊兒來聊聊 Android 的 GUI 系統。java

緣起

在2019年的 Google I/O 大會上,Jetpack 團隊首次爲你們介紹了 Jetpack Compose,這是一種全新的 Android UI 組件庫。當時演講者爲你們分享了一張圖,描述了 Android 10 年裏的在 UI 方面簡要發展歷史,在長達 10 年的發展過程當中,Google 針對不一樣的問題作出了不少的調整,可是惟獨在 UI 構建方面,最初的那一套 UI 構建體系一直沿用至今,幾乎沒有作任何調整。android

Compose 能夠說是 UI 體系的一種顛覆,固然,我今天並非來推銷 Jetpack Compose 的,而是由於我忽然間發現,Android 誕生了這麼長時間,本身也作了辣麼長時間的 UI boy,但是若是你要我立馬說出 View 小姐姐是怎麼在屏幕上給展現出來的,我竟無語凝噎。本想着雨露均沾,結果是萬花叢中過,片葉不沾身,這怎麼能忍!看着UI 小姐姐那真摯的眼神,不給它扒一扒感受都是一種罪惡。git

目標

但願經過此次梳理,能對 Android 總體的 View 框架體系大體流程上能有清晰的認識。起於 App 層,止於驅動層,而且從中挑一些重要的內容來說述,方便理清衆多對象之間的關係脈絡,從而在總體架構上能有比較清晰的認知。這樣,在閱讀源碼細節時候不至於發出哲學三連問——我是誰?我從哪兒來?要到那兒去?設計模式

那麼,開搞!api

我是 Activity

我是一名交際花,專一于于 UI 界面顯示和處理,是應用程序中各組件里人氣最高的偶像之一。我在江湖中能有如此地位,那還得多虧了 Android 爸爸對我不吝的包裝。對於開發者來講,只須要簡單的調用setContentView、onCreate、onStart 等方法,我就能將他們想要顯示的內容展示出來。很簡單是吧,由於我是整個UI體系中離開發者最近的一個窗口了,只有讓開發者用起來足夠爽了,他們纔會喜歡上我啊,因此呢,Android 爸爸也是對我花了不少當心思呢。我呢,將一系列生命週期相關的回調用模板方法模式的一種設計模式封裝,而後暴露給開發者,至於一些那些粗活累活我就彙報給 Android 爸爸去處理,畢竟做爲一個 idol ,人設是萬萬不能倒的。好比像 setContentView 這種大部分狀況下只是傳遞了一個 xml 的佈局的傢伙,又要解析 View tree,又要構建的,想一想都麻煩,我就很機智的交給 framework 去處理了。安全

你別看我多風光的樣子,可是本質上,我也只是一個 window 而已啦。markdown

我是View

我是 app 層面向開發者比較核心的 UI 相關類,目前我在源碼中的實現接近 3W 行。我呢還有一個優秀的 child ,名字叫 ViewGroup。ViewGroup 經過組合模式,而可以在自身內部存在更多的 View 或 ViewGroup,這樣一來,從結構上看,咱們像是俄羅斯套娃,你中有我,我中有你。其實除了 View 和 ViewGroup 這些家喻戶曉的明星成員外,View 家族中還有 ViewParent 、ViewRootImpl 這些重要的幕後成員,你可千萬別覺得 ViewParent 就是個人爹地,它雖然叫 ViewParent 可是它就是一隔壁老王,和我一毛錢關係也沒有。雖然我和 ViewParent 清清白白的,可是 ViewGroup 和 ViewRootImpl 都實現了 ViewParent 的接口方法。架構

Activity 的setContentView()本質是要將 DoctorView,也就是 View 樹的根設置到 ViewRootImpl 中。ViewRootImpl 發起遍歷(調用performTraversals()函數) 後,各個 View 元素就能獲得系統的最終「分配結果」。這個「分配結果」至少會包含兩個方的內容:View 對象的尺寸大小位置,再加上 View 自身的 UI 內容,如此便構成了 UI 顯示的基本三要素。而這重要的三要素,它們在遍歷的過程當中分別對應如下三個函數:app

  • performMeasure 用於計算 View 對象在 UI 界面上的尺寸位置,對應 View 的onMeasure
  • performLayout 用於計算 View 對象在 UI 界面上的繪圖位置。對應 View 的 onLayout
  • performDraw 上述兩個屬性肯定後,View 對象就能夠在此基礎上繪製 UI 了。對應 View 的 onDraw

上面這三個函數是在 ViewRootImpl 中展開的,對於開發者來講,咱們面對更多的則是 View 與 ViewGroup 以及它們的子類,下面是 View 相關的一些生命週期回調:框架

  • measure
測量該控件的大小 ,若是是ViewGroup還需測量子控件大小,measureChildren或調用子控件的measure來觸發子控件元素的onMeasure方法
  • layout
當View分配全部的子元素的大小和位置時,在onLayout方法被調用以前getWidth(), getHeight()是獲取不 到控件的大小
  • draw
view渲染內容
  • dispatchDraw
在onDraw以後會調用此方法,分發子元素繪製,主要是針對ViewGroup。ViewGroup容器組件的繪製,當它沒有背景時直接調用的是dispatchDraw()方法, 而不執行draw()方法,當它有背景的時候就調用draw()方法,而draw()方法裏包含了dispatchDraw()方法的調用。所以要在ViewGroup上繪製東西的時候每每重寫的是dispatchDraw()方法而不是onDraw()方法

我是 Window

我是在應用框架層,被 java 封裝的用來展現窗口的一個抽象類。我負責可視化內容的排版。Android 支持的窗口類型不少,不過咱們能夠統一劃分爲三大類,即 Application Window、System Window 和 Sub Window。另外各個種類下還細分爲若干子類型,這些都是在個人上司 WindowManager 經過進程通訊的方式,去與後臺服務 WindowManagerService 通訊,最終遞交到 SurfaceFlinger 來輸出和呈現。

從用戶的角度來講,我就是一個界面;從 SurfaceFlinger 的角度來講,我是一個 Layer ,承載着和界面有關的數據和屬性;從 WMS 來講,我是一個 WindowState ,用於管理和界面有關的狀態。

窗口類型與層級

Application Window 這類窗口對應應用程序的窗口,取值在 1-99 之間

Type Description
FIRST_APPLICATION_WINDOW = 1 應用程序窗口的起始值
TYPE_BASE_APPLICATION = 1 應用程序窗口的基礎值
TYPE_APPLICATION = 2 普通應用程序的窗口類型
TYPE_APPLICATION_STARTING = 3 應用程序的啓動窗口類型。它不能由應用程序自己使用,而是Android 系統爲應用程序啓動前設計的窗口,當真正的窗口啓動後它就消失了
TYPE_DRAWN_APPLICATION = 4 用於確保應用程序窗口在顯示時已經完成了繪製
LAST_APPLICATION_WINDOW = 99 應用程序窗口的最大值

Sub Window 這類窗口將附着在其餘 Window 中,取值在 1000 到 1999 之間

Type Description
FIRST_SUB_WINDOW = 1000 子窗口的起始值
TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW 應用程序的 panel 子窗口,在它的父窗口之上顯示
TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1 用於顯示多媒體內容的子窗口,位於父窗口之下
TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2 也是一種 panel 子窗口,位於父窗口以及全部 TYPE_APPLICATION_PANEL 之上
TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3 Dialog 子窗口,如 menu 類型
TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4 多媒體窗口的覆蓋層,位於 TYPE_APPLICATION_MEDIA 和應用程序窗口之間,一般透明纔有意義。此類型屬於未開放狀態
LAST_SUB_WINDOW = 1999 子窗口的最大值

System Window 對應系統程序採用的窗口類型,取值在 2000 到 2999 之間

Type Description
FIRST_SYSTEM_WINDOW = 2000 系統窗口的起始值
TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW 系統狀態欄窗口
TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1 系統搜索條窗口
TYPE_PHONE = FIRST_SYSTEM_WINDOW+2 通話窗口
TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3 Alert窗口,如電量不足的提示框
TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4 屏保窗口
TYPE_TOAST = FIRST_SYSTEM_WINDOW+5 短暫的提示框窗口
TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6 系統覆蓋窗口,這種類型的窗口不能接收 input 事件
TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7 電話優先窗口
TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8 RecentsAppDialog 就是這種類型的窗口
TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9 屏保時顯示的對話框
TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10 系統錯誤窗口
TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11 輸入法窗口
TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12 顯示在輸入法之上的對話框窗口
TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13 壁紙窗口
TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14 滑動狀態欄出現的窗口
YPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19 導航欄窗口
TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20 系統音量條
TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21 開機啓動的進度條窗口
TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22 導航欄隱藏時用於消耗事件的僞窗口
LAST_SYSTEM_WINDOW = 2999 系統窗口結束

當某個進程向 WMS 申請一個窗口時,它須要指定所需窗口類型,而後 WMS 根據用戶申請的窗口類型以及當前系統中已有窗口的狀況來給它分配一個最終的層級值,數值越大的窗口,優先級越高,在屏幕上顯示時候就越靠近用戶。

窗口屬性

除了窗口類型外,開發者還能夠設置不一樣的屬性來調整窗口的表現,這些屬性統一放置在 WindowManager.LayoutParams 中。其中主要包括如下幾個重要的變量:

  • Type 也就是上面的窗口類型

  • Flag 窗口標誌,默認爲0

    Flags Description
    FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001 只要此窗口可見,即使屏幕處於開啓狀態也容許鎖屏
    FLAG_DIM_BEHIND = 0x00000002 在窗口後面的全部東西都將變暗淡
    FLAG_NOT_FOCUSABLE = 0x00000008 此窗口不得到輸入焦點,意味着事件將發給該窗口後面的其餘窗口。在設置了此標誌的同時,FLAG_NOT_TOUCH_MODAL 也會同時被設置
    FLAG_NOT_TOUCHABLE = 0x00000010 表示該窗口不接受任何觸摸事件
    FLAG_NOT_TOUCH_MODAL = 0x00000020 無模式的窗口
    FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040 當設備進入休眠狀態時,設置此標誌可使你得到第一次的觸摸事件
    FLAG_KEEP_SCREEN_ON = 0x00000080 只要這個窗口可見,屏幕就亮着
    FLAG_LAYOUT_IN_SCREEN = 0x00000100 窗口顯示時候不考慮系統裝飾框,好比 Status Bar
    LAG_LAYOUT_NO_LIMITS = 0x00000200 容許窗口超過屏幕區域
    FLAG_FULLSCREEN = 0x00000400 隱藏全部的屏幕裝飾窗口
    FLAG_FORCE_NOT_FULLSCREEN = 0x00000800 和 FLAG_FULLSCREEN 正好相反
    FLAG_SECURE = 0x00002000 窗口類容被認爲是保密的,於是它不會出如今截屏中,也不會再不安全的屏幕上顯示
    FLAG_SCALED = 0x00004000; 按照用戶提供的參數作相應的縮放
    FLAG_IGNORE_CHEEK_PRESSES = 0x00008000 有些時候用戶和屏幕會貼的很近,好比打電話時候。這種狀況下出現的某些事件多是無心的,不該該響應
    FLAG_SHOW_WHEN_LOCKED = 0x00080000 使窗口能在鎖屏窗口之上
    FLAG_SHOW_WALLPAPER = 0x00100000 讓壁紙在這個窗口以後顯示。當窗口是透明或者半透明時候就能夠看到後面的壁紙,如 Launcher
    FLAG_TURN_SCREEN_ON = 0x00200000 窗口顯示時將屏幕點亮
    FLAG_DISMISS_KEYGUARD = 0x00400000 設置這個標誌能夠解除屏幕鎖,可是不能解除 secure lock
  • systemUiVisibility 表示系統 UI 的可見性

Flags Description
SYSTEM_UI_FLAG_VISIBLE = 0 請求顯示系統UI,默認狀態
SYSTEM_UI_FLAG_LOW_PROFILE = 0x00000001 低能模式,狀態欄上的一些圖標會被隱藏,遊戲、閱讀、視頻播放等沉浸式應用會須要
SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002 請求隱藏底部導航欄
SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004 請求全屏顯示,狀態欄會被隱藏,底部導航欄不會被隱藏,效果和WindowManager.LayoutParams.FLAG_FULLSCREEN相同
SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800 這個flag只有當設置了SYSTEM_UI_FLAG_HIDE_NAVIGATION才起做用。若是沒有設置這個flag,任意的View相互動做都退出SYSTEM_UI_FLAG_HIDE_NAVIGATION模式。若是設置就不會退出
SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000 這個flag只有當設置了SYSTEM_UI_FLAG_FULLSCREEN|SYSTEM_UI_FLAG_HIDE_NAVIGATION時才起做用。若是沒有設置這個flag,任意的View相互動做都會退出SYSTEM_UI_FLAG_FULLSCREEN|SYSTEM_UI_FLAG_HIDE_NAVIGATION模式,若是設置就不受影響
SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000 狀態欄淺色背景模式,文字爲黑色,Android 6.0之前(api < 23)不支持
SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100 請求系統UI佈局穩定狀態
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400 讓View全屏顯示,Layout會被拉伸到StatusBar下面,不包含NavigationBar
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200 讓View全屏顯示,Layout會被拉伸到NavigationBar下面

上面這些屬性除了 systemUiVisibility 相關的是定義在 View 中的,其餘的都是定義在 WindowManager 中的

我是WindowManager

我是一個繼承於 ViewManager 的接口,WindowManagerImpl 是個人具體實現類。ViewManager 中定義了與 View 交互的接口函數 addView()updateViewLayout()removeView() ,應用程序經過(WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)獲取到 WindowManager 實例後,就能夠經過addView()將 View 添加到 WMS 中去。

可是我只是一個接口啊,就連個人實現者 WindowManagerImpl 也沒有任何持有 WSM 的影子啊。那 View 是如何添加到 WMS 中去的呢?既然咱們兩個無法暗通款曲,那就索性尋個媒婆來明媒正娶。這點,ViewRootImpl 是極其專業的,因而我便找了它。

我是ViewRootImpl

我是一箇中介,負責管理整顆 View 樹的同時,也擔負着與 WMS 進行 IPC 通信的重任。具體而言,我會經過 IWindowSession 創建雙方的橋樑。

從實現上來講,在構造函數中,我會經過 WindowManagerGlobal.getWindowSession()來打開一個IWindowSession 對象來與 WMS 的可用鏈接。IWindowSession 是一個 IBinder 接口,它定義了一系列與 window manager 交互的交互方式,如此一來,當應用程序調用 setView 等方法時,我就能夠利用它來發起一個服務請求。 IWindowSession 的服務端(Session.java)便會響應這個請求,從而調用 WMS 的 addWindow()來傳遞給 WMS 處理。

我是WindowManagerService

我和 AMS 等 Service 同樣,是由 SystemServer 啓動的系統服務的一部分。因爲我是由 SystemServer 啓動的,啓動時機相對較晚,若是在 SystemServer 還沒運行以前,我是無能爲力的。好比在開機時候顯示的開機動畫,那時候我還沒運行起來,因此這時候的顯示則是由 BootAnimation 直接經過 OpenGL ES 與SurfaceFlinger 的配合來完成的。原則上我只負責「窗口」的層級和屬性,之因此可以將 Window 內容顯示出來,也是因爲我與 SurfaceFlinger 溝通後,SufaceFlinger 才真正將窗口數據合成並最終顯示在屏幕上的緣故。

從某種方面來講,我但是整個 Android UI 體系的大導演呢,由於我會根據實際狀況來安排每一個演員(Window)的排序站位,誰前誰後,怎麼進場,如何出場等,目的固然也是爲了將舞臺效果和視覺美感表現得更佳,從而呈現給觀衆。我並不關心這裏面的演員是誰,從源碼角度來講,我不關心 View 樹,或者說這個 window 所表達的具體類容是什麼,我只要知道須要顯示的界面大小,層級值等便可,而這些已經做爲 WindowManager.LayoutParams 參數傳遞給我了。

前面說到,WMS 還須要通知 SurfaceFlinger,才能把正確的結果及時的呈現給「觀衆」。因爲 SurfaceFlinger 繪製 UI 界面須要有「畫板」—— BufferQueue 的支持,BufferQueue 在 SurfaceFlinger 中對應的是 Layer,在 Java 層對應的則是 Surface( Surface 持有 BufferQueue 的實現接口—— IGraphicBufferProducer ),所以,不管是系統窗口仍是應用窗口,都必須向 SurfaceFlinger 申請相應的 Layer,進而獲得圖形緩衝區的使用權。

WMS、AMS 與 Activity 間的聯繫

Activity 運行在應用程序進程中,而 AMS 與WMS 則運行在系統相關進程中,它們之間的通訊須要 Binder 的支持。應用程序訪問 WMS 的服務首先要經過 ServiceManager,由於 WMS 是實名 Binder Server;WMS 還針對每一個 Activity 提供了一種匿名的實現,即 IWindowSession。

當一個新的 Activity 被啓動時候(startActivity),它首先須要在 AMS 中註冊——此時 AMS 會生成一個 ActivityRecord 來記錄這個 Activity ;另外,Activity 還承載着 UI 顯示的功能,因此 WMS 也會對它進行記錄——以 WindowState 來表示。WMS 除了利用 WindowState 來保存一個窗口相關的信息外,還使用 AppWindowToken 來對應 AMS 中的一個 ActivityRecord,從而將三者造成很是緊密的聯繫。

vsync_1

我是Surface

Surface 對應了一塊屏幕緩衝區,每一個 window 對應一個Surface,任何 View 都是畫在 Surface 上的,傳統的 view 共享一塊屏幕緩衝區

我有一個龐大的家族體系,站在臺前的 Android 爲咱們封裝的處於 java 層面的 Surface,咱們家族的幕後長老們同時也在 native 層默默貢獻者他們的力量。在 Surface.java中 Android 是這樣定義個人 Handle onto a raw buffer that is being managed by the screen compositor. 由此能夠看出,首先我是一個 raw buffer(屏幕緩衝區)的句柄,能夠經過我來管理一個 raw buffer ;其次,我自己又被一個叫 screen compositor 的傢伙在管理。同時,我內部持有 IGraphicBufferProducer,而這個 IGraphicBufferProducer 則是 BufferQueue 的實現接口,如此我便又和 BufferQueue 搞上了。

前面說到,WMS 想要將內容展現出來,須要個人支持,具體的,以 addView 來講,我是在 ViewRoot 進行 performTraversals 時,向 WMS 申請一個 Surface 時誕生的。WMS 在建立 Surface 時,會生成一個 SurfaceSession ,而後將這個 SurfaceSession 做爲參數來構造 Surface。這個 SurfaceSession 就是 screen compositor 的一個會話連接。同時,在 java 層面上的 Surface 和 SurfaceSession 構造的時候,都會調用具體的 init 方法,喚醒咱們在 native 層的長老們,他們主要彙集 framework/native/libs/gui 這個」山洞「中。

下面羅列的是其中涉及到的一些比較重要的成員和職責:

  • ISurfaceComposer:經過這個接口能夠訪問到 SurfaceFlinger,能夠經過它創建一個會話,即ISurfaceComposerClient,也能夠經過它去更新 Surface 的相關信息,這個是經過setTransactionState接口完成的,表明一個到 SurfaceFinger 的會話鏈接
  • SurfaceComposerClient:是 SurfaceFlinger 派出的「表明」,不管是 OpenGL ES 仍是 Surface,均可以在這個類的協助下有序地申請和訪問各 Buffer 緩衝區。持有 ISurfaceComposerClient 的客戶端代理,在SurfaceComposerClient 初次實例化時,經過 ISurfaceComposer 的createConnection()接口獲得一個ISurfaceComposerClient 的代理。同時,它也會管理 Surface 的狀態,經過 ISurfaceComposer 更新Surface 狀態
  • SurfaceControl:從字面上看,其做用是控制 Surface。其實際做用是持有 ISurface 的代理及SurfaceComposerClient
  • ISurfaceTexture:對應具體的 buffer 管理
  • ANativeWindow:持有 ISurfaceTexture 的本地代理,經過它能夠訪問到 ISurfaceTexture 的實現。同時它繼承了 ANativeWindow,而 Surface 類會繼承 SurfaceTextureClient. ANativeWindow 表明的本地窗口
  • GraphicBuffer:GraphicBuffer 是一個 ANativeWindowBuffer,每個GraphicBuffer 內部都包含有一塊用來保存UI數據的緩衝區,它實際存儲空間實際上是在 ashmem 上的,具體是gralloc模塊來完成分配的,而後映射到應用程序的進程地址空間

有了 Surface ,即可以獲得一塊屏幕緩衝區,可是這時咱們的視圖仍是不能呈如今觀衆眼前的。因而便要將 Surface 添加到 BufferQueue 中,從而讓 SufaceFlinger 來消費。

我是BufferQueue

我是一名勤勤懇懇的老師,我對每一個應用程序都進行「一對一在線輔導」,指導着 UI 程序的 「申請畫板」、「做畫流程」等一系列的繁瑣細節。我與各應用程序是經過 IGraphicBufferProducer 創建關係的。

BufferQueue 是 Android 顯示系統的核心,它的設計哲學是生產者-消費者模型,只要往 BufferQueue 中填充數據,則認爲是生產者,只要從 BufferQueue中獲取數據,則認爲是消費者。有時候同一個類,在不一樣的場景下既多是生產者也有多是消費者。如 SurfaceFlinger,在合成並顯示 UI 內容時,UI 元素做爲生產者生產內容,SurfaceFlinger 做爲消費者消費這些內容。而在截屏時,SurfaceFlinger 又做爲生產者將當前合成顯示的UI內容填充到另外一個 BufferQueue,截屏應用此時做爲消費者從 BufferQueue 中獲取數據並生產截圖。

站在應用程序的角度來講,應用程序能夠調用 createSurface 來創建多個 Layer,每個 Layer 都對應一個 BufferQueue,換句話說,應用程序與 BufferQueue 也是一對多的關係。爲應用程序申請的 Layer,一方面須要告知 SurfaceFlinger,另外一方面也要記錄到各 Client 內部中。另外,Layer 也沒有直接持有 BufferQueue 對象,而是經過 Layer 內部的 mSurfaceFlingerConsumer 來管理的。

我是SufaceFlinger

我是由 init 進程所啓動的守護進程,運行在Android系統的 System 進程中,負責管理Android系統的幀緩衝區(Frame Buffer),須要顯示 UI 界面的應用程序須要經過 Binder 服務來與我通訊。每一個有 UI 界面的程序都在我這裏有相對應的 Client 實例。應用程序與 Client 間的 Binder 接口是 ISurfaceComposerClient。Client 也只是我分配給應用程序的一個」表明「 ,真正的圖行(Buffer)須要另外申請,即調用 Client 提供的 ISurfaceComposerClient::createSurface()來實現。同時,在 SufaceFlinger 進程中將會有一個 Layer 被建立,表明了一個畫面。ISurface 就是控制這一面的handle,它將保持在應用程序端的 SufaceControl 中。

事實上,我是一個耿直boy,你看個人名字就知道,個人職責是 Flinger,即把系統中因此應用程序最終的「繪圖結果」進行「混合」,而後統一顯示到物理屏幕上。因此我不會太關注各個應用程序的「繪畫過程」,因而我又派出了一個「表明」——BufferQueue 替我去完成這一光榮的使命。

如今萬事俱備,只欠東風,我就能夠鉚足幹勁嘩啦啦的繪製了,觀衆也就能看到美輪美奐的"節目"了。那東風從哪來?又要到哪去?這時候就輪到咱們勤勤懇懇的快遞員選手——VSync 大展身手了。

我是VSync

谷歌在4.1版本引入了一個重大的改進——Project Butter,也便是黃油計劃。Project Butter 對 Android Display 系統進行了重構,引入了三個核心元素,即 VSYNC、Triple Buffer 和 Choreographer。

安卓系統中有 2 種 VSync 信號:屏幕產生的硬件 VSync 和由 SurfaceFlinger 將其轉成的軟件 Vsync 信號。採用 Vsync 進行顯示同步,一旦 Vsync 信號出現,CPU 便當即開始執行 Buffer 的準備工做。目前 Android 是採用 Multiple Buffer 的技術來處理的。

沒有引入vsync的狀況

上圖是沒有引入VSync 機制的處理流程。能夠看出,一個很明顯的問題是,只要一次cpu/gpu 處理出現異常就可能致使後面的一系列的處理出現異常

引入VSync 機制

上圖是引入 VSync 機制的後的處理流程。在 FPS < 手機屏幕刷新率的狀況下,一切運行完美

Double Buffering 異常狀況

上圖是在 VSync 機制下,Double Buffering 時 FPS > 手機屏幕刷新率的狀況。只要出現一次 Jank 就會影響下一次的 VSync (cpu 不能工做)

Triple Buffering 異常狀況

上圖是在 VSync 機制下,Triple Buffering 時FPS > 手機屏幕刷新率的狀況。當第一次 VSync 發生後,CPU 不用再等待了,除了第一次的 Jank 沒法規避,第二次、第三次 VSync 到來時都能有效採用到 buffer,從而有效下降了系統顯示錯誤。

VSync 最終會被 EventThread::threadLoop()分發給各監聽者,如 SurfaceFlinger 進程中就是 MessageQueue 。VSync 被 SurfaceFlinger 監聽到後,SurfaceFlinger 首先須要遍歷 當前 Layer (這裏的 Layer 對應的則是 BufferQueue) ,肯定是否須要重繪。對應 Z-order 等與編排相關的 SurfaceFlinger能夠本身肯定,可是對於各個 Buffer 內容的變更,仍是須要更加專業的 BufferQueue 來處理了。BufferQueue 處理完成,而且將結果返回給 SurfaceFlinger 後,再由 SurfaceFlinger 進行「加工混合」,交由 OpenGL ES 顯示出來 。

我是Choreographer

字面翻譯過來,我是編舞者的意思。具體來說,我主要是配合 Vsync(由於我能夠監聽底層Vsync信號) ,給上層 App 的渲染提供一個穩定的 Message 處理的時機。

ViewRootImpl 啓動時會初始化 Choreographer 的實例。

當 Vsync 信號由 SurfaceFlinger 中建立 HWC 觸發,喚醒 DispSyncThread 線程,再到 EventThread 線程,而後再經過 BitTube(一種進程間通訊的一種機制) 直接傳遞到目標進程所對應的目標線程,執行 handleEvent方法 ,而後經過 C++ 層的 dispatchVsync 進入到 java 層的 dispatchVsync 回調,觸發FrameDisplayEventReceiver.run() 如此 Choreographer 便接收到了消息,doFrame()執行,UI 繪製開始。

參考文獻
相關文章
相關標籤/搜索