
1架構理解
談到跨平臺一般繞不過web、hybird、react native等框架,其中RN是目前比較主流的跨平臺解決方案。我們拿RN和flutter做一下對比。
1.1跨平臺架構簡介
1.1.1 React Native:

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 :

flutter的架構由兩部分組成 分別是framework和Engine:
1.1.2.1framework
framework是一個Dart庫 包括組件、手勢、動畫、有Material和Cupertino風格,是我們最常打交道的庫。開發(fā)中的接大部分需求framework給我們提供的庫都可以滿足。
1.1.2.2Engine
Engine是C/C++編寫的庫,是flutter的核心庫,包含了Dart虛擬機、動畫和圖形、文字渲染、通信通道等,其中渲染引擎采用的是2D的圖形渲染庫Skia,虛擬機是Dart VM,另外還包含了Embedder中間層代碼。
1.2RN和flutter對比
簡單了解了RN和Flutter的架構,我們就能很直觀的看到兩者之間差異,至于為什么同是跨平臺語言,Flutter性能優(yōu)于RN、近似Native,我們看一下下面這張圖:

1.2.1flutter
構建一個flutter項目,我們通過dart語言開發(fā),使用flutter framework提供的庫便可以,之后交于Engine中的Skia引擎做圖像的繪制,最后生成的CPU或者GPU的指令,在設備上完成繪圖。整個過程和Native的流程一模一樣。
1.2.2 Android
從上圖可以看出flutter框架代碼完全取代了Android的Java代碼,所以只要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)化。
在flutter中skia是包含在flutter SDK中的,只要flutter SDK升級,skia也會隨著升級,目前flutter處于快速迭代期,skia上最新的優(yōu)化和改進會很快的應用到flutter項目中。android系統(tǒng)的升級相對慢,且對低端機的升級也不是太友好,所以skia的優(yōu)化在原生app上是不得到及時的體現。
1.3flutter和native
經過上面的分析,我們再看下面這幅圖基本上就可以清晰的看出flutter和native的對比和優(yōu)勢:

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

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)在這里就不詳細介紹。


2.2flutter渲染

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

Layout
確定每個widget的寬高和在屏幕中的位置
Constraints go down. Sizes go up. Parent sets position.
約束條件向下傳,大小信息向上傳,父視圖決定位置

節(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來解決這個問題。

Relayout boundary的作用是設置測量邊界,邊界內的Widget做任何改變都不會導致邊界外重新計算并繪制。這個機制是在一些特定的情況下系統(tǒng)幫你設置的,一般情況下不需要開發(fā)手動來設置。
-
widget設置了固定的size(constraints.isTight (min = max)) - 父
widget是不需要依賴子widget的size(parentUsesSize == false),這種情況下子widget重新布局的時候就不會通知父widget,布局的邊界就是自身。 - 子
widget自動占滿父widget所提供的空間大小(sizedByParent == true),比如(Expanded/Center)。
以上三種情況,就會自動觸發(fā)了Relayout boundary機制,自動隔絕了父子組件的聯動刷新。
默認情況下,開發(fā)不要直接去設置Relayout boundary,只需要觸發(fā)上訴三個條件之一即可。
Paint
為widget提供畫布,讓widget進行繪制
在iOS中每一個UIView都有一個layer,UIView和UIView之間是相互隔離的。但是flutter中的renderObject不一定有layer,通常情況下,一個renderObject的子節(jié)點都是渲染在同一個layer上,這就會出現以下一種情況:

- 節(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ā)展的越來越好。