Flutter:移動端跨平臺技術演進之路

1. 導讀

本文約4688字,閱讀可能須要15分鐘。前端

最先的跨平臺開發(摘自《Apache Cordova移動應用開發實戰》王亞飛,王洪飛編著)

Snipaste 2019 11 08 20 13 54

從廣義上來講,跨平臺技術早於移動端的出現。所以,本文標題前面也加上了一個定語:「移動端」。而由上圖也可窺見一二:移動端跨平臺技術幾乎和移動端自己的歷史同樣長。跨平臺技術之因此生命力如此強大,我的認爲有如下幾個緣由:react

  1. 開發效率 : 這也是跨平臺技術出現的初衷,理想狀態下,一次開發,多端運行,組件複用,提高效率。android

    • 對於管理者,跨平臺能夠下降用人成本,避免了同時養兩個(Android/iOS)開發團隊的現狀git

    • 對於開發者,跨平臺能夠下降學習成本,只須要了解一套框架,就能夠實現雙端開發,提高了自我價值程序員

  2. 業務價值 : 跨平臺開發成本更低,適合產品的快速驗證,待功能穩定後再進行性能體驗上的優化github

  3. 二級生態 : 舉一個例子,JVM在操做系統上創建了本身的二級生態,全部的Java開發者只須要面向JVM編程和優化,能夠忽視操做系統的存在。在原生、底層的平臺上作一層封裝,以很小的性能損失爲代價,爲開發者帶來巨大的效率提高,這在軟件工業是家常便飯的。二級生態有重要的戰略意義,掌握了二級生態,就掌握了話語權和影響力。因此Facebook和Google都在發力。web

  4. 平臺能力 : 乘上第三點,跨平臺還有一個好處就是擁有了本身的生態就能夠開放本身的能力,制定遊戲規則讓其餘開發者參與,典型有小程序(微信/QQ/支付寶)、快應用等編程

跨平臺好處頗多,但挑戰也很多,主要集中在如下四個方面:小程序

  1. 研發效率

    • 工具支持程度:補全、提示、構建管理等

    • Debug是否方便,錯誤日誌是否詳細

    • 文檔完備、項目活躍

    • 隱藏平臺差別,如React Native須要大量橋接工做

    • 開發語言生態,js生態龐大,開發者衆,Dart則名不見經傳

    • 等等

  2. 動態化

    • iOS禁止,但國內平臺廣泛須要

  3. 多端一致性

    • Web方案沒法還原體驗

  4. 性能

    • Web方案UI繪製效率低,網絡流量消耗高

    • 遊戲引擎耗電嚴重,不能應用在普通應用開發中

    • SDK引入致使的安裝包增量

2. 歷史行程

在過去的十多年間,主流的(不考慮一些小衆、沒有取得成功的方案)移動端跨平臺技術經歷了三次變革:

  • Hybrid,表明有:Ionic/Cordova

  • OEM Wrapper,表明有:React Native/Weex

  • 自渲染,表明有:Flutter

從時間上看,這三種方案不是孤立的,既有對前人不足之處的改進(如UI繪製策略),也有對優秀思想的繼承(如React的思想)。若是站在更高的高度上,咱們會看到這些方案並非在移動端獨立演進的。在移動端普及以前,PC端已經積累了不少成熟的方案,對於移動端的探索起到了指導做用,仔細比較一下會發現每一種方案都能找到已有方案的影子,只不過結合了移動端的特色作了定製。 不管哪一種跨平臺方案,都要回答兩個問題:

  1. UI如何繪製

  2. 邏輯(包括用戶交互的邏輯和與宿主系統通信的邏輯)如何響應

對於這兩個問題,Hybrid給出的答案是webview+js,OEM Wrapper給出的是VirtualDOM轉Native組件+js,自渲染(Flutter)給出的答案是Skia+Dart。下文開始詳述。

2.1. Hybrid

texingfenxi

架構圖

hybrid

Hybrid是客戶端跨平臺技術的第一個階段,核心原理是
將原生的接口封裝後暴露給 JavaScript,能夠運行在系統自帶的 WebView中或者其餘內核中。這種方案在上文提到的評價體系裏表現以下:

  1. 開發效率

    • 對前端開發者友好,背靠前端龐大的JavaScript生態

    • 涉及到Native調用的部分不可避免要熟悉Android/iOS

    • 能力受限於橋接層,擴展性弱

    • 在移動端開發,調試和錯誤日誌並非很友好

  2. 動態化

    • Web天生自帶動態能力

  3. 多端一致性

    • 瀏覽器內核的渲染獨立於系統組件,沒法保證原生體驗

    • 涉及宿主的問題,須要開發者處理,作不到徹底屏蔽

  4. 性能

    • 受限於網絡環境,比Native更加消耗流量

    • 受限於瀏覽器、系統平臺特性

    • 渲染性能 ,Webview性能差

    • 特別指出:對於列表的支持差,移動端幾乎全是列表(feed流)

評價:Hybrid是矛盾的結合體,HTML/CSS 過於複雜致使性能問題,但其實這正是 Web 最大的優點所在,由於 Web 最初的目的就是顯示文檔,若是你想顯示豐富的圖文排版,雖然 iOS/Android 都有富文本組件,但比起 CSS 差太遠了,因此在不少 Native 應用中是不可避免要嵌 Web 的(好比不少運營活動的頁面,存在週期短,開發時間短,樣式豐富繁多,適合H5開發)。

既然Web強大的的繪製能力限制了其在移動端的性能,那麼能不能對此進行優化? 這是一個很重要的問題,不少看起來無關的方案都是基於這種思想發源來的:

  • React Native使用系統組件封裝,能夠認爲是把原來的瀏覽器內核換成了一個簡化版的內核:一個不能作物理渲染,只能轉換成有限原生組件的內核。

  • Flutter的 Engine 模塊也能夠認爲是一個瀏覽器內核的角色!事實上Flutter的前身Sky就是打算基於一個精簡的Chromium內核來實現跨平臺。

2.2. OEM Wrapper

架構圖

oem

React Native架構圖

Snipaste 2019 11 16 16 15 28

大概到了2015年,經歷了各類Hybrid方案割據混戰長達數年後,Facebook推出了React Native,這種方案迎合了大前端的趨勢,一經推出就備受關注。核心改變是拋棄了低效的瀏覽器內核渲染,轉而使用本身的DSL生成中間格式,進而映射到對應的平臺。
其實這個方案其實也算不上什麼創舉,在PC時代,The Standard Widget Toolkit採用的就是這種方案(固然要作到React Native這種水平,非Facebook級別的公司不能爲也):

From SWT官網

React Native 在評價體系表現以下:

  1. 開發效率

    • 在Web基礎上引進了React等能力,符合前端大趨勢

    • 版本變更頻繁,須要開發者本身優化,工做量大

    • 與Native交互須要開發者本身支持,維護成本高

    • 文檔不完善、調試信息、錯誤日誌提示不夠友好

  2. 動態化

    • 能夠支持

  3. 多端一致性

    • 渲染成各自平臺的組件,能夠保證Native的體驗

    • 因爲渲染依賴原生控件,不一樣平臺的控件須要單獨維護,而且當系統更新時,社區控件可能會滯後

    • 其控件系統也會受到原生UI系統限制,例如,在Android中,手勢衝突消歧規則是固定的,這在使用不一樣人寫的控件嵌套時,手勢衝突問題將會變得很是棘手

  4. 性能

    • 稍差於Native,但遠好於Hybrid

    • 渲染時須要JavaScript和原生之間通訊,在有些場景如拖動可能會由於通訊頻繁致使卡頓

    • JavaScript爲腳本語言,執行時須要JIT,執行效率和AOT代碼仍有差距。

評價:使用類前端的語法,但又不在瀏覽器內核直接繪製,而是轉成Native控件,交由系統繪製。這樣既保留了前端這套開發體系,又最大限度保證了渲染的性能。咋一看,React Native解決了 Hybrid 技術的痛點:渲染性能,又充分發揮了Hybrid 的優點:前端技術棧。但就在2018年,Airbnb和Udacity相繼宣佈棄用React Native,Facebook也宣佈要大規模重構React Native,致使其前景堪憂,比起React Native的美好願景,其在開發過程當中須要踩的坑更多,長期的維護成本也很高,反而下降了開發效率,此外,庫的增量也不容忽視。

2.3. 自渲染

架構圖

flutter

Flutter 在評價體系表現以下:

  1. 開發效率

    • 開發工具完備,提供了VS Code(最流行的編輯器),Intellij IDEA 插件

    • Google背書,文檔完備,社區較完備

    • Dart語言自己有上手成本,沒有前端的生態,但Dart語言自己是及其優秀的

  2. 動態化

    • 動態性不足,爲了保證UI繪製性能,自繪UI系統通常都會採用AOT模式編譯其發佈包

    • 可能涉及安全政策,Flutter Release目前不支持

    • 國內開發者正在作積極探索

  3. 多端一致性

    • 自繪製UI,提供了Material Design和Cupertino兩種風格的Widget

  4. 性能

    • 性能和Native繪製同樣

評價:Flutter站在前人的肩膀上,取長(React的狀態管理、Web的自繪製UI、React Native的HotReload等)補短(與Native通訊的Channel機制、自渲染、完備的開發工具鏈),而且有Google做爲做爲支撐,在跨平臺領域後發制人,是目前最被看好的方案。

關於Dart,在開發者踩了十幾年坑以後,Google和Microsoft兩大巨頭彷佛看清了須要一種新的、更現代、更適合UI開發的編程語言來從新創建秩序。

Snipaste 2019 11 16 20 22 00

谷歌要推Dart,微軟要推Rust,這兩門語言的年齡比不少開發者的從業年齡都要小,大概是要以犧牲一代開發者爲代價換取一個沒有歷史包袱(作夢)的新生態吧(手動狗頭)。

3. Flutter初探

3.1. Flutter技術架構

FlutterSystemArchitecture01

在Framework層,有如下內容

  • Foundation 底層庫

  • 負責UI繪製和交互處理的 AnimationPaint 等模塊

  • 基於上面的接口實現的基礎組件 Widgets

  • 基於組件實現的 Material 風格組件(Google推薦)和 Cupertino 風格組件(iOS風格)

在Engine層,有如下內容

  • Skia 圖形處理庫

  • Dart 運行時

  • Text 文字排版引擎

這張圖和Android自身的架構很像

android stack 2x

這種設計符合Unix哲學的 正交性 原則(計算機網絡中的OSI模型也是),能夠很好地屏蔽每個層的細節,接下來咱們基於這個架構圖討論幾個問題

  • Flutter如何繪製UI

  • 爲何選擇Dart

  • 如何和Native交互

  • HotReload與動態化

3.2. GUI Framework

在開始下文以前,須要介紹一下現代GUI框架的模型,不管是Web、PC、移動端、遊戲開發,都是基於這種框架工做

Snipaste 2019 11 17 13 51 11

  • Widgets(控件,也叫 View Tree),它是用於描述用戶界面原始數據的樹狀結構。一般這一層根本不關心繪製,它只關心用戶對數據的操做。

  • Render Tree,它是一種更爲抽象的樹狀數據結構,通常來講它是和上一步的 View Tree結構相同,而且它不關心原始數據,只關心控件的佈局和大小。經過這一步計算出控件佈局後才能真正地肯定控件的外觀。

  • Layer Tree 跟 Render Tree是相對應的,這一步會主動觸發 Render Tree中每一個元素的外觀渲染,在已知控件大小和位置的狀況下決定每一個控件的真正外觀。但 Layer Tree的樹狀結構不是和 Render Tree一一對應的,Layer Tree有可能由於 Layer合併優化致使一層的 Render Tree葉子節點最終只對應一個 Layer。

  • 在已經決定好控件的大小位置以及長相後,剩下的工做就須要把這些東西組合起來顯示到屏幕上。這一步原理比較簡單,就是將前一步的 Layer合併成一張 Bitmap,這是一種最簡單的圖像存儲形式。將 Bitmap光柵化後即可以提交給 GPU渲染。

好比Android開發都很熟悉的 measure layout draw 就和前三層吻合,有了原始數據還不夠,屏幕只關心每一個像素點的值,因此最後要進行一次光柵化,歷史已經證實這種模型是目前來講相對高效的GUI方案。

3.3. Flutter如何繪製UI

有了以上背景,咱們來看Flutter的UI繪製過程。

Flutter繪製流程1

FlutterSystemArchitecture02

Flutter繪製流程2

FlutterSystemArchitecture03

  • GPU發出 Vsync 信號,Dart捕獲後就開始一幀的繪製。

  • Throttle: 用來作節流,防止短期內重複調用,提升性能。

  • Compositor: 這一步進行 Layer合成,決定某一塊具體顯示哪個 Layer的數據,能夠額外的計算開支。

  • GL or Vulkan: 這一階段事後獲得的將是一份矢量圖數據,在進行光柵化後提交給 GPU執行渲染便可。

3.4. 爲何選擇Dart

官方解釋:

  • Developer productivity

    • JIT + AOT

    • Dart開發團隊對於Flutter支持粒度很大

  • Object-orientation

  • Predictable, high performance

  • Fast allocation.

其餘聲音:

  • Dart 的性能更好,對高幀率下的視圖數據計算頗有幫助。

  • 多生代無鎖垃圾回收器,專門爲UI框架中常見的大量Widgets對象建立和銷燬優化

  • Native Binding,在 Android上,v8的 Native Binding能夠很好地實現,可是 iOS上的 JavaScriptCore不能夠,因此若是使用 JavaScript,Flutter 基礎框架的代碼模式就很難統一了。而 Dart的 Native Binding能夠很好地經過 Dart Lib實現。

  • Fuchsia OS(谷歌的野心:5G + IOT) 

  • Dart是類型安全的語言,擁有完善的包管理和諸多特性。

關於IOT,國內已經有開發者嘗試在樹莓派上使用Flutter了。

總的來講,想要挑戰JavaScript的地位仍是很難的,JavaScript雖然有不少缺陷,但龐大的生態已經爲本身創建了穩固的堡壘。

有一個關於Lisp的笑話,我以爲也能夠改個Dart版本的:某小偷偷了美國國防部機密軟件的源碼的最後幾頁打算拿回去好好研究,但等他真的打開時發現是這樣

Snipaste 2019 11 16 20 32 51

3.5. 如何和Native交互

Flutter Platform Channel Demo

經過 Platform Channel機制進行通訊,經常使用的 Platform Channel主要有三種

  • BasicMessageChannel:傳遞字符串和半結構化數據

  • MethodChannel:方法調用

  • EventChannel:數據流的通訊

每種Channel具備三個重要的成員變量:

  • name: String類型,表明Channel的名字,也是其惟一標識符。

  • messager:BinaryMessenger類型,表明消息信使,是消息的發送與接收的工具。

  • codec: MessageCodec類型或MethodCodec類型,表明消息的編解碼器。

對常見的Channel進一步封裝成Plugin

Snipaste 2019 11 17 18 23 39

3.6. HotReload與動態化

Flutter Hot Demo

Flutter的HotReload確實讓人耳目一新,但這東西可能只是對於客戶端開發比較稀奇,從Instant Run到freeline,開發者一直但願能提升項目構建速度,更快地看到代碼改動的結果,從原理上來講,只要這門語言及其所在平臺支持解釋執行(or JIT)和增量編譯就能夠作到很好的HotReload效果,flutter的這一個特性算是填上了以前Android開發的一個坑,創舉確定是算不上的。 至於動態化,能夠算是國內開發者的剛需了,Flutter以前移除了對動態化的支持,多是擔憂iOS的政策緣由,目前很多開發者也對Flutter展開了研究,後面應該有一批魔改方案。
Flutter的HotReload過程:

  1. 首先會掃描代碼,找到上次編譯以後有變化的Dart代碼;

  2. 將這些變化的Dart代碼轉化爲增量的Dart Kernel文件;

  3. 將增量的Dart Kernel文件發送到正在移動設備上運行的Dart VM;

  4. 觸發widgets樹的從新創建、從新佈局、從新繪製

不能使用的場景(因此對這個功能不能過於樂觀,業務複雜以後能用的場景就不會太多):

  1. 代碼出現編譯錯誤的不能使用 Hot Reload

  2. Widget的狀態更改不能使用 Hot Reload

  3. 在 Flutter 中,全局變量和靜態字段被視爲狀態,所以在 Hot Reload 期間不會從新初始化。

  4. 修改通用類型聲明時

4. 評價

從Hybrid到Flutter,突破性的創新是不會有的,每個特性、功能都是紮紮實實演進過來的,這也是標題的本意,Flutter可否成爲大前端的答案還沒有可知,谷歌布的局又有多大也不清楚,核心仍是要把握住發展脈絡,對新事物保持警戒和清醒。浪潮退去才知道誰在裸泳,有一天Flutter的替代者來了,才知道誰是API boy(哭泣臉)。

5. 參考

相關文章
相關標籤/搜索