首頁>科技>

你長得辣麼好看,我想著要更詳細地了解你。今天,讓我們一起來聊聊 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 的onMeasureperformLayout 用於計算 View 物件在 UI 介面上的繪圖位置。對應 View 的 onLayoutperformDraw 上述兩個屬性確定後,View 物件就可以在此基礎上繪製 UI 了。對應 View 的 onDraw

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

measure測量該控制元件的大小 ,如果是ViewGroup還需測量子控制元件大小,measureChildren或呼叫子控制元件的measure來觸發子控制元件元素的onMeasure方法layout當View分配所有的子元素的大小和位置時,在onLayout方法被呼叫之前getWidth(), getHeight()是獲取不 到控制元件的大小drawview渲染內容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 locksystemUiVisibility 表示系統 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 的代理及SurfaceComposerClientISurfaceTexture:對應具體的 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來源:掘金著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

最新評論
  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 10幾行程式碼,用python打造實時截圖識別OCR