你長得辣么好看,我想著要更詳細(xì)地了解你。今天,讓我們一起來聊聊 Android 的 GUI 系統(tǒng)。
緣起
在2019年的 google I/O 大會上,Jetpack 團(tuán)隊首次為大家介紹了 Jetpack Compose,這是一種全新的 Android UI 組件庫。當(dāng)時演講者為大家分享了一張圖,描述了 Android 10 年里的在 UI 方面簡要發(fā)展歷史,在長達(dá) 10 年的發(fā)展過程中,Google 針對不同的問題做出了很多的調(diào)整,但是唯獨(dú)在 UI 構(gòu)建方面,最初的那一套 UI 構(gòu)建體系一直沿用至今,幾乎沒有做任何調(diào)整。
Compose 可以說是 UI 體系的一種顛覆,當(dāng)然,我今天并不是來推銷 Jetpack Compose 的,而是因為我突然間發(fā)現(xiàn),Android 誕生了這么長時間,自己也做了辣么長時間的 UI boy,可是如果你要我立馬說出 View 小姐姐是怎么在屏幕上給展示出來的,我竟無語凝噎。本想著雨露均沾,結(jié)果是萬花叢中過,片葉不沾身,這怎么能忍!看著UI 小姐姐那真摯的眼神,不給它扒一扒感覺都是一種罪惡。
目標(biāo)
希望通過這次梳理,能對 Android 整體的 View 框架體系大致流程上能有清晰的認(rèn)識。起于 App 層,止于驅(qū)動層,并且從中挑一些重要的內(nèi)容來講述,方便理清眾多對象之間的關(guān)系脈絡(luò),從而在整體架構(gòu)上能有比較清晰的認(rèn)知。這樣,在閱讀源碼細(xì)節(jié)時候不至于發(fā)出哲學(xué)三連問——我是誰?我從哪兒來?要到那兒去?
那么,開搞!
我是 Activity
我是一名交際花,專注于于 UI 界面顯示和處理,是應(yīng)用程序中各組件里人氣最高的偶像之一。我在江湖中能有如此地位,那還得多虧了 Android 爸爸對我不吝的包裝。對于開發(fā)者來說,只需要簡單的調(diào)用setContentView、onCreate、onStart 等方法,我就能將他們想要顯示的內(nèi)容展現(xiàn)出來。很簡單是吧,因為我是整個UI體系中離開發(fā)者最近的一個窗口了,只有讓開發(fā)者用起來足夠爽了,他們才會喜歡上我啊,所以呢,Android 爸爸也是對我花了很多小心思呢。我呢,將一系列生命周期相關(guān)的回調(diào)用模板方法模式的一種設(shè)計模式封裝,然后暴露給開發(fā)者,至于一些那些粗活累活我就匯報給 Android 爸爸去處理,畢竟作為一個 idol ,人設(shè)是萬萬不能倒的。比如像 setContentView 這種大部分情況下只是傳遞了一個 xml 的布局的家伙,又要解析 View tree,又要構(gòu)建的,想想都麻煩,我就很機(jī)智的交給 framework 去處理了。
你別看我多風(fēng)光的樣子,但是本質(zhì)上,我也只是一個 window 而已啦。
我是View
我是 app 層面向開發(fā)者比較核心的 UI 相關(guān)類,目前我在源碼中的實現(xiàn)接近 3W 行。我呢還有一個優(yōu)秀的 child ,名字叫 ViewGroup。ViewGroup 通過組合模式,而能夠在自身內(nèi)部存在更多的 View 或 ViewGroup,這樣一來,從結(jié)構(gòu)上看,我們像是俄羅斯套娃,你中有我,我中有你。其實除了 View 和 ViewGroup 這些家喻戶曉的明星成員外,View 家族中還有 ViewParent 、ViewRootImpl 這些重要的幕后成員,你可千萬別以為 ViewParent 就是我的爹地,它雖然叫 ViewParent 但是它就是一隔壁老王,和我一毛錢關(guān)系也沒有。雖然我和 ViewParent 清清白白的,但是 ViewGroup 和 ViewRootImpl 都實現(xiàn)了 ViewParent 的接口方法。
Activity 的setContentView()本質(zhì)是要將 DoctorView,也就是 View 樹的根設(shè)置到 ViewRootImpl 中。ViewRootImpl 發(fā)起遍歷(調(diào)用performTraversals()函數(shù)) 后,各個 View 元素就能得到系統(tǒng)的最終“分配結(jié)果”。這個“分配結(jié)果”至少會包含兩個方的內(nèi)容:View 對象的尺寸大小和位置,再加上 View 自身的 UI 內(nèi)容,如此便構(gòu)成了 UI 顯示的基本三要素。而這重要的三要素,它們在遍歷的過程中分別對應(yīng)以下三個函數(shù):
- performMeasure 用于計算 View 對象在 UI 界面上的尺寸位置,對應(yīng) View 的onMeasure
- performLayout 用于計算 View 對象在 UI 界面上的繪圖位置。對應(yīng) View 的 onLayout
- performDraw 上述兩個屬性確定后,View 對象就可以在此基礎(chǔ)上繪制 UI 了。對應(yīng) View 的 onDraw
上面這三個函數(shù)是在 ViewRootImpl 中展開的,對于開發(fā)者來說,我們面對更多的則是 View 與 ViewGroup 以及它們的子類,下面是 View 相關(guān)的一些生命周期回調(diào):
- measure
測量該控件的大小 ,如果是ViewGroup還需測量子控件大小,measureChildren或調(diào)用子控件的measure來觸發(fā)子控件元素的onMeasure方法
- layout
當(dāng)View分配所有的子元素的大小和位置時,在onLayout方法被調(diào)用之前getWidth(), getHeight()是獲取不 到控件的大小
- draw
view渲染內(nèi)容
- dispatchDraw
在onDraw之后會調(diào)用此方法,分發(fā)子元素繪制,主要是針對ViewGroup。ViewGroup容器組件的繪制,當(dāng)它沒有背景時直接調(diào)用的是dispatchDraw()方法, 而不執(zhí)行draw()方法,當(dāng)它有背景的時候就調(diào)用draw()方法,而draw()方法里包含了dispatchDraw()方法的調(diào)用。因此要在ViewGroup上繪制東西的時候往往重寫的是dispatchDraw()方法而不是onDraw()方法
我是 Window
我是在應(yīng)用框架層,被 JAVA 封裝的用來展示窗口的一個抽象類。我負(fù)責(zé)可視化內(nèi)容的排版。Android 支持的窗口類型很多,不過我們可以統(tǒng)一劃分為三大類,即 Application Window、System Window 和 Sub Window。另外各個種類下還細(xì)分為若干子類型,這些都是在我的上司 WindowManager 通過進(jìn)程通信的方式,去與后臺服務(wù) WindowManagerService 通信,最終遞交到 SurfaceFlinger 來輸出和呈現(xiàn)。
從用戶的角度來說,我就是一個界面;從 SurfaceFlinger 的角度來說,我是一個 Layer ,承載著和界面有關(guān)的數(shù)據(jù)和屬性;從 WMS 來說,我是一個 windowstate ,用于管理和界面有關(guān)的狀態(tài)。
窗口類型與層級
Application Window 這類窗口對應(yīng)應(yīng)用程序的窗口,取值在 1-99 之間
Type Description FIRST_APPLICATION_WINDOW = 1 應(yīng)用程序窗口的起始值 TYPE_BASE_APPLICATION = 1 應(yīng)用程序窗口的基礎(chǔ)值 TYPE_APPLICATION = 2 普通應(yīng)用程序的窗口類型 TYPE_APPLICATION_STARTING = 3 應(yīng)用程序的啟動窗口類型。它不能由應(yīng)用程序本身使用,而是Android 系統(tǒng)為應(yīng)用程序啟動前設(shè)計的窗口,當(dāng)真正的窗口啟動后它就消失了 TYPE_DRAWN_APPLICATION = 4 用于確保應(yīng)用程序窗口在顯示時已經(jīng)完成了繪制 LAST_APPLICATION_WINDOW = 99 應(yīng)用程序窗口的最大值
Sub Window 這類窗口將附著在其他 Window 中,取值在 1000 到 1999 之間
Type Description FIRST_SUB_WINDOW = 1000 子窗口的起始值 TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW 應(yīng)用程序的 panel 子窗口,在它的父窗口之上顯示 TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1 用于顯示多媒體內(nèi)容的子窗口,位于父窗口之下 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 和應(yīng)用程序窗口之間,通常透明才有意義。此類型屬于未開放狀態(tài) LAST_SUB_WINDOW = 1999 子窗口的最大值
System Window 對應(yīng)系統(tǒng)程序采用的窗口類型,取值在 2000 到 2999 之間
Type Description FIRST_SYSTEM_WINDOW = 2000 系統(tǒng)窗口的起始值 TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW 系統(tǒng)狀態(tài)欄窗口 TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1 系統(tǒng)搜索條窗口 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 系統(tǒng)覆蓋窗口,這種類型的窗口不能接收 input 事件 TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7 電話優(yōu)先窗口 TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8 RecentsAppDialog 就是這種類型的窗口 TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9 屏保時顯示的對話框 TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10 系統(tǒng)錯誤窗口 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 滑動狀態(tài)欄出現(xiàn)的窗口 YPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19 導(dǎo)航欄窗口 TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20 系統(tǒng)音量條 TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21 開機(jī)啟動的進(jìn)度條窗口 TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22 導(dǎo)航欄隱藏時用于消耗事件的偽窗口 LAST_SYSTEM_WINDOW = 2999 系統(tǒng)窗口結(jié)束
當(dāng)某個進(jìn)程向 WMS 申請一個窗口時,它需要指定所需窗口類型,然后 WMS 根據(jù)用戶申請的窗口類型以及當(dāng)前系統(tǒng)中已有窗口的情況來給它分配一個最終的層級值,數(shù)值越大的窗口,優(yōu)先級越高,在屏幕上顯示時候就越靠近用戶。
窗口屬性
除了窗口類型外,開發(fā)者還可以設(shè)置不同的屬性來調(diào)整窗口的表現(xiàn),這些屬性統(tǒng)一放置在 WindowManager.LayoutParams 中。其中主要包括以下幾個重要的變量:
- Type 也就是上面的窗口類型
- Flag 窗口標(biāo)志,默認(rèn)為0 Flags Description FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001 只要此窗口可見,即便屏幕處于開啟狀態(tài)也允許鎖屏 FLAG_DIM_BEHIND = 0x00000002 在窗口后面的所有東西都將變暗淡 FLAG_NOT_FOCUSABLE = 0x00000008 此窗口不獲得輸入焦點(diǎn),意味著事件將發(fā)給該窗口后面的其他窗口。在設(shè)置了此標(biāo)志的同時,F(xiàn)LAG_NOT_TOUCH_MODAL 也會同時被設(shè)置 FLAG_NOT_TOUCHABLE = 0x00000010 表示該窗口不接受任何觸摸事件 FLAG_NOT_TOUCH_MODAL = 0x00000020 無模式的窗口 FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040 當(dāng)設(shè)備進(jìn)入休眠狀態(tài)時,設(shè)置此標(biāo)志可以使你獲得第一次的觸摸事件 FLAG_KEEP_SCREEN_ON = 0x00000080 只要這個窗口可見,屏幕就亮著 FLAG_LAYOUT_IN_SCREEN = 0x00000100 窗口顯示時候不考慮系統(tǒng)裝飾框,比如 Status Bar LAG_LAYOUT_NO_LIMITS = 0x00000200 允許窗口超過屏幕區(qū)域 FLAG_FULLSCREEN = 0x00000400 隱藏所有的屏幕裝飾窗口 FLAG_FORCE_NOT_FULLSCREEN = 0x00000800 和 FLAG_FULLSCREEN 正好相反 FLAG_SECURE = 0x00002000 窗口類容被認(rèn)為是保密的,因而它不會出現(xiàn)在截屏中,也不會再不安全的屏幕上顯示 FLAG_SCALED = 0x00004000; 按照用戶提供的參數(shù)做相應(yīng)的縮放 FLAG_IGNORE_CHEEK_PRESSES = 0x00008000 有些時候用戶和屏幕會貼的很近,比如打電話時候。這種情況下出現(xiàn)的某些事件可能是無意的,不應(yīng)該響應(yīng) FLAG_SHOW_WHEN_LOCKED = 0x00080000 使窗口能在鎖屏窗口之上 FLAG_SHOW_WALLPAPER = 0x00100000 讓壁紙在這個窗口之后顯示。當(dāng)窗口是透明或者半透明時候就可以看到后面的壁紙,如 Launcher FLAG_TURN_SCREEN_ON = 0x00200000 窗口顯示時將屏幕點(diǎn)亮 FLAG_DISMISS_KEYGUARD = 0x00400000 設(shè)置這個標(biāo)志可以解除屏幕鎖,但是不能解除 secure lock
- systemUiVisibility 表示系統(tǒng) UI 的可見性
Flags Description SYSTEM_UI_FLAG_VISIBLE = 0 請求顯示系統(tǒng)UI,默認(rèn)狀態(tài) SYSTEM_UI_FLAG_LOW_PROFILE = 0x00000001 低能模式,狀態(tài)欄上的一些圖標(biāo)會被隱藏,游戲、閱讀、視頻播放等沉浸式應(yīng)用會需要 SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002 請求隱藏底部導(dǎo)航欄 SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004 請求全屏顯示,狀態(tài)欄會被隱藏,底部導(dǎo)航欄不會被隱藏,效果和WindowManager.LayoutParams.FLAG_FULLSCREEN相同 SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800 這個flag只有當(dāng)設(shè)置了SYSTEM_UI_FLAG_HIDE_NAVIGATION才起作用。如果沒有設(shè)置這個flag,任意的View相互動作都退出SYSTEM_UI_FLAG_HIDE_NAVIGATION模式。如果設(shè)置就不會退出 SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000 這個flag只有當(dāng)設(shè)置了SYSTEM_UI_FLAG_FULLSCREEN|SYSTEM_UI_FLAG_HIDE_NAVIGATION時才起作用。如果沒有設(shè)置這個flag,任意的View相互動作都會退出SYSTEM_UI_FLAG_FULLSCREEN|SYSTEM_UI_FLAG_HIDE_NAVIGATION模式,如果設(shè)置就不受影響 SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000 狀態(tài)欄淺色背景模式,文字為黑色,Android 6.0以前(api < 23)不支持 SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100 請求系統(tǒng)UI布局穩(wěn)定狀態(tài) SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400 讓View全屏顯示,Layout會被拉伸到StatusBar下面,不包含NavigationBar SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200 讓View全屏顯示,Layout會被拉伸到NavigationBar下面
上面這些屬性除了 systemUiVisibility 相關(guān)的是定義在 View 中的,其他的都是定義在 WindowManager 中的
我是WindowManager
我是一個繼承于 ViewManager 的接口,WindowManagerImpl 是我的具體實現(xiàn)類。ViewManager 中定義了與 View 交互的接口函數(shù) addView()、updateViewLayout()、removeView() ,應(yīng)用程序通過(WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)獲取到 WindowManager 實例后,就可以通過addView()將 View 添加到 WMS 中去。
但是我只是一個接口啊,就連我的實現(xiàn)者 WindowManagerImpl 也沒有任何持有 WSM 的影子啊。那 View 是如何添加到 WMS 中去的呢?既然我們兩個沒法暗通款曲,那就索性尋個媒婆來明媒正娶。這點(diǎn),ViewRootImpl 是極其專業(yè)的,于是我便找了它。
我是ViewRootImpl
我是一個中介,負(fù)責(zé)管理整顆 View 樹的同時,也擔(dān)負(fù)著與 WMS 進(jìn)行 IPC 通訊的重任。具體而言,我會通過 IWindowSession 建立雙方的橋梁。
從實現(xiàn)上來說,在構(gòu)造函數(shù)中,我會通過 WindowManagerGlobal.getWindowSession()來打開一個IWindowSession 對象來與 WMS 的可用連接。IWindowSession 是一個 IBinder 接口,它定義了一系列與 window manager 交互的交互方式,如此一來,當(dāng)應(yīng)用程序調(diào)用 setView 等方法時,我就可以利用它來發(fā)起一個服務(wù)請求。 IWindowSession 的服務(wù)端(Session.java)便會響應(yīng)這個請求,從而調(diào)用 WMS 的 addWindow()來傳遞給 WMS 處理。
我是WindowManagerService
我和 AMS 等 Service 一樣,是由 SystemServer 啟動的系統(tǒng)服務(wù)的一部分。由于我是由 SystemServer 啟動的,啟動時機(jī)相對較晚,如果在 SystemServer 還沒運(yùn)行之前,我是無能為力的。比如在開機(jī)時候顯示的開機(jī)動畫,那時候我還沒運(yùn)行起來,所以這時候的顯示則是由 BootAnimation 直接通過 OpenGL ES 與SurfaceFlinger 的配合來完成的。原則上我只負(fù)責(zé)“窗口”的層級和屬性,之所以能夠?qū)?Window 內(nèi)容顯示出來,也是由于我與 SurfaceFlinger 溝通后,SufaceFlinger 才真正將窗口數(shù)據(jù)合成并最終顯示在屏幕上的緣故。
從某種方面來說,我可是整個 Android UI 體系的大導(dǎo)演呢,因為我會根據(jù)實際情況來安排每個演員(Window)的排序站位,誰前誰后,怎么進(jìn)場,如何出場等,目的當(dāng)然也是為了將舞臺效果和視覺美感表現(xiàn)得更佳,從而呈現(xiàn)給觀眾。我并不關(guān)心這里面的演員是誰,從源碼角度來說,我不關(guān)心 View 樹,或者說這個 window 所表達(dá)的具體類容是什么,我只要知道需要顯示的界面大小,層級值等即可,而這些已經(jīng)作為 WindowManager.LayoutParams 參數(shù)傳遞給我了。
前面說到,WMS 還需要通知 SurfaceFlinger,才能把正確的結(jié)果及時的呈現(xiàn)給“觀眾”。由于 SurfaceFlinger 繪制 UI 界面需要有“畫板”—— BufferQueue 的支持,BufferQueue 在 SurfaceFlinger 中對應(yīng)的是 Layer,在 Java 層對應(yīng)的則是 Surface( Surface 持有 BufferQueue 的實現(xiàn)接口—— IGraphicBufferProducer ),因此,無論是系統(tǒng)窗口還是應(yīng)用窗口,都必須向 SurfaceFlinger 申請相應(yīng)的 Layer,進(jìn)而得到圖形緩沖區(qū)的使用權(quán)。
WMS、AMS 與 Activity 間的聯(lián)系
Activity 運(yùn)行在應(yīng)用程序進(jìn)程中,而 AMS 與WMS 則運(yùn)行在系統(tǒng)相關(guān)進(jìn)程中,它們之間的通信需要 Binder 的支持。應(yīng)用程序訪問 WMS 的服務(wù)首先要通過 ServiceManager,因為 WMS 是實名 Binder Server;WMS 還針對每個 Activity 提供了一種匿名的實現(xiàn),即 IWindowSession。
當(dāng)一個新的 Activity 被啟動時候(startActivity),它首先需要在 AMS 中注冊——此時 AMS 會生成一個 ActivityRecord 來記錄這個 Activity ;另外,Activity 還承載著 UI 顯示的功能,所以 WMS 也會對它進(jìn)行記錄——以 WindowState 來表示。WMS 除了利用 WindowState 來保存一個窗口相關(guān)的信息外,還使用 AppWindowToken 來對應(yīng) AMS 中的一個 ActivityRecord,從而將三者形成非常緊密的聯(lián)系。
我是Surface
Surface 對應(yīng)了一塊屏幕緩沖區(qū),每個 window 對應(yīng)一個Surface,任何 View 都是畫在 Surface 上的,傳統(tǒng)的 view 共享一塊屏幕緩沖區(qū)。
我有一個龐大的家族體系,站在臺前的 Android 為我們封裝的處于 java 層面的 Surface,我們家族的幕后長老們同時也在 native 層默默貢獻(xiàn)者他們的力量。在 Surface.java中 Android 是這樣定義我的 Handle onto a raw buffer that is being managed by the screen compositor. 由此可以看出,首先我是一個 raw buffer(屏幕緩沖區(qū))的句柄,可以通過我來管理一個 raw buffer ;其次,我本身又被一個叫 screen compositor 的家伙在管理。同時,我內(nèi)部持有 IGraphicBufferProducer,而這個 IGraphicBufferProducer 則是 BufferQueue 的實現(xiàn)接口,如此我便又和 BufferQueue 搞上了。
前面說到,WMS 想要將內(nèi)容展示出來,需要我的支持,具體的,以 addView 來說,我是在 ViewRoot 進(jìn)行 performTraversals 時,向 WMS 申請一個 Surface 時誕生的。WMS 在創(chuàng)建 Surface 時,會生成一個 SurfaceSession ,然后將這個 SurfaceSession 作為參數(shù)來構(gòu)造 Surface。這個 SurfaceSession 就是 screen compositor 的一個會話鏈接。同時,在 java 層面上的 Surface 和 SurfaceSession 構(gòu)造的時候,都會調(diào)用具體的 init 方法,喚醒我們在 native 層的長老們,他們主要聚集 framework/native/libs/gui 這個”山洞“中。
下面羅列的是其中涉及到的一些比較重要的成員和職責(zé):
- ISurfaceComposer:通過這個接口可以訪問到 SurfaceFlinger,可以通過它建立一個會話,即ISurfaceComposerClient,也可以通過它去更新 Surface 的相關(guān)信息,這個是通過setTransactionState接口完成的,代表一個到 SurfaceFinger 的會話連接
- SurfaceComposerClient:是 SurfaceFlinger 派出的“代表”,不論是 OpenGL ES 還是 Surface,都可以在這個類的協(xié)助下有序地申請和訪問各 Buffer 緩沖區(qū)。持有 ISurfaceComposerClient 的客戶端代理,在SurfaceComposerClient 初次實例化時,通過 ISurfaceComposer 的createConnection()接口得到一個ISurfaceComposerClient 的代理。同時,它也會管理 Surface 的狀態(tài),通過 ISurfaceComposer 更新Surface 狀態(tài)
- SurfaceControl:從字面上看,其作用是控制 Surface。其實際作用是持有 ISurface 的代理及SurfaceComposerClient
- ISurfaceTexture:對應(yīng)具體的 buffer 管理
- ANativeWindow:持有 ISurfaceTexture 的本地代理,通過它可以訪問到 ISurfaceTexture 的實現(xiàn)。同時它繼承了 ANativeWindow,而 Surface 類會繼承 SurfaceTextureClient. ANativeWindow 代表的本地窗口
- GraphicBuffer:GraphicBuffer 是一個 ANativeWindowBuffer,每一個GraphicBuffer 內(nèi)部都包含有一塊用來保存UI數(shù)據(jù)的緩沖區(qū),它實際存儲空間其實是在 ashmem 上的,具體是gralloc模塊來完成分配的,然后映射到應(yīng)用程序的進(jìn)程地址空間
有了 Surface ,便可以得到一塊屏幕緩沖區(qū),但是這時我們的視圖還是不能呈現(xiàn)在觀眾眼前的。于是便要將 Surface 添加到 BufferQueue 中,從而讓 SufaceFlinger 來消費(fèi)。
我是BufferQueue
我是一名勤勤懇懇的老師,我對每個應(yīng)用程序都進(jìn)行“一對一在線輔導(dǎo)”,指導(dǎo)著 UI 程序的 “申請畫板”、“作畫流程”等一系列的繁瑣細(xì)節(jié)。我與各應(yīng)用程序是通過 IGraphicBufferProducer 建立關(guān)系的。
BufferQueue 是 Android 顯示系統(tǒng)的核心,它的設(shè)計哲學(xué)是生產(chǎn)者-消費(fèi)者模型,只要往 BufferQueue 中填充數(shù)據(jù),則認(rèn)為是生產(chǎn)者,只要從 BufferQueue中獲取數(shù)據(jù),則認(rèn)為是消費(fèi)者。有時候同一個類,在不同的場景下既可能是生產(chǎn)者也有可能是消費(fèi)者。如 SurfaceFlinger,在合成并顯示 UI 內(nèi)容時,UI 元素作為生產(chǎn)者生產(chǎn)內(nèi)容,SurfaceFlinger 作為消費(fèi)者消費(fèi)這些內(nèi)容。而在截屏?xí)r,SurfaceFlinger 又作為生產(chǎn)者將當(dāng)前合成顯示的UI內(nèi)容填充到另一個 BufferQueue,截屏應(yīng)用此時作為消費(fèi)者從 BufferQueue 中獲取數(shù)據(jù)并生產(chǎn)截圖。
站在應(yīng)用程序的角度來說,應(yīng)用程序可以調(diào)用 createSurface 來建立多個 Layer,每一個 Layer 都對應(yīng)一個 BufferQueue,換句話說,應(yīng)用程序與 BufferQueue 也是一對多的關(guān)系。為應(yīng)用程序申請的 Layer,一方面需要告知 SurfaceFlinger,另一方面也要記錄到各 Client 內(nèi)部中。另外,Layer 也沒有直接持有 BufferQueue 對象,而是通過 Layer 內(nèi)部的 mSurfaceFlingerConsumer 來管理的。
我是SufaceFlinger
我是由 init 進(jìn)程所啟動的守護(hù)進(jìn)程,運(yùn)行在Android系統(tǒng)的 System 進(jìn)程中,負(fù)責(zé)管理Android系統(tǒng)的幀緩沖區(qū)(Frame Buffer),需要顯示 UI 界面的應(yīng)用程序需要通過 Binder 服務(wù)來與我通信。每個有 UI 界面的程序都在我這里有相對應(yīng)的 Client 實例。應(yīng)用程序與 Client 間的 Binder 接口是 ISurfaceComposerClient。Client 也只是我分配給應(yīng)用程序的一個”代表“ ,真正的圖行(Buffer)需要另外申請,即調(diào)用 Client 提供的 ISurfaceComposerClient::createSurface()來實現(xiàn)。同時,在 SufaceFlinger 進(jìn)程中將會有一個 Layer 被創(chuàng)建,代表了一個畫面。ISurface 就是控制這一面的handle,它將保持在應(yīng)用程序端的 SufaceControl 中。
事實上,我是一個耿直boy,你看我的名字就知道,我的職責(zé)是 Flinger,即把系統(tǒng)中所以應(yīng)用程序最終的“繪圖結(jié)果”進(jìn)行“混合”,然后統(tǒng)一顯示到物理屏幕上。所以我不會太關(guān)注各個應(yīng)用程序的“繪畫過程”,于是我又派出了一個“代表”——BufferQueue 替我去完成這一光榮的使命。
現(xiàn)在萬事俱備,只欠東風(fēng),我就可以鉚足干勁嘩啦啦的繪制了,觀眾也就能看到美輪美奐的"節(jié)目"了。那東風(fēng)從哪來?又要到哪去?這時候就輪到我們勤勤懇懇的快遞員選手——VSync 大展身手了。
我是VSync
谷歌在4.1版本引入了一個重大的改進(jìn)——Project Butter,也即是黃油計劃。Project Butter 對 Android Display 系統(tǒng)進(jìn)行了重構(gòu),引入了三個核心元素,即 VSYNC、Triple Buffer 和 Choreographer。
安卓系統(tǒng)中有 2 種 VSync 信號:屏幕產(chǎn)生的硬件 VSync 和由 SurfaceFlinger 將其轉(zhuǎn)成的軟件 Vsync 信號。采用 Vsync 進(jìn)行顯示同步,一旦 Vsync 信號出現(xiàn),CPU 便立即開始執(zhí)行 Buffer 的準(zhǔn)備工作。目前 Android 是采用 Multiple Buffer 的技術(shù)來處理的。
沒有引入vsync的情況
上圖是沒有引入VSync 機(jī)制的處理流程。可以看出,一個很明顯的問題是,只要一次cpu/gpu 處理出現(xiàn)異常就可能導(dǎo)致后面的一系列的處理出現(xiàn)異常
引入VSync 機(jī)制
上圖是引入 VSync 機(jī)制的后的處理流程。在 FPS < 手機(jī)屏幕刷新率的情況下,一切運(yùn)行完美
Double Buffering 異常情況
上圖是在 VSync 機(jī)制下,Double Buffering 時 FPS > 手機(jī)屏幕刷新率的情況。只要出現(xiàn)一次 Jank 就會影響下一次的 VSync (cpu 不能工作)
Triple Buffering 異常情況
上圖是在 VSync 機(jī)制下,Triple Buffering 時FPS > 手機(jī)屏幕刷新率的情況。當(dāng)?shù)谝淮?VSync 發(fā)生后,CPU 不用再等待了,除了第一次的 Jank 無法規(guī)避,第二次、第三次 VSync 到來時都能有效采用到 buffer,從而有效降低了系統(tǒng)顯示錯誤。
VSync 最終會被 EventThread::threadLoop()分發(fā)給各監(jiān)聽者,如 SurfaceFlinger 進(jìn)程中就是 MessageQueue 。VSync 被 SurfaceFlinger 監(jiān)聽到后,SurfaceFlinger 首先需要遍歷 當(dāng)前 Layer (這里的 Layer 對應(yīng)的則是 BufferQueue) ,確定是否需要重繪。對應(yīng) Z-order 等與編排相關(guān)的 SurfaceFlinger可以自己確定,但是對于各個 Buffer 內(nèi)容的變動,還是需要更加專業(yè)的 BufferQueue 來處理了。BufferQueue 處理完成,并且將結(jié)果返回給 SurfaceFlinger 后,再由 SurfaceFlinger 進(jìn)行“加工混合”,交由 OpenGL ES 顯示出來 。
我是Choreographer
字面翻譯過來,我是編舞者的意思。具體來講,我主要是配合 Vsync(因為我可以監(jiān)聽底層Vsync信號) ,給上層 App 的渲染提供一個穩(wěn)定的 Message 處理的時機(jī)。
ViewRootImpl 啟動時會初始化 Choreographer 的實例。
當(dāng) Vsync 信號由 SurfaceFlinger 中創(chuàng)建 HWC 觸發(fā),喚醒 DispSyncThread 線程,再到 EventThread 線程,然后再通過 BitTube(一種進(jìn)程間通信的一種機(jī)制) 直接傳遞到目標(biāo)進(jìn)程所對應(yīng)的目標(biāo)線程,執(zhí)行 handleEvent方法 ,然后通過 C++ 層的 dispatchVsync 進(jìn)入到 java 層的 dispatchVsync 回調(diào),觸發(fā)FrameDisplayEventReceiver.run() 如此 Choreographer 便接收到了消息,doFrame()執(zhí)行,UI 繪制開始。
參考文獻(xiàn)
- 《深入理解Android內(nèi)核設(shè)計思想》 ——林學(xué)森著
- gityuan 老師相關(guān)博客
- KunMinX 老師的博客重學(xué)安卓:Activity 的快樂你不懂
- AOSP 10.0 源碼
- developer.android.com/
作者:joychic
鏈接:https://juejin.im/post/5e0ca9ccf265da5d4170e844
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。