1. 導(dǎo)讀
本文約4688字,閱讀可能需要15分鐘。
最早的跨平臺開發(fā)(摘自《Apache Cordova移動應(yīng)用開發(fā)實戰(zhàn)》王亞飛,王洪飛編著)
從廣義上來說,跨平臺技術(shù)早于移動端的出現(xiàn)。因此,本文標題前面也加上了一個定語:“移動端”。而由上圖也可窺見一二:移動端跨平臺技術(shù)幾乎和移動端本身的歷史一樣長??缙脚_技術(shù)之所以生命力如此強大,個人認為有以下幾個原因:
開發(fā)效率?: 這也是跨平臺技術(shù)出現(xiàn)的初衷,理想狀態(tài)下,一次開發(fā),多端運行,組件復(fù)用,提升效率。
對于管理者,跨平臺可以降低用人成本,避免了同時養(yǎng)兩個(Android/iOS)開發(fā)團隊的現(xiàn)狀
對于開發(fā)者,跨平臺可以降低學習成本,只需要了解一套框架,就可以實現(xiàn)雙端開發(fā),提升了自我價值
業(yè)務(wù)價值?: 跨平臺開發(fā)成本更低,適合產(chǎn)品的快速驗證,待功能穩(wěn)定后再進行性能體驗上的優(yōu)化
二級生態(tài)?: 舉一個例子,JVM在操作系統(tǒng)上建立了自己的二級生態(tài),所有的Java開發(fā)者只需要面向JVM編程和優(yōu)化,可以忽視操作系統(tǒng)的存在。在原生、底層的平臺上做一層封裝,以很小的性能損失為代價,為開發(fā)者帶來巨大的效率提升,這在軟件工業(yè)是屢見不鮮的。二級生態(tài)有重要的戰(zhàn)略意義,掌握了二級生態(tài),就掌握了話語權(quán)和影響力。所以Facebook和Google都在發(fā)力。
平臺能力?: 乘上第三點,跨平臺還有一個好處就是擁有了自己的生態(tài)就可以開放自己的能力,制定游戲規(guī)則讓其他開發(fā)者參與,典型有小程序(微信/QQ/支付寶)、快應(yīng)用等
跨平臺好處頗多,但挑戰(zhàn)也不少,主要集中在以下四個方面:
研發(fā)效率
工具支持程度:補全、提示、構(gòu)建管理等
Debug是否方便,錯誤日志是否詳細
文檔完備、項目活躍
隱藏平臺差異,如React Native需要大量橋接工作
開發(fā)語言生態(tài),js生態(tài)龐大,開發(fā)者眾,Dart則名不見經(jīng)傳
等等
動態(tài)化
iOS禁止,但國內(nèi)平臺普遍需要
多端一致性
Web方案無法還原體驗
性能
Web方案UI繪制效率低,網(wǎng)絡(luò)流量消耗高
游戲引擎耗電嚴重,不能應(yīng)用在普通應(yīng)用開發(fā)中
SDK引入導(dǎo)致的安裝包增量
2. 歷史行程
在過去的十多年間,主流的(不考慮一些小眾、沒有取得成功的方案)移動端跨平臺技術(shù)經(jīng)歷了三次變革:
Hybrid,代表有:Ionic/Cordova
OEM Wrapper,代表有:React Native/Weex
自渲染,代表有:Flutter
從時間上看,這三種方案不是孤立的,既有對前人不足之處的改進(如UI繪制策略),也有對優(yōu)秀思想的繼承(如React的思想)。如果站在更高的高度上,我們會看到這些方案并不是在移動端獨立演進的。在移動端普及之前,PC端已經(jīng)積累了很多成熟的方案,對于移動端的探索起到了指導(dǎo)作用,仔細比較一下會發(fā)現(xiàn)每一種方案都能找到已有方案的影子,只不過結(jié)合了移動端的特點做了定制。無論哪種跨平臺方案,都要回答兩個問題:
UI如何繪制
邏輯(包括用戶交互的邏輯和與宿主系統(tǒng)通訊的邏輯)如何響應(yīng)
對于這兩個問題,Hybrid給出的答案是webview+js,OEM Wrapper給出的是VirtualDOM轉(zhuǎn)Native組件+js,自渲染(Flutter)給出的答案是Skia+Dart。下文開始詳述。
2.1. Hybrid
架構(gòu)圖
Hybrid是客戶端跨平臺技術(shù)的第一個階段,核心原理是
將原生的接口封裝后暴露給 JavaScript,可以運行在系統(tǒng)自帶的 WebView中或者其他內(nèi)核中。這種方案在上文提到的評價體系里表現(xiàn)如下:
開發(fā)效率
對前端開發(fā)者友好,背靠前端龐大的JavaScript生態(tài)
涉及到Native調(diào)用的部分不可避免要熟悉Android/iOS
能力受限于橋接層,擴展性弱
在移動端開發(fā),調(diào)試和錯誤日志并不是很友好
動態(tài)化
Web天生自帶動態(tài)能力
多端一致性
瀏覽器內(nèi)核的渲染獨立于系統(tǒng)組件,無法保證原生體驗
涉及宿主的問題,需要開發(fā)者處理,做不到完全屏蔽
性能
受限于網(wǎng)絡(luò)環(huán)境,比Native更加消耗流量
受限于瀏覽器、系統(tǒng)平臺特性
渲染性能?,Webview性能差
特別指出:對于列表的支持差,移動端幾乎全是列表(feed流)
評價:Hybrid是矛盾的結(jié)合體,HTML/CSS 過于復(fù)雜導(dǎo)致性能問題,但其實這正是 Web 最大的優(yōu)勢所在,因為 Web 最初的目的就是顯示文檔,如果你想顯示豐富的圖文排版,雖然 iOS/Android 都有富文本組件,但比起 CSS 差太遠了,所以在很多 Native 應(yīng)用中是不可避免要嵌 Web 的(比如很多運營活動的頁面,存在周期短,開發(fā)時間短,樣式豐富繁多,適合H5開發(fā))。
既然Web強大的的繪制能力限制了其在移動端的性能,那么能不能對此進行優(yōu)化? 這是一個很重要的問題,很多看起來無關(guān)的方案都是基于這種思想發(fā)源來的:
React Native使用系統(tǒng)組件封裝,可以認為是把原來的瀏覽器內(nèi)核換成了一個簡化版的內(nèi)核:一個不能做物理渲染,只能轉(zhuǎn)換成有限原生組件的內(nèi)核。
Flutter的 Engine 模塊也可以認為是一個瀏覽器內(nèi)核的角色!事實上Flutter的前身Sky就是打算基于一個精簡的Chromium內(nèi)核來實現(xiàn)跨平臺。
2.2. OEM Wrapper
架構(gòu)圖
React Native架構(gòu)圖
大概到了2015年,經(jīng)歷了各種Hybrid方案割據(jù)混戰(zhàn)長達數(shù)年后,F(xiàn)acebook推出了React Native,這種方案迎合了大前端的趨勢,一經(jīng)推出就備受關(guān)注。核心改變是拋棄了低效的瀏覽器內(nèi)核渲染,轉(zhuǎn)而使用自己的DSL生成中間格式,進而映射到對應(yīng)的平臺。
其實這個方案其實也算不上什么創(chuàng)舉,在PC時代,The Standard Widget Toolkit采用的就是這種方案(當然要做到React Native這種水平,非Facebook級別的公司不能為也):
From SWT官網(wǎng)
React Native 在評價體系表現(xiàn)如下:
開發(fā)效率
在Web基礎(chǔ)上引進了React等能力,符合前端大趨勢
版本變動頻繁,需要開發(fā)者自己優(yōu)化,工作量大
與Native交互需要開發(fā)者自己支持,維護成本高
文檔不完善、調(diào)試信息、錯誤日志提示不夠友好
動態(tài)化
可以支持
多端一致性
渲染成各自平臺的組件,可以保證Native的體驗
由于渲染依賴原生控件,不同平臺的控件需要單獨維護,并且當系統(tǒng)更新時,社區(qū)控件可能會滯后
其控件系統(tǒng)也會受到原生UI系統(tǒng)限制,例如,在Android中,手勢沖突消歧規(guī)則是固定的,這在使用不同人寫的控件嵌套時,手勢沖突問題將會變得非常棘手
性能
稍差于Native,但遠好于Hybrid
渲染時需要JavaScript和原生之間通信,在有些場景如拖動可能會因為通信頻繁導(dǎo)致卡頓
JavaScript為腳本語言,執(zhí)行時需要JIT,執(zhí)行效率和AOT代碼仍有差距。
評價:使用類前端的語法,但又不在瀏覽器內(nèi)核直接繪制,而是轉(zhuǎn)成Native控件,交由系統(tǒng)繪制。這樣既保留了前端這套開發(fā)體系,又最大限度保證了渲染的性能。咋一看,React Native解決了 Hybrid 技術(shù)的痛點:渲染性能,又充分發(fā)揮了Hybrid 的優(yōu)勢:前端技術(shù)棧。但就在2018年,Airbnb和Udacity相繼宣布棄用React Native,Facebook也宣布要大規(guī)模重構(gòu)React Native,導(dǎo)致其前景堪憂,比起React Native的美好愿景,其在開發(fā)過程中需要踩的坑更多,長期的維護成本也很高,反而降低了開發(fā)效率,此外,庫的增量也不容忽視。
2.3. 自渲染
架構(gòu)圖
Flutter 在評價體系表現(xiàn)如下:
開發(fā)效率
開發(fā)工具完備,提供了VS Code(最流行的編輯器),Intellij IDEA 插件
Google背書,文檔完備,社區(qū)較完備
Dart語言本身有上手成本,沒有前端的生態(tài),但Dart語言本身是及其優(yōu)秀的
動態(tài)化
動態(tài)性不足,為了保證UI繪制性能,自繪UI系統(tǒng)一般都會采用AOT模式編譯其發(fā)布包
可能涉及安全政策,F(xiàn)lutter Release目前不支持
國內(nèi)開發(fā)者正在做積極探索
多端一致性
自繪制UI,提供了Material Design和Cupertino兩種風格的Widget
性能
性能和Native繪制一樣
評價:Flutter站在前人的肩膀上,取長(React的狀態(tài)管理、Web的自繪制UI、React Native的HotReload等)補短(與Native通信的Channel機制、自渲染、完備的開發(fā)工具鏈),并且有Google作為作為支撐,在跨平臺領(lǐng)域后發(fā)制人,是目前最被看好的方案。
關(guān)于Dart,在開發(fā)者踩了十幾年坑之后,Google和Microsoft兩大巨頭似乎看清了需要一種新的、更現(xiàn)代、更適合UI開發(fā)的編程語言來重新建立秩序。
谷歌要推Dart,微軟要推Rust,這兩門語言的年齡比很多開發(fā)者的從業(yè)年齡都要小,大概是要以犧牲一代開發(fā)者為代價換取一個沒有歷史包袱(做夢)的新生態(tài)吧(手動狗頭)。
3. Flutter初探
3.1. Flutter技術(shù)架構(gòu)
在Framework層,有以下內(nèi)容
Foundation?底層庫負責UI繪制和交互處理的?
Animation?、?Paint?等模塊基于上面的接口實現(xiàn)的基礎(chǔ)組件?
Widgets基于組件實現(xiàn)的?
Material?風格組件(Google推薦)和?Cupertino?風格組件(iOS風格)
在Engine層,有以下內(nèi)容
Skia?圖形處理庫Dart?運行時Text?文字排版引擎
這張圖和Android自身的架構(gòu)很像
這種設(shè)計符合Unix哲學的?正交性?原則(計算機網(wǎng)絡(luò)中的OSI模型也是),可以很好地屏蔽每一個層的細節(jié),接下來我們基于這個架構(gòu)圖討論幾個問題
Flutter如何繪制UI
為什么選擇Dart
如何和Native交互
HotReload與動態(tài)化
3.2. GUI Framework
在開始下文之前,需要介紹一下現(xiàn)代GUI框架的模型,無論是Web、PC、移動端、游戲開發(fā),都是基于這種框架工作
Widgets(控件,也叫 View Tree),它是用于描述用戶界面原始數(shù)據(jù)的樹狀結(jié)構(gòu)。通常這一層根本不關(guān)心繪制,它只關(guān)心用戶對數(shù)據(jù)的操作。
Render Tree,它是一種更為抽象的樹狀數(shù)據(jù)結(jié)構(gòu),一般來說它是和上一步的 View Tree結(jié)構(gòu)相同,并且它不關(guān)心原始數(shù)據(jù),只關(guān)心控件的布局和大小。通過這一步計算出控件布局后才能真正地確定控件的外觀。
Layer Tree 跟 Render Tree是相對應(yīng)的,這一步會主動觸發(fā) Render Tree中每個元素的外觀渲染,在已知控件大小和位置的情況下決定每個控件的真正外觀。但 Layer Tree的樹狀結(jié)構(gòu)不是和 Render Tree一一對應(yīng)的,Layer Tree有可能因為 Layer合并優(yōu)化導(dǎo)致一層的 Render Tree葉子節(jié)點最終只對應(yīng)一個 Layer。
在已經(jīng)決定好控件的大小位置以及長相后,剩下的工作就需要把這些東西組合起來顯示到屏幕上。這一步原理比較簡單,就是將前一步的 Layer合并成一張 Bitmap,這是一種最簡單的圖像存儲形式。將 Bitmap光柵化后便可以提交給 GPU渲染。
比如Android開發(fā)都很熟悉的?measure layout draw?就和前三層吻合,有了原始數(shù)據(jù)還不夠,屏幕只關(guān)心每個像素點的值,所以最后要進行一次光柵化,歷史已經(jīng)證明這種模型是目前來說相對高效的GUI方案。
3.3. Flutter如何繪制UI
有了以上背景,我們來看Flutter的UI繪制過程。
Flutter繪制流程1
Flutter繪制流程2
GPU發(fā)出 Vsync 信號,Dart捕獲后就開始一幀的繪制。
Throttle: 用來做節(jié)流,防止短時間內(nèi)重復(fù)調(diào)用,提高性能。
Compositor: 這一步進行 Layer合成,決定某一塊具體顯示哪一個 Layer的數(shù)據(jù),可以額外的計算開支。
GL or Vulkan: 這一階段過后得到的將是一份矢量圖數(shù)據(jù),在進行光柵化后提交給 GPU執(zhí)行渲染即可。
3.4. 為什么選擇Dart
官方解釋:
Developer productivity
JIT + AOT
Dart開發(fā)團隊對于Flutter支持粒度很大
Object-orientation
Predictable, high performance
Fast allocation.
其他聲音:
Dart 的性能更好,對高幀率下的視圖數(shù)據(jù)計算很有幫助。
多生代無鎖垃圾回收器,專門為UI框架中常見的大量Widgets對象創(chuàng)建和銷毀優(yōu)化
Native Binding,在 Android上,v8的 Native Binding可以很好地實現(xiàn),但是 iOS上的 JavaScriptCore不可以,所以如果使用 JavaScript,F(xiàn)lutter 基礎(chǔ)框架的代碼模式就很難統(tǒng)一了。而 Dart的 Native Binding可以很好地通過 Dart Lib實現(xiàn)。
Fuchsia OS(谷歌的野心:5G + IOT)
Dart是類型安全的語言,擁有完善的包管理和諸多特性。
關(guān)于IOT,國內(nèi)已經(jīng)有開發(fā)者嘗試在樹莓派上使用Flutter了。
總的來說,想要挑戰(zhàn)JavaScript的地位還是很難的,JavaScript雖然有很多缺陷,但龐大的生態(tài)已經(jīng)為自己建立了穩(wěn)固的堡壘。
有一個關(guān)于Lisp的笑話,我覺得也可以改個Dart版本的:某小偷偷了美國國防部機密軟件的源碼的最后幾頁打算拿回去好好研究,但等他真的打開時發(fā)現(xiàn)是這樣
3.5. 如何和Native交互
Flutter Platform Channel Demo
通過 Platform Channel機制進行通信,常用的 Platform Channel主要有三種
BasicMessageChannel:傳遞字符串和半結(jié)構(gòu)化數(shù)據(jù)
MethodChannel:方法調(diào)用
EventChannel:數(shù)據(jù)流的通信
每種Channel具有三個重要的成員變量:
name: String類型,代表Channel的名字,也是其唯一標識符。
messager:BinaryMessenger類型,代表消息信使,是消息的發(fā)送與接收的工具。
codec: MessageCodec類型或MethodCodec類型,代表消息的編解碼器。
對常見的Channel進一步封裝成Plugin
3.6. HotReload與動態(tài)化
Flutter Hot Demo
Flutter的HotReload確實讓人耳目一新,但這東西可能只是對于客戶端開發(fā)比較稀奇,從Instant Run到freeline,開發(fā)者一直希望能提高項目構(gòu)建速度,更快地看到代碼改動的結(jié)果,從原理上來說,只要這門語言及其所在平臺支持解釋執(zhí)行(or JIT)和增量編譯就可以做到很好的HotReload效果,flutter的這一個特性算是填上了之前Android開發(fā)的一個坑,創(chuàng)舉肯定是算不上的。至于動態(tài)化,可以算是國內(nèi)開發(fā)者的剛需了,F(xiàn)lutter之前移除了對動態(tài)化的支持,可能是擔心iOS的政策原因,目前不少開發(fā)者也對Flutter展開了研究,后面應(yīng)該有一批魔改方案。Flutter的HotReload過程:
首先會掃描代碼,找到上次編譯之后有變化的Dart代碼;
將這些變化的Dart代碼轉(zhuǎn)化為增量的Dart Kernel文件;
將增量的Dart Kernel文件發(fā)送到正在移動設(shè)備上運行的Dart VM;
觸發(fā)widgets樹的重新建立、重新布局、重新繪制
不能使用的場景(所以對這個功能不能過于樂觀,業(yè)務(wù)復(fù)雜之后能用的場景就不會太多):
代碼出現(xiàn)編譯錯誤的不能使用 Hot Reload
Widget的狀態(tài)更改不能使用 Hot Reload
在 Flutter 中,全局變量和靜態(tài)字段被視為狀態(tài),因此在 Hot Reload 期間不會重新初始化。
修改通用類型聲明時
4. 評價
從Hybrid到Flutter,突破性的創(chuàng)新是不會有的,每一個特性、功能都是扎扎實實演進過來的,這也是標題的本意,F(xiàn)lutter能否成為大前端的答案尚未可知,谷歌布的局又有多大也不清楚,核心還是要把握住發(fā)展脈絡(luò),對新事物保持警惕和清醒。浪潮退去才知道誰在裸泳,有一天Flutter的替代者來了,才知道誰是API boy(哭泣臉)。
5. 參考
Web App Hybrid App和 Native App的區(qū)別
跨端開發(fā)面面談之基于WebView的Hybrid開發(fā)模式 - 知乎
SWT: The Standard Widget Toolkit | The Eclipse Foundation
深入理解react-native | 垂釣江湖
React Native at Airbnb - Airbnb Engineering & Data Science - Medium
Udacity也棄用React Native了 !-InfoQ
React Native: A retrospective from the mobile-engineering team at Udacity
Flutter 基礎(chǔ)(一)移動開發(fā)的跨平臺技術(shù)演進 | Flutter 技術(shù)社區(qū)
聊聊移動端跨平臺開發(fā)的各種技術(shù) - FEX
閑聊 Flutter ? bang’s blog
Flutter在2019年會有怎樣的表現(xiàn) - 21CTO - 從程序員到技術(shù)官
Flutter System Architecture - Google 幻燈片
Flutter’s Rendering Engine: A Tutorial — Part 1 - SAUGO 360 - Medium
Why Flutter doesn’t use OEM widgets - Flutter - Medium
Flutter原理與實踐 - 美團技術(shù)團隊
Flutter原理簡解 · stephenwzl
GUI Framework Inside · stephenwzl
GMTC全球大前端技術(shù)大會(北京站)2019
FAQ - Flutter
Note |
本文永久鏈接?/?如何發(fā)表評論?/?關(guān)于本博客?/?關(guān)于作者 |