Flutter UI框架繪圖流程

什么是Flutter?

Flutter 是 Google推出并開源的移動(dòng)應(yīng)用開發(fā)框架,主打跨平臺(tái)、高保真、高性能。開發(fā)者可以通過(guò) Dart語(yǔ)言開發(fā) App,一套代碼同時(shí)運(yùn)行在 iOS 和 Android平臺(tái)。 Flutter提供了豐富的組件、接口,開發(fā)者可以很快地為 Flutter添加 native擴(kuò)展。同時(shí) Flutter還使用 Native引擎渲染視圖,這無(wú)疑能為用戶提供良好的體驗(yàn)。

前端基本繪圖原理

我們都知道顯示器以固定的頻率刷新。當(dāng)一幀圖像繪制完畢后準(zhǔn)備繪制下一幀時(shí),顯示器會(huì)發(fā)出一個(gè)垂直同步信號(hào)(VSync),所以 60Hz的屏幕就會(huì)一秒內(nèi)發(fā)出 60次這樣的信號(hào)。
并且一般地來(lái)說(shuō),計(jì)算機(jī)系統(tǒng)中,CPU、GPU和顯示器以一種特定的方式協(xié)作:CPU將計(jì)算好的顯示內(nèi)容提交給 GPU,GPU渲染后放入幀緩沖區(qū),然后視頻控制器按照 VSync信號(hào)從幀緩沖區(qū)取幀數(shù)據(jù)傳遞給顯示器顯示。
所以,Android、iOS的 App在顯示 UI時(shí)是如此,F(xiàn)lutter也不例外,也遵循了這種模式。所以從這里可以看出 Flutter和 React-Native之眾的本質(zhì)區(qū)別:React-Native之類只是擴(kuò)展調(diào)用 OEM組件,而 Flutter是自己渲染。
在 Flutter Architecture的解釋中,Google還提供了一張更為詳盡的圖來(lái)解釋 Flutter的原理:


flutter-vsync.png

這張圖清晰地解釋了:Flutter只關(guān)心向 GPU提供視圖數(shù)據(jù),GPU的 VSync信號(hào)同步到 UI線程,UI線程使用 Dart來(lái)構(gòu)建抽象的視圖結(jié)構(gòu),這份數(shù)據(jù)結(jié)構(gòu)在 GPU線程進(jìn)行圖層合成,視圖數(shù)據(jù)提供給 Skia引擎渲染為 GPU數(shù)據(jù),這些數(shù)據(jù)通過(guò) OpenGL或者 Vulkan提供給 GPU。

所以 Flutter并不關(guān)心顯示器、視頻控制器以及 GPU具體工作,它只關(guān)心 GPU發(fā)出的 VSync信號(hào),盡可能快地在兩個(gè) VSync信號(hào)之間計(jì)算并合成視圖數(shù)據(jù),并且把數(shù)據(jù)提供給 GPU。

在了解 Flutter的基本概念后,我們來(lái)看下Flutter是如何被設(shè)計(jì)的。

Flutter是如何設(shè)計(jì)的?

Flutter的整個(gè)架構(gòu)設(shè)計(jì)圖:


flutter-framework.png

Flutter Framework

這是一個(gè)純 Dart實(shí)現(xiàn)的 SDK。它實(shí)現(xiàn)了一套基礎(chǔ)庫(kù), 用于處理動(dòng)畫、繪圖和手勢(shì)。并且基于繪圖封裝了一套 UI組件庫(kù),然后根據(jù) Material 和Cupertino兩種視覺風(fēng)格區(qū)分開來(lái)。這個(gè)純 Dart實(shí)現(xiàn)的 SDK被封裝為了一個(gè)叫作 dart:ui的 Dart庫(kù)。我們?cè)谑褂?Flutter寫 App的時(shí)候,直接導(dǎo)入這個(gè)庫(kù)即可使用組件等功能。

Flutter Engine

這是一個(gè)純 C++實(shí)現(xiàn)的 SDK,其中囊括了 Skia引擎、Dart運(yùn)行時(shí)、文字排版引擎等。不過(guò)說(shuō)白了,它就是 Dart的一個(gè)運(yùn)行時(shí),它可以以 JIT、JIT Snapshot 或者 AOT的模式運(yùn)行 Dart代碼。在代碼調(diào)用 dart:ui庫(kù)時(shí),提供 dart:ui庫(kù)中 Native Binding 實(shí)現(xiàn)。 這個(gè)運(yùn)行時(shí)還控制著 VSync信號(hào)的傳遞、GPU數(shù)據(jù)的填充等,并且還負(fù)責(zé)把客戶端的事件傳遞到運(yùn)行時(shí)中的代碼。

UI框架繪圖流程

流程包括7個(gè)步驟:


flutter-ui.jpg

首先是獲取到用戶的操作,然后你的應(yīng)用會(huì)因此顯示一些動(dòng)畫,接著 Flutter 開始構(gòu)建 Widget 對(duì)象。
Widget 對(duì)象構(gòu)建完成后進(jìn)入渲染階段,這個(gè)階段主要包括三步:
布局元素:決定頁(yè)面元素在屏幕上的位置和大??;
繪制階段:將頁(yè)面元素繪制成它們應(yīng)有的樣式;
合成階段:按照繪制規(guī)則將之前兩個(gè)步驟的產(chǎn)物組合在一起。
最后的光柵化由 Engine 層來(lái)完成。

在渲染階段,控件樹(widget)會(huì)轉(zhuǎn)換成對(duì)應(yīng)的渲染對(duì)象(RenderObject)樹,在 Rendering 層進(jìn)行布局和繪制。

布局的計(jì)算

在布局時(shí) Flutter 深度優(yōu)先遍歷渲染對(duì)象樹。數(shù)據(jù)流的傳遞方式是從上到下傳遞約束,從下到上傳遞大小。也就是說(shuō),父節(jié)點(diǎn)會(huì)將自己的約束傳遞給子節(jié)點(diǎn),子節(jié)點(diǎn)根據(jù)接收到的約束來(lái)計(jì)算自己的大小,然后將自己的尺寸返回給父節(jié)點(diǎn)。整個(gè)過(guò)程中,位置信息由父節(jié)點(diǎn)來(lái)控制,子節(jié)點(diǎn)并不關(guān)心自己所在的位置,而父節(jié)點(diǎn)也不關(guān)心子節(jié)點(diǎn)具體長(zhǎng)什么樣子。


flutter-layout-size.jpg

性能優(yōu)化

為了防止因子節(jié)點(diǎn)發(fā)生變化而導(dǎo)致的整個(gè)控件樹重繪,F(xiàn)lutter 加入了一個(gè)機(jī)制——Relayout Boundary,在一些特定的情形下 Relayout Boundary 會(huì)被自動(dòng)創(chuàng)建,不需要開發(fā)者手動(dòng)添加。
邊界:Flutter使用邊界標(biāo)記需要重新布局和重新繪制的節(jié)點(diǎn)部分,這樣就可以避免其他節(jié)點(diǎn)被污染或者觸發(fā)重建。就是控件大小不會(huì)影響其他控件時(shí),就沒必要重新布局整個(gè)控件樹。有了這個(gè)機(jī)制后,無(wú)論子樹發(fā)生什么樣的變化,處理范圍都只在子樹上。


flutter-relayout-boundary.jpg

緩存:要提升性能表現(xiàn),緩存也是少不了的。在 Flutter中,幾乎所有的 Element都會(huì)具有一個(gè) key,這個(gè) key是唯一的。當(dāng)子樹重建后,只會(huì)刷新 key不同的部分。而節(jié)點(diǎn)數(shù)據(jù)的復(fù)用就是依靠 key來(lái)從緩存中取得。

在確定每個(gè)空間的位置和大小之后,就進(jìn)入繪制階段。繪制節(jié)點(diǎn)的時(shí)候也是深度遍歷繪制節(jié)點(diǎn)樹,然后把不同的 RenderObject 繪制到不同的圖層上。

繪制

這時(shí)有可能出現(xiàn)一種特殊情況,如下圖所示節(jié)點(diǎn) 2 在繪制子節(jié)點(diǎn) 4 時(shí),由于其節(jié)點(diǎn) 4 需要單獨(dú)繪制到一個(gè)圖層上,因此綠色圖層上面多了個(gè)黃色的圖層。之后再需要繪制其他內(nèi)容(節(jié)點(diǎn) 5)就需要再增加一個(gè)圖層(紅色)。再接下來(lái)要繪制節(jié)點(diǎn) 1 的右子樹(節(jié)點(diǎn) 6),也會(huì)被繪制到紅色圖層上。所以如果 節(jié)點(diǎn)2發(fā)生改變就會(huì)改變紅色圖層上的內(nèi)容,因此也影響到了毫不相干的節(jié)點(diǎn)6。


flutter-repaint3.jpg

為了避免這種情況,F(xiàn)lutter 的設(shè)計(jì)者這里基于 Relayout Boundary 的思想增加了 Repaint Boundary。在繪制頁(yè)面時(shí)候如果遇見 Repaint Boundary 就會(huì)強(qiáng)制切換圖層。

如下圖所示,在從上到下遍歷控件樹遇到 Repaint Boundary 會(huì)重新繪制到新的圖層(深藍(lán)色),在從下到上返回的時(shí)候又遇到 Repaint Boundary,于是又增加一個(gè)新的圖層(淺藍(lán)色)。這樣,即使發(fā)生重繪也不會(huì)對(duì)其他子樹產(chǎn)生影響。


flutter-repaint-boundary2.jpg

整個(gè)UI框架流程大致分析如此。接下來(lái)再看下視圖的結(jié)構(gòu)。

視圖結(jié)構(gòu)

底層的UI框架在向繪制引擎提供視圖數(shù)據(jù)都需要一份結(jié)構(gòu)化的視圖數(shù)據(jù),俗稱為“視圖樹”(View Tree)。Flutter 的視圖結(jié)構(gòu)的抽象分為三部分:Widget, Element, RenderObject。

Widget

Widget是 Flutter中控件實(shí)現(xiàn)的基本單位,其意義類似于 Cocoa Touch中的 UIView。Widget里面存儲(chǔ)了一個(gè)視圖的配置信息,包括布局、屬性等待。所以它只是一份輕量的,可直接使用的數(shù)據(jù)結(jié)構(gòu)。在構(gòu)建為結(jié)構(gòu)樹,甚至重新創(chuàng)建和銷毀結(jié)構(gòu)樹時(shí)都不存在明顯的性能問題。

Element

Element是 Widget的抽象,它其實(shí)承載了視圖構(gòu)建的上下文數(shù)據(jù)。構(gòu)建系統(tǒng)通過(guò)遍歷 Element樹來(lái)構(gòu)建 RenderObject數(shù)據(jù),比如視圖更新時(shí),只會(huì)標(biāo)記 dirty Element,而不會(huì)標(biāo)記 dirty Widget。所以 Widget“無(wú)狀態(tài)”,而 Element“有狀態(tài)” (這個(gè)狀態(tài)指框架層的構(gòu)建狀態(tài))。

RenderObject

在 RenderObject樹中會(huì)發(fā)生 Layout、Paint的繪制事件,所以 Flutter中大部分的繪圖性能優(yōu)化發(fā)生在這里。RenderObject樹構(gòu)建的數(shù)據(jù)會(huì)被加入到 Engine所需的 LayerTree中,Engine通過(guò) LayerTree進(jìn)行視圖合成并光柵化,提交給 GPU。

Flutter通過(guò)這三種概念,把原本比較復(fù)雜的視圖樹狀態(tài)、數(shù)據(jù)的維護(hù)和構(gòu)建拆分得更單一、易于集中治理。

作者:gofun成都技術(shù)中心-micck

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容