Flutter 原理和繪圖

WX20210318-174108@2x.png

1架構理解

談到跨平臺一般繞不過webhybird、react native等框架,其中RN是目前比較主流的跨平臺解決方案。我們拿RNflutter做一下對比。

1.1跨平臺架構簡介

1.1.1 React Native:
Rf85f8ebd19aba1d9607033b500ef7f9f.jpeg

RN可以讓JS開發(fā)者使用JS代碼就可以寫出一個跨平臺的App,同時RN需要一個JS運行環(huán)境,在iOS中使用的是內置的javascriptcore,在android使用的是webkit.org官方開源的jsc.so,搭建UI只需要在虛擬DOM中,之后通過框架轉換對應到native的視圖上,需要一個和native通信的則使用Bridge進行異步通信。

1.1.2 flutter :
v2-6733941a13ba1b2cfe65a9c57fa112fb_b.png

flutter的架構由兩部分組成 分別是frameworkEngine:

1.1.2.1framework

framework是一個Dart庫 包括組件、手勢、動畫、有MaterialCupertino風格,是我們最常打交道的庫。開發(fā)中的接大部分需求framework給我們提供的庫都可以滿足。

1.1.2.2Engine

EngineC/C++編寫的庫,是flutter的核心庫,包含了Dart虛擬機、動畫和圖形、文字渲染、通信通道等,其中渲染引擎采用的是2D的圖形渲染庫Skia,虛擬機是Dart VM,另外還包含了Embedder中間層代碼。

1.2RN和flutter對比

簡單了解了RNFlutter的架構,我們就能很直觀的看到兩者之間差異,至于為什么同是跨平臺語言,Flutter性能優(yōu)于RN、近似Native,我們看一下下面這張圖:

WX20210315-173940.png
1.2.1flutter

構建一個flutter項目,我們通過dart語言開發(fā),使用flutter framework提供的庫便可以,之后交于Engine中的Skia引擎做圖像的繪制,最后生成的CPU或者GPU的指令,在設備上完成繪圖。整個過程和Native的流程一模一樣。

1.2.2 Android

從上圖可以看出flutter框架代碼完全取代了AndroidJava代碼,所以只要flutter框架dart代碼的效率可以媲美原生框架的Java代碼的時候,那么flutter的性能就可以媲美原生app。

1.2.3 iOS

iOS中和Skia起到相似作用的是Core Graphic / Core Animation,因為Skia是一個google開源的2D圖像庫,所以直接用Skia來代替Core Graphic / Core Animation 來做圖形的繪制即可,因為要把Skia集成到項目中去,所以同一個項目iOS打出來的包是要比Android要大一些。

1.2.4 RN

它首先要調用框架本身的JavaScript代碼,然后再調用Android(iOS)框架的Java(OC)代碼,然后調用skia(Core Graphic / Core Animation),這比原生的APP調用方式多出來一步,必然會產型性能上的損耗。

1.2.5 android 低端機

flutter開發(fā)的app在一些低端機型上的性能優(yōu)于android原生app。
因為在原生app中,skia圖形引擎是作為android操作系統(tǒng)的一部分,只要等操作系統(tǒng)升級,skia才會升級得到最新的優(yōu)化。
flutterskia是包含在flutter SDK中的,只要flutter SDK升級,skia也會隨著升級,目前flutter處于快速迭代期,skia上最新的優(yōu)化和改進會很快的應用到flutter項目中。android系統(tǒng)的升級相對慢,且對低端機的升級也不是太友好,所以skia的優(yōu)化在原生app上是不得到及時的體現。

1.3flutter和native

經過上面的分析,我們再看下面這幅圖基本上就可以清晰的看出flutter和native的對比和優(yōu)勢:

1408135781-5ff422679b370.png

2、圖像繪制

2.1基本概念

幀率:
GPU一秒內繪制圖像的次數(skia可以達到120幀)
刷新率:
屏幕顯示圖像的數量60幀/秒 (現在機型能達到90幀或者120幀),人眼可接受的最低是12幀。
兩者關系:
GPU提供數據 存放在Buffer中,顯示器從Buffer中獲取圖像顯示在屏幕,理想情況下,幀率和刷新率一直,實現畫面流暢顯示。
tearing:
通常情況下幀率是大于刷新率的,在這種情況下,第n張圖片在屏幕上加載完成,n+1就已經出來了,會出現撕裂tearing。

0ef9bf5a-b974-4ce3-8e0b-827abd9d99d2.png

Vsync:
為了解決tearing的情況,就需要引入Vsync垂直同步信號,在屏幕刷新兩幀之間會有一個VBlank,這個間隔就會產生一個Vsync信號,GPU/CPU在收到信號之后再去計算下一幀畫面,這樣就能夠避免tearing,就算沒有產生tearing也建議使用Vsync,普遍情況下幀率是要高于刷新率的,為了避免GPU/CPU過快的渲染結果,產生冗余的計算。

Jank:
tearing相對應的就是jank,這是由于在CPU/GPU在收到Vsync之后沒有在16.6ms(以60幀/秒為例)提供渲染結果供屏幕顯示,就會出現jank卡頓,這種情況下,就需要優(yōu)化代碼(isolate),或者做三級緩存(cache Buffer)在這里就不詳細介紹。

WX20210315-185343.png
WX20210315-185322.png

2.2flutter渲染

f7246b600c33874440c97d574abec4fed62aa0c1.jpeg
2.2.1渲染過程
  1. framework 注冊監(jiān)聽enginevsync信號
    2.engine注冊監(jiān)聽GPU的vsync信號
  2. GPU收到屏幕的vsync信號通知engine
    4.engine通知framework
  3. 通過framework完成頁面的重建、布局、繪制、合成,最終交由engine
  4. GPU存放到buffer中,等待屏幕來獲取,獲取之后,屏幕發(fā)出vsync信號,重復3-6步驟。
2.2.2 framework中的流程

framew中的操作是開發(fā)具體操作的流程,大致步驟如下圖:

WX20210318-110911.png
Layout

確定每個widget的寬高和在屏幕中的位置

Constraints go down. Sizes go up. Parent sets position.
約束條件向下傳,大小信息向上傳,父視圖決定位置

WX20210318-110923.png

節(jié)點在組建視圖樹的時候,每個節(jié)點的Constraint是自上而下,大小位置是從內到外的,應為節(jié)點自己的大小是由子節(jié)點size決定的,所以獲取size必須從下到上,從里到外。

通過下面的例子就很好理解了(來源flutter官網):

# 整個屏幕作為 Container 的父級,并且強制 Container 變成和屏幕一樣的大小。
# 所以這個 Container 充滿了整個屏幕,并繪制成紅色。
Container(color: Colors.red)


#紅色的 Container 想要變成 100 x 100 的大小,但是它無法變成,因為屏幕強制它變成和屏幕一樣的大小。
#所以 Container 充滿了整個屏幕。
Container(width: 100, height: 100, color: Colors.red)


#屏幕強制 Center 變得和屏幕一樣大,所以 Center 充滿了屏幕。
#然后 Center 告訴 Container 可以變成任意大小,但是不能超出屏幕。現在,Container 可以真正變成 100 × 100 大小了。
Center(
   child: Container(width: 100, height: 100, color: Colors.red)
)

Relayout boundary
通過上面的layout結構來看,一個節(jié)點的size變化了,整個樹結構都需要從新計算,為了避免這個問題使用Relayout boundary來解決這個問題。

WX20210318-144239.png

Relayout boundary的作用是設置測量邊界,邊界內的Widget做任何改變都不會導致邊界外重新計算并繪制。這個機制是在一些特定的情況下系統(tǒng)幫你設置的,一般情況下不需要開發(fā)手動來設置。

  • widget設置了固定的size(constraints.isTight (min = max))
  • widget是不需要依賴子widgetsize(parentUsesSize == false),這種情況下子widget重新布局的時候就不會通知父widget,布局的邊界就是自身。
  • widget自動占滿父widget所提供的空間大小(sizedByParent == true),比如(Expanded/Center)。
    以上三種情況,就會自動觸發(fā)了Relayout boundary機制,自動隔絕了父子組件的聯動刷新。

默認情況下,開發(fā)不要直接去設置Relayout boundary,只需要觸發(fā)上訴三個條件之一即可。

Paint

為widget提供畫布,讓widget進行繪制

在iOS中每一個UIView都有一個layerUIViewUIView之間是相互隔離的。但是flutter中的renderObject不一定有layer,通常情況下,一個renderObject的子節(jié)點都是渲染在同一個layer上,這就會出現以下一種情況:

23b34bb3984f1c8e0ac5cade96112d92cab.jpeg
  • 節(jié)點2、3、4、5、6 是節(jié)點1的子節(jié)點,應該繪制在節(jié)點1所在的綠色layer上。
  • 如果節(jié)點4(video)需要單獨占據一個layer,這個時候就4就會單獨占據一個黃色layer。
  • 節(jié)點2上面還有部分內容需要繪制,頂部黃色layer是4單獨占據的,需要新的紅色layer來繪制節(jié)點2上面剩余的部分內容5。
  • 6也會繪制在頂層紅色layer上,這樣紅色layer上就會存在5和6的共同數據。
  • 如果節(jié)點2發(fā)生改變紅色layer會重繪,這樣會影響毫不相干的節(jié)點6

Repaint Boundary
為了應對上訴情況,flutter設計了和Relayout Boundary相同思想的Repaint Boundary,在遇上上訴問題的時候 就強制添加新的layer
給節(jié)點6添加父節(jié)點RepaintBoundary widget,在創(chuàng)建的時候遇到RepaintBoundary widget就會添加新的layer,實現節(jié)點layer的隔離。即使發(fā)生重繪也不會其他子節(jié)點產生影響。

Composite

屏幕上看到的界面一般都是有很多的layer一層一層疊加在一起的,在這一步
把繪制好的多個layer合成為一個layer進行光柵化處理,然后交付給GPU處理,提高效率。

總結

flutter是一款非常優(yōu)秀的框架,在跨平臺上直截了當,和Native的切割也十分干凈,flutter已經發(fā)布了2.0版本,支持空安全優(yōu)化了web端的支持,期待flutter發(fā)展的越來越好。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容