日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

从架构到源码:一文了解Flutter渲染机制

發(fā)布時(shí)間:2024/9/3 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从架构到源码:一文了解Flutter渲染机制 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
簡(jiǎn)介:Flutter從本質(zhì)上來講還是一個(gè)UI框架,它解決的是一套代碼在多端渲染的問題。在渲染管線的設(shè)計(jì)上更加精簡(jiǎn),加上自建渲染引擎,相比ReactNative、Weex以及WebView等方案,具有更好的性能體驗(yàn)。本文將從架構(gòu)和源碼的角度詳細(xì)分析Flutter渲染機(jī)制的設(shè)計(jì)與實(shí)現(xiàn)。較長(zhǎng),同學(xué)們可收藏后再看。

寫在前面

跨平臺(tái)技術(shù)由于其一碼多端的生產(chǎn)力提升而表現(xiàn)出巨大的生命力,從早期的Hybrid App到ReactNative/Weex、小程序/快應(yīng)用,再到現(xiàn)在的Flutter,跨平臺(tái)技術(shù)一直在解決效率問題的基礎(chǔ)上最大化的解決性能和體驗(yàn)問題。這也引出了任何跨平臺(tái)技術(shù)都會(huì)面臨的核心問題:

  • 效率:解決在多應(yīng)用、多平臺(tái)、多容器上開發(fā)效率的問題,一碼多端,業(yè)務(wù)快跑。
  • 性能:解決的是業(yè)務(wù)的性能和體驗(yàn)問題。

效率作為跨平臺(tái)技術(shù)的基本功能,大家都能做到。問題是誰能把性能和體驗(yàn)做得更好,在渲染技術(shù)這塊一共有三種方案:

  • WebView渲染:依賴WebView進(jìn)行渲染,在功能和性能上有妥協(xié),例如PhoneGap、Cordova、小程序(有的小程序底層也采用了ReactNative等渲染方案)等。
  • 原生渲染:上層擁抱W3C,通過中間層把前端框架翻譯為原生控件,例如ReactNative+React、Weex+Vue的組合,這種方案多了一層轉(zhuǎn)譯層,性能上有損耗。隨著原生系統(tǒng)的升級(jí),在兼容性上也會(huì)有問題。
  • 自建渲染:自建渲染框架,底層使用Skia等圖形庫(kù)進(jìn)行渲染,例如Flutter、Unity。

Flutter由于其自建渲染引擎,貼近原生的實(shí)現(xiàn)方式,獲得了優(yōu)秀的渲染性能。

Flutter擁有自己的開發(fā)工具,開發(fā)語言、虛擬機(jī),編譯機(jī)制,線程模型和渲染管線,和Android相比,它也可以看做一個(gè)小型的OS了。

第一次接觸Flutter,可以看看Flutter的創(chuàng)始人Eric之前的訪談《What is Flutter?》,Eric之前致力于Chromium渲染管線的設(shè)計(jì)與開發(fā),因此Flutter的渲染與Chromium有一定的相似之處,后面我們會(huì)做下類比。

后面我們會(huì)從架構(gòu)和源碼的角度分析Flutter渲染機(jī)制的設(shè)計(jì)與實(shí)現(xiàn),在此之前也可以先看看Flutter官方對(duì)于渲染機(jī)制的分享《How Flutter renders Widgets》。視頻+圖文的方式會(huì)更加直觀,可以有一個(gè)大體的理解。

架構(gòu)分析

架構(gòu)設(shè)計(jì)

從結(jié)構(gòu)上看,Flutter渲染由UI Thread與GPU Thread相互配合完成。

1)UI Thread

對(duì)應(yīng)圖中1-5,執(zhí)行Dart VM中的Dart代碼(包含應(yīng)用程序和Flutter框架代碼),主要負(fù)責(zé)Widget Tree、Element Tree、RenderObject Tree的構(gòu)建,布局、以及繪制生成繪制指令,生成Layer Tree(保存繪制指令)等工作。

2)GPU Thread

對(duì)應(yīng)圖中6-7,執(zhí)行Flutter引擎中圖形相關(guān)代碼(Skia),這個(gè)線程通過與GPU通信,獲取Layer Tree并執(zhí)行柵格化以及合成上屏等操作,將Layer Tree顯示在屏幕上。

注:圖層樹(Layer Tree)是Flutter組織繪制指令的方式,類似于Android Rendering里的View DisplayList,都是組織繪制指令的一種方式。

UI Thread與GPU Thread屬于生產(chǎn)者和消費(fèi)者的角色。

流程設(shè)計(jì)

我們知道Android上的渲染都是在VSync信號(hào)驅(qū)動(dòng)下進(jìn)行的,Flutter在Android上的渲染也不例外,它會(huì)向Android系統(tǒng)注冊(cè)并等待VSync信號(hào),等到VSync信號(hào)到來以后,調(diào)用沿著C++ Engine->Java Engine,到達(dá)Dart Framework,開始執(zhí)行Dart代碼,經(jīng)歷Layout、Paint等過程,生成一棵Layer Tree,將繪制指令保存在Layer中,接著進(jìn)行柵格化和合成上屏。

具體說來:

1)向Android系統(tǒng)注冊(cè)并等待VSync信號(hào)

Flutter引擎啟動(dòng)時(shí),會(huì)向Android系統(tǒng)的Choreographer(管理VSync信號(hào)的類)注冊(cè)并接收VSync信號(hào)的回調(diào)。

2)接收到VSync信號(hào),通過C++ Engine向Dart Framework發(fā)起渲染調(diào)用

當(dāng)VSync信號(hào)產(chǎn)生以后,Flutter注冊(cè)的回調(diào)被調(diào)用,VsyncWaiter::fireCallback() 方法被調(diào)用,接著會(huì)執(zhí)行 Animator::BeiginFrame(),最終調(diào)用到 Window::BeginFrame() 方法,WIndow實(shí)例是連接底層Engine和Dart Framework的重要橋梁,基本上與平臺(tái)相關(guān)的操作都會(huì)通過Window實(shí)例來連接,例如input事件、渲染、無障礙等。

3)Dart Framework開始在UI線程執(zhí)行渲染邏輯,生成Layer Tree,并將柵格化任務(wù)post到GPU線程執(zhí)行

Window::BeiginFrame() 接著調(diào)用,執(zhí)行到 RenderBinding::drawFrame() 方法,這個(gè)方法會(huì)去驅(qū)動(dòng)UI界面上的dirty節(jié)點(diǎn)(需要重繪的節(jié)點(diǎn))進(jìn)行重新布局和繪制,如果渲染過程中遇到圖片,會(huì)先放到Worker Thead去加載和解碼,然后再放到IO Thread生成圖片紋理,由于IO Thread和GPI Thread共享EGL Context,因此IO Thread生成的圖片紋理可以被GPU Thread直接訪問。

4)GPU線程接收到Layer Tree,進(jìn)行柵格化以及合成上屏的工作

Dart Framework繪制完成以后會(huì)生成繪制指令保存在Layer Tree中,通過 Animator::RenderFrame() 把Layer Tree提交給GPU Thread,GPU Thread接著執(zhí)行柵格化和上屏顯示。之后通過 Animator::RequestFrame() 請(qǐng)求接收系統(tǒng)的下一次VSync信號(hào),如此循環(huán)往復(fù),驅(qū)動(dòng)UI界面不斷更新。

逐個(gè)調(diào)用流程比較長(zhǎng),但是核心點(diǎn)沒多少,不用糾結(jié)調(diào)用鏈,抓住關(guān)鍵實(shí)現(xiàn)即可,我們把里面涉及到的一些主要類用顏色分了個(gè)類,對(duì)著這個(gè)類圖,基本可以摸清Flutter的脈絡(luò)。


綠色:Widget 黃色:Element 紅色:RenderObject

以上便是Flutter渲染的整體流程,會(huì)有多個(gè)線程配合,多個(gè)模塊參與,拋開冗長(zhǎng)的調(diào)用鏈,我們針對(duì)每一步來具體分析。我們?cè)诜治鼋Y(jié)構(gòu)時(shí)把Flutter的渲染流程分為了7大步,Flutter的timeline也可以清晰地看到這些流程,如下所示:

UI Thread

1)Animate

由 handleBeiginFrame() 方法的transientCallbacks觸發(fā),如果沒有動(dòng)畫,則該callback為空;如果有動(dòng)畫,則會(huì)回調(diào) Ticker.tick() 觸發(fā)動(dòng)畫Widget更新下一幀的值。

2)Build

由 BuildOwner.buildScope() 觸發(fā),主要用來構(gòu)建或者更新三棵樹,Widget Tree、Element Tree和RenderObject Tree。

3)Layout

由 PipelineOwner.flushLayout() 觸發(fā),它會(huì)調(diào)用 RenderView.performLayout(),遍歷整棵Render Tree,調(diào)用每個(gè)節(jié)點(diǎn)的 layout(),根據(jù)build過程記錄的信息,更新dirty區(qū)域RenderObject的排版數(shù)據(jù),使得每個(gè)RenderObject最終都能有正確的大小(size)和位置(position,保存在parentData中)。

4)Compositing Bits

由 PipelineOwner.flushCompositingBits() 觸發(fā),更新具有dirty合成位置的渲染對(duì)象,此階段每個(gè)渲染對(duì)象都會(huì)了解其子項(xiàng)是否需要合成,在繪制階段使用此信息選擇如何實(shí)現(xiàn)裁剪等視覺效果。

5)Paint

由 PipeOwner.flushPaint() 觸發(fā),它會(huì)調(diào)用 RenderView.paint()。最終觸發(fā)各個(gè)節(jié)點(diǎn)的 paint(),最終生成一棵Layer Tree,并把繪制指令保存在Layer中。

6)Submit(Compositing)

由 renderView.compositeFrame() 方法觸發(fā),這個(gè)地方官方的說法叫Compositing,不過我覺得叫Compositing有歧義,因?yàn)樗⒉皇窃诤铣?#xff0c;而是把Layer Tree提交給GPU Thread,因而我覺得叫Submit更合適。

GPU Thread

7)Compositing

由 Render.compositeFrame() 觸發(fā),它通過Layer Tree構(gòu)建一個(gè)Scene,傳給Window進(jìn)行最終的光柵化。

GPU Thread通過Skia向GPU繪制一幀數(shù)據(jù),GPU將幀信息保存在FrameBuffer里,然后根據(jù)VSync信號(hào)周期性的從FrameBuffer取出幀數(shù)據(jù)交給顯示器,從而顯示出最終的界面。

Rendering Pipeline

Flutter引擎啟動(dòng)時(shí),向Android系統(tǒng)的Choreographer注冊(cè)并接收VSync信號(hào),GPU硬件產(chǎn)生VSync信號(hào)以后,系統(tǒng)便會(huì)觸發(fā)回調(diào),并驅(qū)動(dòng)UI線程進(jìn)行渲染工作。

1 Animate

觸發(fā)方法:由 handleBeiginFrame() 方法的transientCallbacks觸發(fā)

Animate在 handleBeiginFrame() 方法里由transientCallbacks觸發(fā),如果沒有動(dòng)畫,則該callback為空;如果有動(dòng)畫,則會(huì)回調(diào) Ticker._tick() 觸發(fā)動(dòng)畫Widget更新下一幀的值。

void handleBeginFrame(Duration rawTimeStamp) {...try {// TRANSIENT FRAME CALLBACKSTimeline.startSync('Animate', arguments: timelineWhitelistArguments);_schedulerPhase = SchedulerPhase.transientCallbacks;final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;_transientCallbacks = <int, _FrameCallbackEntry>{};callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {if (!_removedIds.contains(id))_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);});...} finally {...}}

handleBeiginFrame() 處理完成以后,接著調(diào)用 handleDrawFrame(),handleDrawFrame() 會(huì)觸發(fā)以下回調(diào):

  • postFrameCallbacks用來通知監(jiān)聽者繪制已經(jīng)完成。
  • pesistentCallbacks用來觸發(fā)渲染。

這兩個(gè)回調(diào)都是SchedulerBinding內(nèi)部的回調(diào)隊(duì)列,如下所示:

  • _transientCallbacks:用于存放一些臨時(shí)回調(diào),目前是在 Ticker.scheduleTick() 中注冊(cè),用來驅(qū)動(dòng)動(dòng)畫。
  • _persistentCallbacks:用來存放一些持久回調(diào),不能在此回調(diào)中再請(qǐng)求新的繪制幀,持久回調(diào)一經(jīng)注冊(cè)就不嫩嫩移除, RenderBinding.initInstaces().addPersitentFrameCallback() 添加了一個(gè)持久回調(diào),用來觸發(fā) drawFrame()。
  • _postFrameCallbacks:在Frame結(jié)束時(shí)會(huì)被調(diào)用一次,調(diào)用后會(huì)被移除,它主要是用來通知監(jiān)聽者這個(gè)Frame已經(jīng)完成。

接著會(huì)調(diào)用 WidgetBinder.drawFrame() 方法,它會(huì)先調(diào)用會(huì)先調(diào)用 BuildOwner.buildScope() 觸發(fā)樹的更新,然后才進(jìn)行繪制。

@override void drawFrame() {...try {if (renderViewElement != null)buildOwner.buildScope(renderViewElement);super.drawFrame();buildOwner.finalizeTree();} finally {assert(() {debugBuildingDirtyElements = false;return true;}());}... }

接著調(diào)用 RenderingBinding.drawFrame() 觸發(fā)layout、paingt等流程。

void drawFrame() {assert(renderView != null);pipelineOwner.flushLayout();pipelineOwner.flushCompositingBits();pipelineOwner.flushPaint();if (sendFramesToEngine) {renderView.compositeFrame(); // this sends the bits to the GPUpipelineOwner.flushSemantics(); // this also sends the semantics to the OS._firstFrameSent = true;} }

以上便是核心流程代碼,我們接著來Build的實(shí)現(xiàn)。

2 Build

觸發(fā)方法:由 BuildOwner.buildScope() 觸發(fā)。

我們上面說到,handleDrawFrame() 會(huì)觸發(fā)樹的更新,事實(shí)上 BuildOwner.buildScope() 會(huì)有兩種調(diào)用時(shí)機(jī):

  • 樹構(gòu)建(應(yīng)用啟動(dòng)時(shí)):我們上面提到的 runApp() 方法調(diào)用的 scheduleAttachRootWidget() 方法,它會(huì)構(gòu)建Widgets Tree、Element Tree與RenderObject Tree三棵樹。
  • 樹更新(幀繪制與更新時(shí)):這里不會(huì)重新構(gòu)建三棵樹,而是只會(huì)更新dirty區(qū)域的Element。

也即是說樹的構(gòu)建和更新都是由 BuildOwner.buildScope() 方法來完成的。它們的差別在于樹構(gòu)建的時(shí)候傳入了一個(gè) element.mount(null, null) 回調(diào)。在 buildScope() 過程中會(huì)觸發(fā)這個(gè)回調(diào)。

這個(gè)回調(diào)會(huì)構(gòu)建三棵樹,為什么會(huì)有三棵樹呢,因?yàn)閃idget只是對(duì)UI元素的一個(gè)抽象描述,我們需要先將其inflate成Element,然后生成對(duì)應(yīng)的RenderObject來驅(qū)動(dòng)渲染,如下所示:

  • Widget Tree:為Element描述需要的配置,調(diào)用createElement方法創(chuàng)建Element,決定Element是否需要更新。Flutter通過查分算法比對(duì)Widget樹前后的變化,來決定Element的State是否改變。
  • Element Tree:表示W(wǎng)idget Tree特定位置的一個(gè)實(shí)例,調(diào)用createRenderObject創(chuàng)建RenderObject,同時(shí)持有Widget和RenderObject,負(fù)責(zé)管理Widget的配置和RenderObjec的渲染。Element的狀態(tài)由Flutter維護(hù),開發(fā)人員只需要維護(hù)Widget即可。
  • RenderObject Tree:RenderObject繪制,測(cè)量和繪制節(jié)點(diǎn),布局子節(jié)點(diǎn),處理輸入事件。

3 Layout

觸發(fā)方法:由 PipelineOwner.flushLayout() 觸發(fā)。

  • 相關(guān)文檔:Understanding constraints
  • 相關(guān)源碼:PipelineOwner.flushLayout()

Layout是基于單向數(shù)據(jù)流來實(shí)現(xiàn)的,父節(jié)點(diǎn)向子節(jié)點(diǎn)傳遞約束(Constraints),子節(jié)點(diǎn)向父節(jié)點(diǎn)傳遞大小(Size,保存在父節(jié)點(diǎn)的parentData變量中)。先深度遍歷RenderObject Tree,然后再遞歸遍歷約束。單向數(shù)據(jù)流讓布局流程變得更簡(jiǎn)單,性能也更好。

對(duì)于RenderObject而言,它只是提供了一套基礎(chǔ)的布局協(xié)議,沒有定義子節(jié)點(diǎn)模型、坐標(biāo)系統(tǒng)和具體的布局協(xié)議。它的子類RenderBox則提供了一套笛卡爾坐標(biāo)體系(和Android&iOS一樣),大部分RenderObject類都是直接繼承RenderBox來實(shí)現(xiàn)的。RenderBox有幾個(gè)不同的子類實(shí)現(xiàn),它們各自對(duì)應(yīng)了不同的布局算法。

  • RenderFlex:彈性布局,這是一種很常見的布局方式,它對(duì)應(yīng)的是Widget組件Flex、Row和Column。關(guān)于這一塊的布局算法代碼注釋里有描述,也可以直接看這篇文章的解釋。
  • RenderStack:棧布局。

我們?cè)賮砹牧腖ayout流程中涉及的兩個(gè)概念邊界約束(Constraints)和重新布局邊界(RelayoutBoundary)。

邊界約束(Constraints):邊界約束是父節(jié)點(diǎn)用來限制子節(jié)點(diǎn)的大小的一種方式,例如BoxConstraints、SliverConstraints等。

RenderBox提供一套BoxConstraints,如圖所示,它會(huì)提供以下限制:

  • minWidth
  • maxWidth
  • minHeight
  • maxHeight

利用這種簡(jiǎn)單的盒模型約束,我們可以非常靈活的實(shí)現(xiàn)很多常見的布局,例如完全和父節(jié)點(diǎn)一樣的大小,垂直布局(寬度和父節(jié)點(diǎn)一樣大)、水平布局(高度和父容器一樣大)。

通過Constraints和子節(jié)點(diǎn)自己配置的大小信息,就可以最終算出子節(jié)點(diǎn)的大小,接下來就需要計(jì)算子節(jié)點(diǎn)的位置。子節(jié)點(diǎn)的位置是由父節(jié)點(diǎn)來決定的。

重新布局邊界(RelayoutBoundary):為一個(gè)子節(jié)點(diǎn)設(shè)置重新布局邊界,這樣當(dāng)它的大小發(fā)生變化時(shí),不會(huì)導(dǎo)致父節(jié)點(diǎn)重新布局,這是個(gè)標(biāo)志位,在標(biāo)記dirty的markNeedsLayout()方法中會(huì)檢查這個(gè)標(biāo)記位來決定是否重新進(jìn)行布局。

重新布局邊界這種機(jī)制提升了布局排版的性能。

通過Layout,我們了解了所有節(jié)點(diǎn)的位置和大小,接下來就會(huì)去繪制它們。

4 Compositing Bits

觸發(fā)方法:由 PipelineOwner.flushCompositingBits() 觸發(fā)。

在Layout之后,在Paint之前會(huì)先執(zhí)行Compositing Bits,它會(huì)檢查RenderObject是否需要重繪,然后更新RenderObject Tree各個(gè)節(jié)點(diǎn)的needCompositing標(biāo)志。如果為true,則需要重繪。

5 Paint

觸發(fā)方法:由 PipeOwner.flushPaint() 觸發(fā)。

相關(guān)源碼:

  • Dart層調(diào)用入口:painting.dart
  • C++層實(shí)現(xiàn):canvas.cc

我們知道現(xiàn)代的UI系統(tǒng)都會(huì)進(jìn)行界面的圖層劃分,這樣可以進(jìn)行圖層復(fù)用,減少繪制量,提升繪制性能,因此Paint(繪制)的核心問題還是解決繪制命令應(yīng)該放到哪個(gè)圖層的問題。

Paint的過程也是單向數(shù)據(jù)流,先向下深度遍歷RenderObject Tree,再遞歸遍歷子節(jié)點(diǎn),遍歷的過程中會(huì)決定每個(gè)子節(jié)點(diǎn)的繪制命令應(yīng)該放在那一層,最終生成Layer Tree。

和Layout一樣,為了提到繪制性能,繪制階段也引入了重新繪制邊界。

重新繪制邊界(RepaintBoundary):為一個(gè)子節(jié)點(diǎn)設(shè)置重新繪制邊界,這樣當(dāng)它需要重新繪制時(shí),不會(huì)導(dǎo)致父節(jié)點(diǎn)重新繪制,這是個(gè)標(biāo)志位,在標(biāo)記dirty的markNeedsPaint()方法中會(huì)檢查這個(gè)標(biāo)記位來決定是否重新進(jìn)行重繪。

事實(shí)上這種重繪邊界的機(jī)制相對(duì)于把圖層分層這個(gè)功能開放給了開發(fā)者,開發(fā)者可以自己決定自己的頁(yè)面那一塊在重繪時(shí)不參與重繪(例如滾動(dòng)容器),以提升整體頁(yè)面的性能。重新繪制邊界會(huì)改變最終的圖層樹(Layer Tree)結(jié)構(gòu)。

當(dāng)然這些重繪邊界并不都需要我們手動(dòng)放置,大部分Widget組件會(huì)自動(dòng)放置重繪邊界(自動(dòng)分層)。

設(shè)置了RepaintBoundary的就會(huì)額外生成一個(gè)圖層,其所有的子節(jié)點(diǎn)都會(huì)被繪制在這個(gè)新的圖層上,Flutter中使用圖層來描述一個(gè)層次上(一個(gè)繪制指令緩沖區(qū))的所有RenderObject,根節(jié)點(diǎn)的RenderView會(huì)創(chuàng)建Root Layer,并且包含若干個(gè)子Layer,每個(gè)Layer又包含多個(gè)RenderObject,這些Layer便形成了一個(gè)Layer Tree。每個(gè)RenderObject在繪制時(shí),會(huì)產(chǎn)生相關(guān)的繪制指令和繪制參數(shù),并保存在對(duì)應(yīng)的Layer上。

相關(guān)Layer都繼承Layer類,如下所示:

  • ClipRectLayer:矩形裁剪層,可以指定裁剪和矩形行為參數(shù)。共有4種裁剪行為,none、hardEdge、antiAlias、antiAliashWithSaveLayer。
  • ClipRRectLayer:圓角矩形裁剪層,行為同上。
  • ClipPathLayer:路徑裁剪層,可以指定路徑和行為裁剪參數(shù),行為同上。
  • OpacityLayer:透明層,可以指定透明度和偏移(畫布坐標(biāo)系原點(diǎn)到調(diào)用者坐標(biāo)系原點(diǎn)的偏移)參數(shù)。
  • ShaderMaskLayer:著色層,可以指定著色器矩陣和混合模式參數(shù)。
  • ColorFilterLayer:顏色過濾層,可以指定顏色和混合模式參數(shù)。
  • TransformLayer:變換圖層,可以指定變換矩陣參數(shù)。
  • BackdropFilterLayer:背景過濾層,可以指定背景圖參數(shù)。
  • PhysicalShapeLayer:物理性狀層,可以指定顏色等八個(gè)參數(shù)。

具體可以參考文章上方的Flutter類圖。

聊完了繪制的基本概念,我們?cè)賮砜纯蠢L制的具體流程,上面提到渲染第一幀的時(shí)候,會(huì)從根節(jié)點(diǎn)RenderView開始,逐個(gè)遍歷所有子節(jié)點(diǎn)進(jìn)行操作。如下所示:

1)創(chuàng)建Canvas對(duì)象

Canvas對(duì)象通過PaintCotext獲取,它內(nèi)部會(huì)創(chuàng)建一個(gè)PictureLayer,并通過ui.PictureRecorder調(diào)用到C++層創(chuàng)建一個(gè)Skia的SkPictureRecorder的實(shí)例,并通過SkPictureRecorder創(chuàng)建SkCanvas,而后將SkCanvas返回給Dart Framework使用。SkPictureRecorder可以用來記錄生成的繪制命令。

2)通過Canvas執(zhí)行繪制

繪制命令會(huì)被SkPictureRecorder記錄下來。

3)通過Canvas結(jié)束繪制,準(zhǔn)備進(jìn)行柵格化

繪制結(jié)束后,會(huì)調(diào)用 Canvas.stopRecordingIfNeeded() 方法,它會(huì)接著去調(diào)用C++層的SkPictureRecorder::endRecording()方法生成一個(gè)Picture對(duì)象并保存在PictureLayer中,Picture對(duì)象包含了所有的繪制指令。所有的Layer繪制完成,形成Layer Tree。

繪制完成以后,接著就可以向GPU Thread提交Layer Tree了。

6 Submit(Compositing)

觸發(fā)方法:由 renderView.compositeFrame() 方法觸發(fā)。

  • Dart層調(diào)用入口:compositing.dart widow.dart
  • C++層實(shí)現(xiàn):scene.cc scene_builder.cc

注:這個(gè)地方官方的說法叫Compositing,不過我覺得叫Compositing有歧義,因?yàn)樗⒉皇窃诤铣?#xff0c;而是把Layer Tree提交給GPU Thread,因而我覺得叫Submit更合適。

void compositeFrame() {Timeline.startSync('Compositing', arguments: timelineArgumentsIndicatingLandmarkEvent);try {final ui.SceneBuilder builder = ui.SceneBuilder();final ui.Scene scene = layer.buildScene(builder);if (automaticSystemUiAdjustment)_updateSystemChrome();_window.render(scene);scene.dispose();assert(() {if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);return true;}());} finally {Timeline.finishSync();}}
  • 創(chuàng)建SceneBuilder對(duì)象,并通過 SceneBuilder.addPicture() 將上文中生成的Picture添加到SceneBuilder對(duì)象對(duì)象中。
  • 通過 SceneBuilder.build() 方法生成Scene對(duì)象,接著會(huì)通過window.render(scene)將包含繪制指令的Layer Tree提交給CPU線程進(jìn)行光柵化和合成。

在這個(gè)過程中Dart Framework層的Layer會(huì)被轉(zhuǎn)換為C++層使用的flow::layer,Flow模塊是一個(gè)基于Skia的簡(jiǎn)單合成器,運(yùn)行在GPU線程,并向Skia上傳指令信息。Flutter Engine使用flow緩存Paint階段生成的繪制指令和像素信息。我們?cè)赑aint階段的Layer,它們都與Flow模塊里的Layer一一對(duì)應(yīng)。

Graphics Pipeline

7 Raster&Compositing

有了包含渲染指令的Layer Tree以后就可以進(jìn)行光柵化和合成了。

光柵化是把繪制指令轉(zhuǎn)換成對(duì)應(yīng)的像素?cái)?shù)據(jù),合成是把各圖層?xùn)鸥窕蟮臄?shù)據(jù)進(jìn)行相關(guān)的疊加和特性處理。這個(gè)流程稱為Graphics Pipeline。

相關(guān)代碼:rasterizer.cc

Flutter采用的是同步光柵化。什么是同步光柵化?

同步光柵化:

光柵化和合成在一個(gè)線程,或者通過線程同步等方式來保證光柵化和合成的的順序。

直接光柵化:直接執(zhí)行可見圖層的DisplayList中可見區(qū)域的繪制指令進(jìn)行光柵化,在目標(biāo)Surface的像素緩沖區(qū)上生成像素的顏色值。
間接光柵化:為指定圖層分配額外的像素緩沖區(qū)(例如Android提供View.setLayerType允許應(yīng)用為指定View提供像素緩沖區(qū),Flutter提供了Relayout Boundary機(jī)制來為特定圖層分配額外緩沖區(qū)),該圖層光柵化的過程中會(huì)先寫入自身的像素緩沖區(qū),渲染引擎再將這些圖層的像素緩沖區(qū)通過合成輸出到目標(biāo)Surface的像素緩沖區(qū)。

異步分塊光柵化:

圖層會(huì)按照一定的規(guī)則粉塵同樣大小的圖塊,光柵化以圖塊為單位進(jìn)行,每個(gè)光柵化任務(wù)執(zhí)行圖塊區(qū)域內(nèi)的指令,將執(zhí)行結(jié)果寫入分塊的像素緩沖區(qū),光柵化和合成不在一個(gè)線程內(nèi)執(zhí)行,并且不是同步的。如果合成過程中,某個(gè)分塊沒有完成光柵化,那么它會(huì)保留空白或者繪制一個(gè)棋盤格圖形。

Android和Flutter采用同步光柵化策略,以直接光柵化為主,光柵化和合成同步執(zhí)行,在合成的過程中完成光柵化。而Chromium采用異步分塊光柵化測(cè)量,圖層會(huì)進(jìn)行分塊,光柵化和合成異步執(zhí)行。

從文章上方的序列圖可以看到,光柵化的入口是 Rasterizer::DoDraw() 方法。它會(huì)接著調(diào)用 ScopedFrame::Raster() 方法,這個(gè)方法就是光柵化的核心實(shí)現(xiàn),它主要完成以下工作:

  • LayerTree::Preroll():處理繪制前的一些準(zhǔn)備工作。
  • LayerTree::Paint():嵌套調(diào)用不通Layer的繪制方法。
  • SkCanvas::Flush():將數(shù)據(jù)flush給GPU。
  • AndroidContextGL::SwapBuffers():交換幀緩存給顯示器顯示。

到這里我們Flutter整體的渲染實(shí)現(xiàn)我們就分析完了。

Android、Chromium與Flutter

Android、Chromium、Flutter都作為Google家的明星級(jí)項(xiàng)目,它們?cè)阡秩緳C(jī)制的設(shè)計(jì)上既有相似又有不同,借著這個(gè)機(jī)會(huì)我們對(duì)它們做個(gè)比較。

現(xiàn)代渲染流水線的基本設(shè)計(jì):

我們?cè)俜謩e來看看Android、Chromium和Flutter是怎么實(shí)現(xiàn)的。

Android渲染流水線:

Chromium渲染流水線:

Flutter渲染流水線:

相互比較:

寫在最后

最后的最后,談一談我對(duì)跨平臺(tái)生態(tài)的理解。

跨平臺(tái)容器生態(tài)至少可以分為三個(gè)方面:

前端框架生態(tài)

前端框架生態(tài)直接面向的是業(yè)務(wù),它應(yīng)該具備兩個(gè)特點(diǎn):

  • 擁抱W3C生態(tài)
  • 相對(duì)穩(wěn)定性

它應(yīng)該是擁抱W3C生態(tài)的。W3C生態(tài)是一個(gè)繁榮且充滿活力的生態(tài),它會(huì)發(fā)展的更久更遠(yuǎn)。試圖拋棄W3C生態(tài),自建子集的做法很難走的長(zhǎng)遠(yuǎn)。這從微信小程序、Flutter都推出for web系列就能看出端倪。

它應(yīng)該是相對(duì)穩(wěn)定的。不能說我們每換一套容器,前端的業(yè)務(wù)就需要重新寫一遍,例如我們之前做H5容器,后來做小程序容器,因?yàn)镈SL不通,前端要花大力氣將業(yè)務(wù)重寫。雖然小程序是一碼多端,但是我認(rèn)為這并沒有解決效率問題,主要存在兩個(gè)問題:

  • 前端的學(xué)習(xí)成本增加,小程序的DSL還算簡(jiǎn)單,Flutter的Widget體系學(xué)習(xí)起來就需要花上一點(diǎn)時(shí)間,這些對(duì)于團(tuán)隊(duì)來說都是成本。
  • 業(yè)務(wù)代碼重寫,大量邏輯需要梳理,而且老業(yè)務(wù)并不一定都適合遷移到新容器上,比如小程序本來就是個(gè)很輕量的解決方案,但是我們?cè)谏厦娑逊e了很多功能,造成了嚴(yán)重的體驗(yàn)問題。

在這種情況下,業(yè)務(wù)很難實(shí)現(xiàn)快速奔跑。所以說不管底層容器怎么變,前端的框架一定是相對(duì)穩(wěn)定的。而這種穩(wěn)定性就有賴于容器統(tǒng)一層。

容器統(tǒng)一層

容器統(tǒng)一層是在前端框架和容器層之間的一個(gè)層級(jí)。它定義了容器提供的基本能力,這些能力就像協(xié)議一樣,是相對(duì)穩(wěn)定的。

協(xié)議是非常重要的,就像OpenGL協(xié)議一樣,有了OpenGL協(xié)議,不管底層的渲染方案如何實(shí)現(xiàn),上層的調(diào)用是不用變的。對(duì)于我們的業(yè)務(wù)也是一樣,圍繞著容器統(tǒng)一層,我們需要沉淀通用的解決方案。

  • 統(tǒng)一API解決方案
  • 統(tǒng)一性能解決方案
  • 統(tǒng)一組件解決方案
  • 統(tǒng)一配套設(shè)施解決方案
  • 等等

這些東西不能說每搞一套容器,我們都要大刀闊斧重來一遍,這種做法是有問題的。已經(jīng)做過的東西,遇到新的技術(shù)就推倒重來,只能說明以前定義的方案考慮不周全,沒有考慮沉淀統(tǒng)一和擴(kuò)展的情況。

如果我們自顧自的一遍遍做著功能重復(fù)的技術(shù)方案,業(yè)務(wù)能等著我們嗎。

容器層

容器層的迭代核心是為了在解決效率問題的基礎(chǔ)上最大化的解決性能和體驗(yàn)問題。

早期的ReactNative模式解決了效率了問題,但是多了一個(gè)通信層(ReactNative是依靠將虛擬DOM的信息傳遞給原生,然后原生根據(jù)這些布局信息構(gòu)建對(duì)應(yīng)的原生控件樹來實(shí)現(xiàn)的原生渲染)存在性能問題,而且這種轉(zhuǎn)譯的方式需要適配系統(tǒng)版本,帶來更多的兼容性問題。

微信后續(xù)又推出了小程序方案,在我看來,小程序方案不像是一個(gè)技術(shù)方案,它更像是一個(gè)商業(yè)解決方案,解決了平臺(tái)大流量規(guī)范管理和分發(fā)的問題,給業(yè)務(wù)方提供通用的技術(shù)解決方案,當(dāng)然小程序底層的渲染方案也是多種多樣的。

后起之秀Flutter解決的痛點(diǎn)是性能能力,它自建了一套GUI系統(tǒng),底層直接調(diào)用Skia圖形庫(kù)進(jìn)行渲染(與Android的機(jī)制一樣),進(jìn)而實(shí)現(xiàn)了原生渲染。但是它基于開發(fā)效率、性能以及自身生態(tài)等因素的考慮最終選擇了Dart,這種做法無疑是直接拋棄了繁榮的前端生態(tài),就跨平臺(tái)容器的發(fā)展歷史來看,在解決效率與性能的基礎(chǔ)上,最大化的擁抱W3C生態(tài),可能是未來最好的方向。Flutter目前也推出了Flutter for Web,從它的思路來看,是先打通Android與iOS,再逐步向Web滲透,我們期待它的表現(xiàn)。

容器技術(shù)是動(dòng)態(tài)向前發(fā)展的,我們今年搞Flutter,明年可能還會(huì)搞其他技術(shù)方案。在方案變遷的過程中,我們需要保證業(yè)務(wù)快速平滑的過度,而不是每次大刀闊斧的再來一遍。

隨著手機(jī)性能的提升,WebView的性能也越來越好,Flutter又為解決性能問題提供了新的思路,一個(gè)基礎(chǔ)設(shè)施完善,體驗(yàn)至上,一碼多端的跨平臺(tái)容器生態(tài)值得期待。

最后的最后

歡迎加入本地生活終端技術(shù)部!

本地生活終端技術(shù)部隸屬于阿里本地生活用戶技術(shù)部,從事客戶端技術(shù)研發(fā)工作,主要負(fù)責(zé)本地生活餓了么App 和 口碑App 的客戶端架構(gòu)、基礎(chǔ)中間件、跨平臺(tái)技術(shù)解決方案,以及賬號(hào)、首頁(yè)、全局購(gòu)物車、收銀臺(tái)、訂單列表、紅包卡券、直播、短視頻等平臺(tái)化核心業(yè)務(wù)鏈路。目前團(tuán)隊(duì)規(guī)模50+人,我們依托阿里強(qiáng)大的終端技術(shù)底盤,以及本地生活的業(yè)務(wù)土壤,致力于打造最優(yōu)秀的O2O技術(shù)團(tuán)隊(duì)。

招聘本地生活-客戶端開發(fā)專家/高級(jí)技術(shù)專家-杭州/上海/北京,歡迎您的加盟!簡(jiǎn)歷發(fā)送至 wushi@alibaba-inc.com

附錄

相關(guān)平臺(tái)
[1]Flutter pub.dev
(https://pub.dev/flutter/packages)
相關(guān)文檔
[1]Flutter 官方文檔
(https://flutter.dev/docs/get-started/install/macos)
[2]Flutter for Android developers
(https://flutter.dev/docs/get-started/flutter-for/android-devs)
[3]Flutter Widget Doc
(https://flutter.dev/docs/reference/widgets)
[4]Flutter API Doc(https://api.flutter.dev/)
[5]Dart Doc
(https://dart.dev/guides/language)
相關(guān)源碼
[1]Dart Framework
(https://github.com/flutter/flutter/tree/master/packages)
[2]Flutter Engine
(https://github.com/flutter/engine)
相關(guān)資源
[1]Flutter Render Pipeline
(https://www.youtube.com/watch?v=UUfXWzp0-DU)
[2]How Flutter renders Widgets
(https://www.youtube.com/watch?v=996ZgFRENMs)
[3]深入了解Flutter的高性能圖形渲染 video
(https://www.bilibili.com/video/av48772383)
[4]深入了解Flutter的高性能圖形渲染 ppt
(https://files.flutter-io.cn/events/gdd2018/Deep_Dive_into_Flutter_Graphics_Performance.pdf)

原文鏈接:https://developer.aliyun.com/article/770384?

版權(quán)聲明:本文內(nèi)容由阿里云實(shí)名注冊(cè)用戶自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,阿里云開發(fā)者社區(qū)不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。具體規(guī)則請(qǐng)查看《阿里云開發(fā)者社區(qū)用戶服務(wù)協(xié)議》和《阿里云開發(fā)者社區(qū)知識(shí)產(chǎn)權(quán)保護(hù)指引》。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,填寫侵權(quán)投訴表單進(jìn)行舉報(bào),一經(jīng)查實(shí),本社區(qū)將立刻刪除涉嫌侵權(quán)內(nèi)容。

總結(jié)

以上是生活随笔為你收集整理的从架构到源码:一文了解Flutter渲染机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。