你長得辣么好看,我想著要更詳細地了解你。今天,讓我們一起來聊聊 Android 的 GUI 系統。
緣起
在2019年的 google I/O 大會上,Jetpack 團隊首次為大家介紹了 Jetpack Compose,這是一種全新的 Android UI 組件庫。當時演講者為大家分享了一張圖,描述了 Android 10 年里的在 UI 方面簡要發展歷史,在長達 10 年的發展過程中,Google 針對不同的問題做出了很多的調整,但是唯獨在 UI 構建方面,最初的那一套 UI 構建體系一直沿用至今,幾乎沒有做任何調整。

Compose 可以說是 UI 體系的一種顛覆,當然,我今天并不是來推銷 Jetpack Compose 的,而是因為我突然間發現,Android 誕生了這么長時間,自己也做了辣么長時間的 UI boy,可是如果你要我立馬說出 View 小姐姐是怎么在屏幕上給展示出來的,我竟無語凝噎。本想著雨露均沾,結果是萬花叢中過,片葉不沾身,這怎么能忍!看著UI 小姐姐那真摯的眼神,不給它扒一扒感覺都是一種罪惡。
目標
希望通過這次梳理,能對 Android 整體的 View 框架體系大致流程上能有清晰的認識。起于 App 層,止于驅動層,并且從中挑一些重要的內容來講述,方便理清眾多對象之間的關系脈絡,從而在整體架構上能有比較清晰的認知。這樣,在閱讀源碼細節時候不至于發出哲學三連問——我是誰?我從哪兒來?要到那兒去?
那么,開搞!
我是 Activity
我是一名交際花,專注于于 UI 界面顯示和處理,是應用程序中各組件里人氣最高的偶像之一。我在江湖中能有如此地位,那還得多虧了 Android 爸爸對我不吝的包裝。對于開發者來說,只需要簡單的調用setContentView、onCreate、onStart 等方法,我就能將他們想要顯示的內容展現出來。很簡單是吧,因為我是整個UI體系中離開發者最近的一個窗口了,只有讓開發者用起來足夠爽了,他們才會喜歡上我啊,所以呢,Android 爸爸也是對我花了很多小心思呢。我呢,將一系列生命周期相關的回調用模板方法模式的一種設計模式封裝,然后暴露給開發者,至于一些那些粗活累活我就匯報給 Android 爸爸去處理,畢竟作為一個 idol ,人設是萬萬不能倒的。比如像 setContentView 這種大部分情況下只是傳遞了一個 xml 的布局的家伙,又要解析 View tree,又要構建的,想想都麻煩,我就很機智的交給 framework 去處理了。
你別看我多風光的樣子,但是本質上,我也只是一個 window 而已啦。
我是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 顯示的基本三要素。而這重要的三要素,它們在遍歷的過程中分別對應以下三個函數:
- 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,從而將三者形成非常緊密的聯系。
我是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 繪制開始。
參考文獻
- 《深入理解Android內核設計思想》 ——林學森著
- gityuan 老師相關博客
- KunMinX 老師的博客重學安卓:Activity 的快樂你不懂
- AOSP 10.0 源碼
- developer.android.com/
作者:joychic
鏈接:https://juejin.im/post/5e0ca9ccf265da5d4170e844
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。