作者:yuzhiqiang、sunfei、wanglei,華為軟件架構(gòu)工程師UI 框架簡介以及業(yè)界發(fā)展趨勢
UI,即用戶界面,主要包含視覺(比如圖像、文字、動(dòng)畫等可視化內(nèi)容)以及交互(比如按鈕點(diǎn)擊、列表滑動(dòng)、圖片縮放等用戶操作)。UI框架,則是為開發(fā)UI而提供的基礎(chǔ)設(shè)施,比如視圖布局,UI組件,事件響應(yīng)機(jī)制等。
從操作系統(tǒng)平臺(tái)支持方式來看,UI框架一般可分為原生UI框架和跨平臺(tái)UI框架兩種。
1.原生UI框架。一般是指操作系統(tǒng)自帶的UI框架,典型的例子包括iOS的UI Kit,Android的View框架等。這些UI框架和操作系統(tǒng)深度綁定,一般只能運(yùn)行在相應(yīng)的操作系統(tǒng)上。功能,性能,開發(fā)調(diào)測等方面和相應(yīng)的操作系統(tǒng)結(jié)合較好。
2.跨平臺(tái)UI框架。一般是指可以在不同的平臺(tái)(OS)上運(yùn)行的獨(dú)立的UI框架。典型例子包括HTML5以及基于HTML5延伸出來的前端框架React Native, 以及Google 的Flutter等。跨平臺(tái)UI框架的目標(biāo)是代碼只需一次編寫,經(jīng)過少量修改甚至不修改,可以部署到不同的操作系統(tǒng)平臺(tái)上。當(dāng)然,實(shí)現(xiàn)跨平臺(tái)也是有代價(jià)的,由于不同平臺(tái)存在差異性(比如UI的呈現(xiàn)方式差異,API差異等等),導(dǎo)致UI框架本身的架構(gòu)實(shí)現(xiàn),以及和不同平臺(tái)的融合都有不小的挑戰(zhàn)。
從編程方式上來看,UI框架一般可分為命令式UI框架和聲明式UI框架兩種:
1.命令式UI框架,過程導(dǎo)向——告訴“機(jī)器”具體步驟,命令“機(jī)器”按照指定步驟去做。比如Android原生UI框架(View框架)或iOS的UIKit,提供了一系列的API讓開發(fā)者直接操控UI組件-比如定位到某個(gè)指定UI組件,進(jìn)行屬性變更等。
這種方式的優(yōu)點(diǎn)是開發(fā)者可以控制具體的實(shí)現(xiàn)路徑,經(jīng)驗(yàn)豐富的開發(fā)者能夠?qū)懗鲚^為高效的實(shí)現(xiàn)。不過這種情況下,開發(fā)者需了解大量的API細(xì)節(jié)并指定好具體的執(zhí)行路徑,開發(fā)門檻較高。具體的實(shí)現(xiàn)效果上,也高度依賴開發(fā)者本身的開發(fā)技能。另外,由于和具體實(shí)現(xiàn)綁定較緊,在跨設(shè)備情況下,靈活性和擴(kuò)展性相對(duì)有限。
2.聲明式UI框架,結(jié)果導(dǎo)向—— 告訴“機(jī)器”你需要什么,“機(jī)器”負(fù)責(zé)怎么去做。比如Web前端框架Vue,或iOS的SwiftUI等,框架會(huì)根據(jù)聲明式語法的描述,渲染出相應(yīng)的UI,同時(shí)結(jié)合相應(yīng)編程模型,框架會(huì)根據(jù)數(shù)據(jù)的變化來自動(dòng)更新相應(yīng)的UI。
這種方式的優(yōu)點(diǎn)是開發(fā)者只需描述好結(jié)果,相應(yīng)的實(shí)現(xiàn)和優(yōu)化由框架來處理。另外,由于結(jié)果描述和具體實(shí)現(xiàn)分離,實(shí)現(xiàn)方式相對(duì)靈活同時(shí)容易擴(kuò)展。不過這種情況下,對(duì)框架的要求較高,需要框架有完備的直觀的描述能力并能夠針對(duì)相應(yīng)的描述信息實(shí)現(xiàn)高效的處理。
UI框架是應(yīng)用開發(fā)的核心組成部分??v觀業(yè)界UI框架,其主要發(fā)展趨勢表現(xiàn)為:
從命令式UI往聲明式UI發(fā)展
比如iOS中的UIKit到SwiftUI, Android中的View到Jetpack Compose。這樣可以實(shí)現(xiàn)更加直觀便捷的UI開發(fā)。
UI框架和語言運(yùn)行時(shí)深度融合SwiftUI,Jetpack Compose, Flutter都利用了各自的語言特性——比如在UI描述方面,SwiftUI中的Swift語言,Jetpack Compose中的Kotlin語言都精簡了UI描述語法;在性能方面, Swift通過引入輕量化結(jié)構(gòu)體等語言特性更好的實(shí)現(xiàn)內(nèi)存快速分配和釋放,F(xiàn)lutter中Dart語言則在運(yùn)行時(shí)專門針對(duì)小對(duì)象內(nèi)存管理做相應(yīng)優(yōu)化等??缙脚_(tái)(OS)能力跨平臺(tái)(OS)能力可以讓一套代碼復(fù)用到不同的OS上,主要是為了提升開發(fā)效率,降低開發(fā)成本。不過這里面也有一系列的挑戰(zhàn),比如運(yùn)行在不同平臺(tái)上的性能問題,能力和渲染效果的一致性問題等。業(yè)界在這方面也是不斷的演進(jìn),主要有幾種方式:
1.JS/Web方案。比如HTML5利用JS/Web的標(biāo)準(zhǔn)化生態(tài),通過相應(yīng)的Web引擎實(shí)現(xiàn)跨平臺(tái)目標(biāo);
2.JS+Native混合方式。比如React Native、Weex等,結(jié)合JS橋接到原生UI組件的方式實(shí)現(xiàn)了一套應(yīng)用代碼能夠運(yùn)行到不同OS上;
3.平臺(tái)無關(guān)的UI自繪制能力+新的語言。比如Flutter,整個(gè)UI基于底層畫布由框架層來繪制,同時(shí)結(jié)合Dart語言實(shí)現(xiàn)完整的UI框架。Flutter從設(shè)計(jì)之初就是將跨平臺(tái)能力作為重要的競爭力去吸引更多的開發(fā)者;
另外,有趣的是,部分原生開發(fā)框架也開始往跨平臺(tái)演進(jìn)。比如,Android原生的開發(fā)框架Jetpack Compose也開始將跨OS支持作為其中的目標(biāo),計(jì)劃將Compose拓展到桌面平臺(tái),比如Windows,MacOS等。
然而,隨著智能設(shè)備的普及,多設(shè)備場景下,設(shè)備的形態(tài)差異(屏幕大小、分辨率,形狀, 交互模式等),設(shè)備的能力差異(從百K級(jí)內(nèi)存到G級(jí)內(nèi)存設(shè)備等)以及應(yīng)用需要在不同設(shè)備間協(xié)同,這些都對(duì)UI框架以及應(yīng)用開發(fā)帶來了新的挑戰(zhàn)。
為什么要重新設(shè)計(jì)一個(gè)ACE UI框架
ACE全稱是Ability Cross-platform Environment (元能力跨平臺(tái)執(zhí)行環(huán)境)。是華為設(shè)計(jì)的應(yīng)用在HarmonyOS上的UI框架。ACE UI框架結(jié)合HarmonyOS的基礎(chǔ)運(yùn)行單元Ability,語言和運(yùn)行時(shí),以及各種平臺(tái)(OS)能力API等共同構(gòu)成HarmonyOS應(yīng)用開發(fā)的基礎(chǔ),實(shí)現(xiàn)了跨設(shè)備分布式調(diào)度,以及原子化服務(wù)免安裝等能力。
ACE提供兩種開發(fā)語言以供不同開發(fā)者進(jìn)行選擇,分別為Java語言和JavaScript語言,其中Java僅支持在內(nèi)存較大的設(shè)備上使用如大屏、手機(jī)、平板等設(shè)備使用,而JavaScript支持在百K級(jí)到G級(jí)設(shè)備上使用。
目前主流的UI框架都有各自的不足。另外,在多設(shè)備場景下,由于不同的設(shè)備形態(tài)以及設(shè)備能力的巨大差異,目前業(yè)界還沒有任何一個(gè)UI框架能夠較好的解決相應(yīng)的問題。
而ACE UI框架的整體設(shè)計(jì)思路是:
1.建立分層機(jī)制,引入高效的UI基礎(chǔ)后端,并能夠與OS平臺(tái)解耦,形成一致化的UI體驗(yàn)
2.通過多前端的方式擴(kuò)展應(yīng)用生態(tài),并結(jié)合聲明式UI,在開發(fā)效率上持續(xù)演進(jìn)
3.框架層統(tǒng)一結(jié)合語言以及運(yùn)行時(shí),分布式,組件化設(shè)計(jì)等,圍繞跨設(shè)備,進(jìn)一步提升體驗(yàn)
ACE將應(yīng)用的UI界面進(jìn)行解析,通過創(chuàng)建后端具體UI組件、進(jìn)行布局計(jì)算、資源加載等處理后生成具體繪制指令,并將繪制命令發(fā)送給渲染引擎,渲染引擎將繪制指令轉(zhuǎn)換為具體屏幕像素,最終通過顯示設(shè)備將應(yīng)用程序轉(zhuǎn)換為可見的界面效果展示給用戶。
ACE UI框架的整體架構(gòu)如下圖所示,主要由前端框架層、橋接層、引擎層和平臺(tái)抽象層四大部分組成,下面我們一一介紹。
1前端框架層該層主要包括相應(yīng)的開發(fā)范式(比如主流的類Web開發(fā)范式),組件/API,以及編程模型MVVM(Model-View-ViewModel)。其中Model是數(shù)據(jù)模型層,代表了從數(shù)據(jù)源讀取到的數(shù)據(jù);View是視圖UI層,通過一定的形式把系統(tǒng)中的數(shù)據(jù)向用戶呈現(xiàn)出來;ViewModel: 視圖模型層,是數(shù)據(jù)和視圖之間的橋梁。它雙向綁定了視圖和數(shù)據(jù),使得數(shù)據(jù)的變更能夠及時(shí)在視圖上呈現(xiàn),用戶在視圖上的修改也能夠及時(shí)傳遞給后臺(tái)數(shù)據(jù),從而實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)的UI自動(dòng)變更。
開發(fā)范式可以擴(kuò)展,來支持生態(tài)發(fā)展。不同的開發(fā)范式可以統(tǒng)一適配到底層的引擎層
2橋接層
該層主要是作為一個(gè)中間層,實(shí)現(xiàn)前端開發(fā)范式到底層引擎(包括UI后端,語言&運(yùn)行時(shí))的對(duì)接。
3引擎層該層主要包含兩部分:UI后端引擎和語言執(zhí)行引擎。
1.由C++構(gòu)建的UI后端引擎,包括UI組件、布局視圖、動(dòng)畫事件、自繪制渲染管線和渲染引擎 。
在渲染方面,我們盡可能把這部分組件設(shè)計(jì)得小而靈活。這樣的設(shè)計(jì),為不同前端框架提供靈活的UI能力,這部分通過C++組件組合而成。通過底層組件的按需組合,布局計(jì)算和渲染并行化,并結(jié)合上層開發(fā)范式實(shí)現(xiàn)了視圖變化最小化的局部更新機(jī)制,從而實(shí)現(xiàn)高效的UI渲染。
除此之外,引擎層還提供了組件的渲染管線、動(dòng)畫、主題、事件處理等基礎(chǔ)能力。目前復(fù)用了Flutter引擎提供基礎(chǔ)的圖形渲染能力、字體管理、文字排版等能力,底層使用Skia或其他圖形庫實(shí)現(xiàn),并通過OpenGL實(shí)現(xiàn)GPU硬件渲染加速。
在多設(shè)備UI適配方面,通過多種原子化布局能力(自動(dòng)折行、隱藏、等比縮放等),多態(tài)UI控件(描述統(tǒng)一,表現(xiàn)形式多樣),以及統(tǒng)一交互框架(不同的交互方式歸一到統(tǒng)一的事件處理)來滿足不同設(shè)備的形態(tài)差異化需求。
另外,引擎層也包含了能力擴(kuò)展基礎(chǔ)設(shè)施,來實(shí)現(xiàn)自定義組件以及系統(tǒng)API的能力擴(kuò)展
2.語言&運(yùn)行時(shí)執(zhí)行引擎??筛鶕?jù)需要切換到不同的運(yùn)行時(shí)執(zhí)行引擎,滿足不同設(shè)備的能力差異化需求。
4平臺(tái)抽象層
該層主要是通過平臺(tái)抽象,將平臺(tái)依賴聚焦到底層畫布,通用線程以及事件機(jī)制等少數(shù)必要的接口上,為跨平臺(tái)打造了相應(yīng)的基礎(chǔ)設(shè)施,并能夠?qū)崿F(xiàn)一致化UI渲染體驗(yàn)。
相應(yīng)的,配套的開發(fā)者工具(HUAWEI DevEco Studio)結(jié)合ACE UI的跨平臺(tái)渲染基礎(chǔ)設(shè)施,以及自適應(yīng)渲染,可做到和設(shè)備比較一致的渲染體驗(yàn)以及多設(shè)備上的UI實(shí)時(shí)預(yù)覽。
另外,ACE UI框架還設(shè)計(jì)了可伸縮的架構(gòu),即前端框架、語言運(yùn)行時(shí)、UI后端等都做了解耦,可以有不同的實(shí)現(xiàn)。這樣就具備可部署到百K級(jí)內(nèi)存的輕量級(jí)設(shè)備的能力,如下所示:
在ACE UI的輕量化實(shí)現(xiàn)中,通過前端框架核心下沉C++化,減小JS部分的內(nèi)存占用,使用C++進(jìn)行更為嚴(yán)格的內(nèi)存分配與管理,并且采用更為輕量的JS引擎,UI部分采用輕量的UIKit并結(jié)合輕量圖形引擎,達(dá)到內(nèi)存非常輕量占用的目標(biāo)。
接口能力保證是全量能力的子集,這樣可以保證輕量化設(shè)備上可執(zhí)行的應(yīng)用,可以在更高等級(jí)的設(shè)備上執(zhí)行,而無需重新開發(fā)。這也就是采用ACE JS開發(fā)范式的優(yōu)勢所在,采用統(tǒng)一的開發(fā)范式進(jìn)行應(yīng)用開發(fā)后,開發(fā)者無需關(guān)心具體運(yùn)行時(shí)的前端框架、JS引擎與后端UI組件。
根據(jù)運(yùn)行平臺(tái)不同,采用最佳的模塊,保障了應(yīng)用在不同平臺(tái)都可具有最佳的運(yùn)行性能。不過由于輕量級(jí)設(shè)備上的資源限制, 所支持的API 能力相對(duì)有限,但公共部分的API是完全共通的。
綜上所述,ACE UI框架具備如下特點(diǎn):
支持主流的語言生態(tài) – JavaScript;
支持類Web開發(fā)范式, MVVM機(jī)制。并在架構(gòu)上可支持多前端開發(fā)范式,進(jìn)一步簡化開發(fā);
通過統(tǒng)一的UI后端,實(shí)現(xiàn)高性能以及跨平臺(tái)一致化的渲染體驗(yàn);
通過多態(tài)UI、原子化布局、統(tǒng)一交互,以及可伸縮的運(yùn)行時(shí)設(shè)計(jì),進(jìn)一步降低不同設(shè)備形態(tài)下的UI開發(fā)門檻,并能夠通過統(tǒng)一的開發(fā)范式,實(shí)現(xiàn)一套代碼跨設(shè)備部署(覆蓋百K級(jí)到G級(jí)內(nèi)存設(shè)備)。
ACE UI框架渲染流程解析
接下來我們通過一個(gè)手機(jī)側(cè)ACE JS應(yīng)用渲染流程的完整流程來介紹ACE UI框架的具體渲染技術(shù)。
1線程模型
ACE JS應(yīng)用啟動(dòng)時(shí)會(huì)創(chuàng)建一系列線程,形成獨(dú)立的線程模型,以實(shí)現(xiàn)高性能的渲染流程。
每個(gè)ACE JS應(yīng)用的進(jìn)程,包含唯一一個(gè)Platform線程和若干后臺(tái)線程組成的異步任務(wù)線程池:
Platform線程:當(dāng)前平臺(tái)的主線程,也就是應(yīng)用的主線程,主要負(fù)責(zé)平臺(tái)層的交互、應(yīng)用生命周期以及窗口環(huán)境的創(chuàng)建
后臺(tái)線程池:一系列后臺(tái)任務(wù),用于一些低優(yōu)先級(jí)的可并行異步任務(wù),如網(wǎng)絡(luò)請求、Asset資源加載等。除此之外,每個(gè)實(shí)例還包括一系列專有線程
JS線程:JS前端框架的執(zhí)行線程,應(yīng)用的JS邏輯以及應(yīng)用UI界面的解析構(gòu)建都在該線程執(zhí)行
UI線程:引擎的核心線程,組件樹的構(gòu)建以及整個(gè)渲染管線的核心邏輯都在該線程:包括渲染樹的構(gòu)建、布局、繪制以及動(dòng)畫調(diào)度
GPU線程:現(xiàn)代的渲染引擎,為了充分發(fā)揮硬件性能,都支持GPU硬件加速,在該線程上,會(huì)通過系統(tǒng)的窗口句柄,創(chuàng)建GPU加速的OpenGL環(huán)境,負(fù)責(zé)將整個(gè)渲染樹的內(nèi)容光柵化,直接將每一幀的內(nèi)容渲染合成到該窗口的Surface上并送顯
IO線程:主要為了異步的文件IO讀寫,同時(shí)該線程會(huì)創(chuàng)建一個(gè)離屏的GL環(huán)境,這個(gè)環(huán)境和 GPU線程的GL環(huán)境是同一個(gè)共享組,可以共享資源,圖片資源解碼的內(nèi)容可直接在該線程上傳生成GPU紋理,實(shí)現(xiàn)更高效的圖片渲染
每個(gè)線程的作用,在后續(xù)的渲染流程中還會(huì)進(jìn)一步提到。2前端腳本解析
ACE UI框架支持不同的開發(fā)范式,可以對(duì)接到不同的前端框架上。
以類Web開發(fā)范式為例,開發(fā)者開發(fā)的應(yīng)用,通過開發(fā)工具鏈的編譯,會(huì)生成引擎可執(zhí)行的Bundle文件。應(yīng)用啟動(dòng)時(shí),會(huì)將Bundle文件在JS線程上進(jìn)行加載,并且將該內(nèi)容作為輸入,供JS引擎進(jìn)行解析執(zhí)行。
最終生成前端組件的結(jié)構(gòu)化描述,并建立數(shù)據(jù)綁定關(guān)系。例如包含若干簡單文本的應(yīng)用會(huì)生成類似下圖的樹形結(jié)構(gòu),每個(gè)組件節(jié)點(diǎn)會(huì)包含該節(jié)點(diǎn)的屬性及樣式信息。
3渲染管線構(gòu)建
如上圖,前端框架的解析后,根據(jù)具體的組件規(guī)范定義向前端框架對(duì)接層請求創(chuàng)建ACE渲染引擎提供的組件。
前端框架對(duì)接層通過ACE引擎層提供的Component組件實(shí)現(xiàn)前端組件定義的能力。Component是一個(gè)由C++實(shí)現(xiàn)的UI組件的聲明式描述,描述了UI組件的屬性及樣式,用于生成組件的實(shí)體元素。每一個(gè)前端組件會(huì)對(duì)接到一個(gè)Composed Component,表示一個(gè)組合型的UI組件,通過不同的子Component組合,構(gòu)造出前端對(duì)應(yīng)的Composed組件。每個(gè)Composed組件是前后端對(duì)接的一個(gè)基礎(chǔ)的更新單位。
以上面的前端組件樹為例,每個(gè)節(jié)點(diǎn)會(huì)使用一組Composed組件進(jìn)行組合描述,對(duì)應(yīng)關(guān)系如下圖,該對(duì)應(yīng)關(guān)系只是一個(gè)示例,實(shí)際場景的對(duì)應(yīng)關(guān)系可能會(huì)更復(fù)雜。
有了每個(gè)前端節(jié)點(diǎn)對(duì)應(yīng)的Component,就形成了一個(gè)完成Page的描述結(jié)構(gòu),通知渲染管線掛載新的頁面。
在Page掛載之前,渲染管線已經(jīng)提前創(chuàng)建了幾個(gè)關(guān)鍵的核心結(jié)構(gòu),Element樹和Render樹:
Element樹,Element是Component的實(shí)例,表示一個(gè)具體的組件節(jié)點(diǎn),它形成的Element樹負(fù)責(zé)維持界面在整個(gè)運(yùn)行時(shí)的樹形結(jié)構(gòu),方便計(jì)算局部更新算法。另外對(duì)于一些復(fù)雜的組件,在該數(shù)據(jù)結(jié)構(gòu)上會(huì)實(shí)現(xiàn)一些對(duì)子組件邏輯上的管理。
Render樹,對(duì)于每個(gè)可顯示的Element都會(huì)為其創(chuàng)建對(duì)應(yīng)的RenderNode,它負(fù)責(zé)一個(gè)節(jié)點(diǎn)的顯示信息,它形成的Render樹維護(hù)著整個(gè)界面的渲染需要用到的信息,包括它的位置、大小、繪制命令等,界面后續(xù)的布局、繪制都是在Render樹上進(jìn)行的。
當(dāng)應(yīng)用啟動(dòng)時(shí),最初形成的Element樹只有幾個(gè)基礎(chǔ)的幾節(jié)點(diǎn),一般包括root、overlay、stage,分別作用如下:
RootElement:Element樹的根節(jié)點(diǎn),僅僅負(fù)責(zé)全局背景色的繪制
OverlayElement:一個(gè)全局的懸浮層容器,用于彈窗等全局繪制場景的管理
StageElement:一個(gè)Stack容器,作為全局的“舞臺(tái)”,每個(gè)加載完成的頁面都要掛載到這個(gè)“舞臺(tái)”下,它管理應(yīng)用的多個(gè)頁面之間的轉(zhuǎn)場動(dòng)效等。
在Element樹創(chuàng)建的過程中,也會(huì)同步的把Render樹也創(chuàng)建起來。
當(dāng)前端框架對(duì)接層通知渲染管線準(zhǔn)備好了頁面,在下一個(gè)幀同步信號(hào)(VSync)到來時(shí),就會(huì)在渲染管線上進(jìn)行頁面的掛載,具體流程就是通過Component來實(shí)例化生成Element的過程,創(chuàng)建成功的Element同步創(chuàng)建對(duì)應(yīng)的RenderNode:
如上圖所示,目標(biāo)要將整個(gè)Page的Component描述掛載到StageElement上,如果當(dāng)前Stage下還未有任何Element節(jié)點(diǎn),就會(huì)遞歸逐個(gè)節(jié)點(diǎn)生成Component對(duì)應(yīng)的Element節(jié)點(diǎn)。
對(duì)于組合類型的ComposedElement,則同時(shí)會(huì)把Element的引用記錄到一個(gè)Composed Map中,方便后續(xù)更新時(shí)快速查找。對(duì)于可見類型的容器節(jié)點(diǎn)或渲染節(jié)點(diǎn),則會(huì)創(chuàng)建對(duì)應(yīng)的RenderNode,并掛在Render樹上。
當(dāng)生成了當(dāng)前頁面的Element樹和Render樹,頁面渲染構(gòu)建的完整過程就結(jié)束了。4布局繪制機(jī)制
接下來就進(jìn)入了布局和繪制的階段,布局和繪制都是在Render樹上進(jìn)行的。每個(gè)RenderNode都會(huì)實(shí)現(xiàn)自己的布局算法和繪制方法。
布局
布局的過程就是通過各類布局的算法計(jì)算出每個(gè)RenderNode在相對(duì)空間上的真實(shí)大小和位置。
如下圖所示,當(dāng)某個(gè)節(jié)點(diǎn)的內(nèi)容發(fā)生變化時(shí),就會(huì)標(biāo)記自己為needLayout,并一直向上標(biāo)記到布局邊界(ReLayout Boundary),布局邊界是重新布局的一個(gè)范圍標(biāo)記,一般情況下,如果一個(gè)節(jié)點(diǎn)的布局參數(shù)信息(LayoutParam)是強(qiáng)約束的,例如它布局期望的最大尺寸和最小尺寸是相同的,那么它就可以作為一個(gè)布局邊界。
布局是個(gè)深度優(yōu)先遍歷的過程。從布局邊界開始,父節(jié)點(diǎn)自頂向下將LayoutParam傳給子節(jié)點(diǎn),子節(jié)點(diǎn)自底向上據(jù)此計(jì)算得到尺寸大小和位置。
對(duì)于每個(gè)節(jié)點(diǎn)來說,布局分為三個(gè)步驟:
① 當(dāng)前節(jié)點(diǎn)遞歸調(diào)用子節(jié)點(diǎn)的layout方法,并傳遞布局的參數(shù)信息(LayoutParam),包含了布局期望的最大尺寸和最小尺寸等;
② 子節(jié)點(diǎn)根據(jù)布局參數(shù)信息,使用自己定義的布局算法來計(jì)算自己的尺寸大??;
③ 當(dāng)前節(jié)點(diǎn)獲取子節(jié)點(diǎn)布局后的大小,再根據(jù)自己的布局算法來計(jì)算每個(gè)子節(jié)點(diǎn)的位置信息,并將相對(duì)位置設(shè)置給子節(jié)點(diǎn)保存。
根據(jù)上述的流程,一次布局遍歷完成后,每個(gè)節(jié)點(diǎn)的大小和位置就都計(jì)算出來了,可以進(jìn)行下一步的繪制。
繪制
同布局一樣,繪制也是一個(gè)深度遍歷的過程,遍歷調(diào)用每個(gè)RenderNode的Paint方法,此時(shí)的繪制只是根據(jù)布局算出來的大小和位置,在當(dāng)前繪制的上下文記錄每個(gè)節(jié)點(diǎn)的繪制命令。
為什么是記錄命令,而不是直接繪制渲染呢?在現(xiàn)代的渲染引擎中,為了充分使用GPU硬件加速的能力,一般都會(huì)使用DisplayList的機(jī)制,繪制過程中僅僅將繪制的命令記錄下來。
在GPU渲染的時(shí)候統(tǒng)一轉(zhuǎn)成OpenGL的指令執(zhí)行,能最大限度的提高圖形的處理效率。所以在上面提到的繪制上下文中,會(huì)提供一個(gè)可以記錄繪制命令的畫布(Canvas)。每一個(gè)獨(dú)立的繪制上下文可以看作是一個(gè)圖層。
為了提高性能,這里引入了圖層(Layer)的概念。通常繪制會(huì)將渲染內(nèi)容分為多個(gè)層進(jìn)行加速。對(duì)于會(huì)頻繁變化的內(nèi)容,將其單獨(dú)創(chuàng)建一個(gè)圖層,那么這個(gè)獨(dú)立圖層的頻繁刷新就不必導(dǎo)致其他內(nèi)容重新繪制,從而達(dá)到提升性能并減少功耗的效果,同時(shí)還可以支持GPU緩存等優(yōu)化。每個(gè)RenderNode都可以決定自己是否需要單獨(dú)分層。
如下圖所示,繪制流程會(huì)從需要繪制的節(jié)點(diǎn)中,挑選最近的且需要分層的節(jié)點(diǎn)開始,自頂向下的執(zhí)行每個(gè)節(jié)點(diǎn)的Paint方法。
對(duì)每個(gè)節(jié)點(diǎn),繪制分為四個(gè)步驟:
① 如果當(dāng)前節(jié)點(diǎn)需要分層,那么需要?jiǎng)?chuàng)建一個(gè)新的繪制上下文,并提供可以記錄繪制命令的畫布;
② 在當(dāng)前的畫布上記錄背景的繪制命令;
③ 遞歸調(diào)用子節(jié)點(diǎn)的繪制方法,記錄子節(jié)點(diǎn)的繪制命令;
④ 在當(dāng)前的畫布上記錄前景的繪制命令。
一次完整的繪制流程結(jié)束后,我們會(huì)得到一棵完整的Layer樹,Layer樹上包含了這一幀完整的繪制信息:包括每一層的位置、transform信息、Clip信息、以及每個(gè)元素的繪制命令。下一步就要經(jīng)過光柵化和合成的過程,將這一幀的內(nèi)容顯示到界面。
5光柵化合成機(jī)制
在上面的繪制流程結(jié)束后,會(huì)通知GPU線程開始進(jìn)行合成的流程。
UI線程(UI Thread)在渲染管線中的輸出是LayerTree,它相當(dāng)于一個(gè)生產(chǎn)者,將生產(chǎn)的LayerTree添加到渲染隊(duì)列中。GPU線程(GPU Thread)的合成器(Compositor)相當(dāng)于消費(fèi)者,每個(gè)新的渲染周期中,合成器會(huì)從渲染隊(duì)列中獲取一個(gè)LayerTree進(jìn)行合成消費(fèi)。
對(duì)于需要緩存的Layer,還要執(zhí)行光柵化生成GPU紋理,所謂光柵化就是將Layer里面記錄的命令進(jìn)行回放,生成每個(gè)實(shí)體的像素的過程。像素是存儲(chǔ)在紋理的圖形內(nèi)存中。
合成器會(huì)從系統(tǒng)的窗口中獲取當(dāng)前的Surface,將每個(gè)Layer生成的紋理進(jìn)行合成,最終合成到當(dāng)前Surface的圖形內(nèi)存(Graphic Buffer)中。這塊內(nèi)存中存儲(chǔ)的就是當(dāng)前幀的渲染結(jié)果內(nèi)容。最終還需要將渲染結(jié)果提交到系統(tǒng)合成器中合成顯示。系統(tǒng)的合成過程如下圖所示:
當(dāng)GPU線程的合成器完成一幀的合成后,會(huì)進(jìn)行一次SwapBuffer的操作,將生成的Graphic Buffer提交到與系統(tǒng)合成器建立的幀緩沖隊(duì)列(Buffer Queue)中。系統(tǒng)合成器會(huì)從各個(gè)生產(chǎn)端獲取最新的內(nèi)容進(jìn)行最終的合成。
系統(tǒng)合成器會(huì)將當(dāng)前應(yīng)用的內(nèi)容和系統(tǒng)其它的顯示內(nèi)容,例如System UI的狀態(tài)欄、導(dǎo)航欄,進(jìn)行一次合成,最終寫入到屏幕對(duì)應(yīng)的幀緩沖區(qū)(Frame Buffer)中。液晶屏的驅(qū)動(dòng)就會(huì)從緩沖區(qū)讀取內(nèi)容進(jìn)行屏幕的刷新,最終將內(nèi)容顯示到屏幕上。
6局部更新機(jī)制
經(jīng)過上面1~5的流程,完成了首次完整的渲染的流程,在后續(xù)的運(yùn)行中,例如用戶輸入、動(dòng)畫、數(shù)據(jù)改變都有可能造成頁面的刷新,如果只是部分元素發(fā)生了變化,并不需要全局的刷新,只需要啟動(dòng)局部更新即可。那么局部更新是怎么做到的?下面我們介紹一下局部 更新的流程。
JS在代碼中更新了數(shù)據(jù),通過數(shù)據(jù)綁定模塊會(huì)自動(dòng)觸發(fā)前端組件屬性的更新,然后通過JS引擎異步發(fā)起更新屬性的請求。前端組件會(huì)根據(jù)變更的屬性,構(gòu)建一組新的Composed的補(bǔ)?。≒atch),作為渲染管線更新的輸入。
在下一個(gè)VSync到來時(shí),渲染管線會(huì)在UI線程開始更新的流程。通過Composed補(bǔ)丁的Id,在ComposedMap中查詢到對(duì)應(yīng)的ComposedElement在Element樹上的位置。通過補(bǔ)丁對(duì)Element樹進(jìn)行更新。
以ComposedElement為起始,逐層進(jìn)行對(duì)比,如果節(jié)點(diǎn)類型一致則直接更新對(duì)應(yīng)屬性和對(duì)應(yīng)的RenderNode,如果不一致則重新創(chuàng)建新的Element和RenderNode。并將相關(guān)的RenderNode標(biāo)記為needLayout和needRender。
根據(jù)標(biāo)記需要重新布局和重新渲染的RenderNode,從最近的布局邊界和繪制圖層進(jìn)行布局和繪制的流程,生成新的Layer樹,只需要重新生成變更RenderNode對(duì)應(yīng)的Layer即可。
接下來,根據(jù)刷新后的Layer樹作為輸入,在GPU線程進(jìn)行光柵化和合成。對(duì)于已經(jīng)緩存的Layer則不需要重新光柵化,合成器只需要將已緩存的Layer和未緩存或更新的Layer重新合成即可。最終經(jīng)過系統(tǒng)合成器的合成,就會(huì)將新一幀的內(nèi)容顯示。
以上就是一個(gè)ACE JS應(yīng)用的渲染及更新的流程。最后,通過一張流程圖回顧一下整體的流程:
ACE UI框架目前的成熟度以及演進(jìn)
截至目前,ACE UI框架已商用落地了華為運(yùn)動(dòng)手表,華為智能手表,華為智慧屏,華為手機(jī),華為平板等一系列產(chǎn)品。使用場景包括日歷、出行、健身、實(shí)用工具等各類應(yīng)用,手機(jī)-設(shè)備碰一碰全品類的應(yīng)用,以及今年六月份發(fā)布的HarmonyOS中各類的服務(wù)卡片-圖庫、相機(jī)等。
另外,在開發(fā)調(diào)測方面,開發(fā)者工具(HUAWEI DevEco Studio)中也集成了ACE UI框架,支持在PC端(MacOS,Windows)上的開發(fā)調(diào)測,實(shí)時(shí)預(yù)覽(包括實(shí)時(shí)多設(shè)備預(yù)覽,組件級(jí)預(yù)覽,雙向預(yù)覽等),實(shí)現(xiàn)了在PC上和設(shè)備上一致的渲染體驗(yàn)。
未來,面向開發(fā)者的極簡開發(fā),面向消費(fèi)者的流暢酷炫的體驗(yàn),以及能夠高效在不同設(shè)備不同平臺(tái)上部署,ACE UI框架會(huì)繼續(xù)沿著精簡開發(fā)和高性能兩個(gè)方面演進(jìn)。
結(jié)合語言更進(jìn)一步簡化開發(fā)范式,結(jié)合運(yùn)行時(shí)在跨語言交互,類型優(yōu)化等方面進(jìn)一步增強(qiáng)性能體驗(yàn),結(jié)合分布式能力將編程模型從MVVM演進(jìn)到分布式MVVM(Distributed Model-View-ViewModel)等。采用類自然語言的聲明式UI描述進(jìn)行界面搭建,編程語言也進(jìn)一步開放,未來考慮向TS進(jìn)行演進(jìn),從動(dòng)效、布局和性能方面進(jìn)一步提升用戶使用體驗(yàn)。
當(dāng)然,應(yīng)用生態(tài)還會(huì)涉及更多的方面,比如三方插件的繁榮,跨OS平臺(tái)的擴(kuò)展,更具創(chuàng)新的分布式體驗(yàn)等等。ACE UI框架還很年輕,期待和眾多開發(fā)者一起,重點(diǎn)圍繞著多設(shè)備組成的超級(jí)終端的新興場景,不斷打磨完善,共同構(gòu)建領(lǐng)先的應(yīng)用體驗(yàn)和生態(tài)!
目前HarmonyOS的線上開發(fā)體驗(yàn)已上架,歡迎大家在線體驗(yàn)。ACE JS框架已經(jīng)進(jìn)駐開源社區(qū),歡迎關(guān)注與共建,期待諸位一起共建我們的開發(fā)框架,有開發(fā)過程中的疑問和對(duì)HarmonyOS開發(fā)的好的建議歡迎登錄論壇,我們一起探討。
編輯:jq
-
ui
+關(guān)注
關(guān)注
0文章
203瀏覽量
21330
原文標(biāo)題:深入解析ACE UI框架,帶你看懂UI渲染流程
文章出處:【微信號(hào):HarmonyOS_Dev,微信公眾號(hào):HarmonyOS開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評(píng)論請先 登錄
相關(guān)推薦
評(píng)論