今天,讓我們一起來聊聊 Android 的 GUI 系統(tǒng),。緣起在2019年的 Google I/O 大會上,,Jetpack 團(tuán)隊(duì)首次為大家介紹了 Jetpack Compose,這是一種全新的 Android UI 組件庫,。當(dāng)時(shí)演講者為大家分享了一張圖,,描述了 Android 10 年里的在 UI 方面簡要發(fā)展歷史,在長達(dá) 10 年的發(fā)展過程中,,Google 針對不同的問題做出了很多的調(diào)整,,但是唯獨(dú)在 UI 構(gòu)建方面,最初的那一套 UI 構(gòu)建體系一直沿用至今,,幾乎沒有做任何調(diào)整,。
目標(biāo)希望通過這次梳理,,能對 Android 整體的 View 框架體系大致流程上能有清晰的認(rèn)識。起于 App 層,,止于驅(qū)動(dòng)層,,并且從中挑一些重要的內(nèi)容來講述,方便理清眾多對象之間的關(guān)系脈絡(luò),,從而在整體架構(gòu)上能有比較清晰的認(rèn)知,。這樣,在閱讀源碼細(xì)節(jié)時(shí)候不至于發(fā)出哲學(xué)三連問——我是誰,?我從哪兒來,?要到那兒去? 那么,,開搞,! 我是 Activity我是一名交際花,專注于于 UI 界面顯示和處理,,是應(yīng)用程序中各組件里人氣最高的偶像之一,。我在江湖中能有如此地位,那還得多虧了 Android 爸爸對我不吝的包裝,。對于開發(fā)者來說,,只需要簡單的調(diào)用setContentView、onCreate,、onStart 等方法,,我就能將他們想要顯示的內(nèi)容展現(xiàn)出來。很簡單是吧,,因?yàn)槲沂钦麄€(gè)UI體系中離開發(fā)者最近的一個(gè)窗口了,,只有讓開發(fā)者用起來足夠爽了,他們才會喜歡上我啊,,所以呢,,Android 爸爸也是對我花了很多小心思呢。我呢,將一系列生命周期相關(guān)的回調(diào)用模板方法模式的一種設(shè)計(jì)模式封裝,,然后暴露給開發(fā)者,,至于一些那些粗活累活我就匯報(bào)給 Android 爸爸去處理,畢竟作為一個(gè) idol ,,人設(shè)是萬萬不能倒的,。比如像 setContentView 這種大部分情況下只是傳遞了一個(gè) xml 的布局的家伙,又要解析 View tree,,又要構(gòu)建的,,想想都麻煩,我就很機(jī)智的交給 framework 去處理了,。 你別看我多風(fēng)光的樣子,,但是本質(zhì)上,我也只是一個(gè) window 而已啦,。 我是View我是 app 層面向開發(fā)者比較核心的 UI 相關(guān)類,,目前我在源碼中的實(shí)現(xiàn)接近 3W 行。我呢還有一個(gè)優(yōu)秀的 child ,,名字叫 ViewGroup,。ViewGroup 通過組合模式,而能夠在自身內(nèi)部存在更多的 View 或 ViewGroup,,這樣一來,,從結(jié)構(gòu)上看,我們像是俄羅斯套娃,,你中有我,,我中有你。其實(shí)除了 View 和 ViewGroup 這些家喻戶曉的明星成員外,,View 家族中還有 ViewParent ,、ViewRootImpl 這些重要的幕后成員,你可千萬別以為 ViewParent 就是我的爹地,,它雖然叫 ViewParent 但是它就是一隔壁老王,,和我一毛錢關(guān)系也沒有。雖然我和 ViewParent 清清白白的,,但是 ViewGroup 和 ViewRootImpl 都實(shí)現(xiàn)了 ViewParent 的接口方法,。 Activity 的
上面這三個(gè)函數(shù)是在 ViewRootImpl 中展開的,對于開發(fā)者來說,,我們面對更多的則是 View 與 ViewGroup 以及它們的子類,,下面是 View 相關(guān)的一些生命周期回調(diào):
測量該控件的大小 ,如果是ViewGroup還需測量子控件大小,measureChildren或調(diào)用子控件的measure來觸發(fā)子控件元素的onMeasure方法
當(dāng)View分配所有的子元素的大小和位置時(shí),在onLayout方法被調(diào)用之前getWidth(), getHeight()是獲取不 到控件的大小
view渲染內(nèi)容
在onDraw之后會調(diào)用此方法,分發(fā)子元素繪制,主要是針對ViewGroup,。ViewGroup容器組件的繪制,,當(dāng)它沒有背景時(shí)直接調(diào)用的是dispatchDraw()方法, 而不執(zhí)行draw()方法,當(dāng)它有背景的時(shí)候就調(diào)用draw()方法,,而draw()方法里包含了dispatchDraw()方法的調(diào)用,。因此要在ViewGroup上繪制東西的時(shí)候往往重寫的是dispatchDraw()方法而不是onDraw()方法我是 Window我是在應(yīng)用框架層,被 java 封裝的用來展示窗口的一個(gè)抽象類,。我負(fù)責(zé)可視化內(nèi)容的排版,。Android 支持的窗口類型很多,不過我們可以統(tǒng)一劃分為三大類,,即 Application Window,、System Window 和 Sub Window。另外各個(gè)種類下還細(xì)分為若干子類型,,這些都是在我的上司 WindowManager 通過進(jìn)程通信的方式,,去與后臺服務(wù) WindowManagerService 通信,最終遞交到 SurfaceFlinger 來輸出和呈現(xiàn),。 從用戶的角度來說,,我就是一個(gè)界面;從 SurfaceFlinger 的角度來說,,我是一個(gè) Layer ,,承載著和界面有關(guān)的數(shù)據(jù)和屬性;從 WMS 來說,,我是一個(gè) WindowState ,,用于管理和界面有關(guān)的狀態(tài)。 窗口類型與層級Application Window 這類窗口對應(yīng)應(yīng)用程序的窗口,,取值在 1-99 之間
Sub Window 這類窗口將附著在其他 Window 中,,取值在 1000 到 1999 之間
System Window 對應(yīng)系統(tǒng)程序采用的窗口類型,取值在 2000 到 2999 之間
當(dāng)某個(gè)進(jìn)程向 WMS 申請一個(gè)窗口時(shí),它需要指定所需窗口類型,,然后 WMS 根據(jù)用戶申請的窗口類型以及當(dāng)前系統(tǒng)中已有窗口的情況來給它分配一個(gè)最終的層級值,,數(shù)值越大的窗口,優(yōu)先級越高,,在屏幕上顯示時(shí)候就越靠近用戶,。 窗口屬性除了窗口類型外,開發(fā)者還可以設(shè)置不同的屬性來調(diào)整窗口的表現(xiàn),,這些屬性統(tǒng)一放置在 WindowManager.LayoutParams 中,。其中主要包括以下幾個(gè)重要的變量:
上面這些屬性除了 systemUiVisibility 相關(guān)的是定義在 View 中的,,其他的都是定義在 WindowManager 中的 我是WindowManager我是一個(gè)繼承于 ViewManager 的接口,WindowManagerImpl 是我的具體實(shí)現(xiàn)類,。ViewManager 中定義了與 View 交互的接口函數(shù) 但是我只是一個(gè)接口啊,,就連我的實(shí)現(xiàn)者 我是ViewRootImpl我是一個(gè)中介,負(fù)責(zé)管理整顆 View 樹的同時(shí),,也擔(dān)負(fù)著與 WMS 進(jìn)行 IPC 通訊的重任,。具體而言,我會通過 IWindowSession 建立雙方的橋梁,。 從實(shí)現(xiàn)上來說,,在構(gòu)造函數(shù)中,我會通過 我是WindowManagerService我和 AMS 等 Service 一樣,,是由 SystemServer 啟動(dòng)的系統(tǒng)服務(wù)的一部分。由于我是由 SystemServer 啟動(dòng)的,,啟動(dòng)時(shí)機(jī)相對較晚,,如果在 SystemServer 還沒運(yùn)行之前,我是無能為力的,。比如在開機(jī)時(shí)候顯示的開機(jī)動(dòng)畫,,那時(shí)候我還沒運(yùn)行起來,所以這時(shí)候的顯示則是由 BootAnimation 直接通過 OpenGL ES 與SurfaceFlinger 的配合來完成的,。原則上我只負(fù)責(zé)“窗口”的層級和屬性,,之所以能夠?qū)?Window 內(nèi)容顯示出來,也是由于我與 SurfaceFlinger 溝通后,,SufaceFlinger 才真正將窗口數(shù)據(jù)合成并最終顯示在屏幕上的緣故,。 從某種方面來說,我可是整個(gè) Android UI 體系的大導(dǎo)演呢,,因?yàn)槲視鶕?jù)實(shí)際情況來安排每個(gè)演員(Window)的排序站位,,誰前誰后,怎么進(jìn)場,,如何出場等,,目的當(dāng)然也是為了將舞臺效果和視覺美感表現(xiàn)得更佳,從而呈現(xiàn)給觀眾,。我并不關(guān)心這里面的演員是誰,,從源碼角度來說,我不關(guān)心 View 樹,,或者說這個(gè) window 所表達(dá)的具體類容是什么,,我只要知道需要顯示的界面大小,層級值等即可,,而這些已經(jīng)作為 WindowManager.LayoutParams 參數(shù)傳遞給我了,。 前面說到,WMS 還需要通知 SurfaceFlinger,,才能把正確的結(jié)果及時(shí)的呈現(xiàn)給“觀眾”,。由于 SurfaceFlinger 繪制 UI 界面需要有“畫板”—— BufferQueue 的支持,BufferQueue 在 SurfaceFlinger 中對應(yīng)的是 Layer,,在 Java 層對應(yīng)的則是 Surface( Surface 持有 BufferQueue 的實(shí)現(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,因?yàn)?WMS 是實(shí)名 Binder Server,;WMS 還針對每個(gè) Activity 提供了一種匿名的實(shí)現(xiàn),,即 IWindowSession。 當(dāng)一個(gè)新的 Activity 被啟動(dòng)時(shí)候(startActivity),,它首先需要在 AMS 中注冊——此時(shí) AMS 會生成一個(gè) ActivityRecord 來記錄這個(gè) Activity ,;另外,Activity 還承載著 UI 顯示的功能,,所以 WMS 也會對它進(jìn)行記錄——以 WindowState 來表示,。WMS 除了利用 WindowState 來保存一個(gè)窗口相關(guān)的信息外,,還使用 AppWindowToken 來對應(yīng) AMS 中的一個(gè) ActivityRecord,,從而將三者形成非常緊密的聯(lián)系。 我是Surface
我有一個(gè)龐大的家族體系,,站在臺前的 Android 為我們封裝的處于 java 層面的 Surface,我們家族的幕后長老們同時(shí)也在 native 層默默貢獻(xiàn)者他們的力量,。在 前面說到,,WMS 想要將內(nèi)容展示出來,,需要我的支持,具體的,,以 下面羅列的是其中涉及到的一些比較重要的成員和職責(zé):
有了 Surface ,,便可以得到一塊屏幕緩沖區(qū),但是這時(shí)我們的視圖還是不能呈現(xiàn)在觀眾眼前的,。于是便要將 Surface 添加到 BufferQueue 中,,從而讓 SufaceFlinger 來消費(fèi)。 我是BufferQueue我是一名勤勤懇懇的老師,我對每個(gè)應(yīng)用程序都進(jìn)行“一對一在線輔導(dǎo)”,,指導(dǎo)著 UI 程序的 “申請畫板”,、“作畫流程”等一系列的繁瑣細(xì)節(jié)。我與各應(yīng)用程序是通過 IGraphicBufferProducer 建立關(guān)系的,。 BufferQueue 是 Android 顯示系統(tǒng)的核心,,它的設(shè)計(jì)哲學(xué)是生產(chǎn)者-消費(fèi)者模型,只要往 BufferQueue 中填充數(shù)據(jù),,則認(rèn)為是生產(chǎn)者,,只要從 BufferQueue中獲取數(shù)據(jù),則認(rèn)為是消費(fèi)者,。有時(shí)候同一個(gè)類,,在不同的場景下既可能是生產(chǎn)者也有可能是消費(fèi)者。如 SurfaceFlinger,,在合成并顯示 UI 內(nèi)容時(shí),,UI 元素作為生產(chǎn)者生產(chǎn)內(nèi)容,SurfaceFlinger 作為消費(fèi)者消費(fèi)這些內(nèi)容,。而在截屏?xí)r,,SurfaceFlinger 又作為生產(chǎn)者將當(dāng)前合成顯示的UI內(nèi)容填充到另一個(gè) BufferQueue,截屏應(yīng)用此時(shí)作為消費(fèi)者從 BufferQueue 中獲取數(shù)據(jù)并生產(chǎn)截圖,。 站在應(yīng)用程序的角度來說,,應(yīng)用程序可以調(diào)用 createSurface 來建立多個(gè) Layer,每一個(gè) Layer 都對應(yīng)一個(gè) BufferQueue,,換句話說,,應(yīng)用程序與 BufferQueue 也是一對多的關(guān)系,。為應(yīng)用程序申請的 Layer,,一方面需要告知 SurfaceFlinger,另一方面也要記錄到各 Client 內(nèi)部中,。另外,,Layer 也沒有直接持有 BufferQueue 對象,而是通過 Layer 內(nèi)部的 mSurfaceFlingerConsumer 來管理的,。 我是SufaceFlinger我是由 init 進(jìn)程所啟動(dòng)的守護(hù)進(jìn)程,,運(yùn)行在Android系統(tǒng)的 System 進(jìn)程中,負(fù)責(zé)管理Android系統(tǒng)的幀緩沖區(qū)(Frame
Buffer),,需要顯示 UI 界面的應(yīng)用程序需要通過 Binder 服務(wù)來與我通信,。每個(gè)有 UI 界面的程序都在我這里有相對應(yīng)的
Client 實(shí)例。應(yīng)用程序與 Client 間的 Binder 接口是 ISurfaceComposerClient,。Client
也只是我分配給應(yīng)用程序的一個(gè)”代表“ ,,真正的圖行(Buffer)需要另外申請,即調(diào)用 Client 提供的 事實(shí)上,我是一個(gè)耿直boy,,你看我的名字就知道,,我的職責(zé)是 Flinger,即把系統(tǒng)中所以應(yīng)用程序最終的“繪圖結(jié)果”進(jìn)行“混合”,,然后統(tǒng)一顯示到物理屏幕上,。所以我不會太關(guān)注各個(gè)應(yīng)用程序的“繪畫過程”,于是我又派出了一個(gè)“代表”——BufferQueue 替我去完成這一光榮的使命,。 現(xiàn)在萬事俱備,,只欠東風(fēng),我就可以鉚足干勁嘩啦啦的繪制了,,觀眾也就能看到美輪美奐的"節(jié)目"了,。那東風(fēng)從哪來?又要到哪去,?這時(shí)候就輪到我們勤勤懇懇的快遞員選手——VSync 大展身手了,。 我是VSync谷歌在4.1版本引入了一個(gè)重大的改進(jìn)——Project Butter,也即是黃油計(jì)劃,。Project Butter 對 Android Display 系統(tǒng)進(jìn)行了重構(gòu),,引入了三個(gè)核心元素,即 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ī)制
Double Buffering 異常情況上圖是在 VSync 機(jī)制下,,Double Buffering 時(shí) FPS > 手機(jī)屏幕刷新率的情況。只要出現(xiàn)一次 Jank 就會影響下一次的 VSync (cpu 不能工作) Triple Buffering 異常情況上圖是在 VSync 機(jī)制下,Triple Buffering 時(shí)FPS > 手機(jī)屏幕刷新率的情況,。當(dāng)?shù)谝淮?VSync 發(fā)生后,,CPU 不用再等待了,除了第一次的 Jank 無法規(guī)避,,第二次,、第三次 VSync 到來時(shí)都能有效采用到 buffer,從而有效降低了系統(tǒng)顯示錯(cuò)誤,。 VSync 最終會被 我是Choreographer字面翻譯過來,,我是編舞者的意思。具體來講,,我主要是配合 Vsync(因?yàn)槲铱梢员O(jiān)聽底層Vsync信號) ,,給上層 App 的渲染提供一個(gè)穩(wěn)定的 Message 處理的時(shí)機(jī)。 ViewRootImpl 啟動(dòng)時(shí)會初始化 Choreographer 的實(shí)例,。 當(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ā) |
|