Flutter實(shí)現(xiàn)原理及在馬蜂窩的跨平臺開發(fā)實(shí)踐
一直以來,跨平臺開發(fā)都是困擾移動(dòng)客戶端開發(fā)的難題。
在馬蜂窩旅游 App 很多業(yè)務(wù)場景里,我們嘗試過一些主流的跨平臺開發(fā)解決方案, 比如WebView 和 React Native,來提升開發(fā)效率和用戶體驗(yàn)。但這兩種方式也帶來了新的問題。
比如使用 WebView 跨平臺方式,優(yōu)點(diǎn)確實(shí)非常明顯?;?WebView 的框架集成了當(dāng)下 Web 開發(fā)的諸多優(yōu)勢:豐富的控件庫、動(dòng)態(tài)化、良好的技術(shù)社區(qū)、測試自動(dòng)化等等。但是缺點(diǎn)也同樣明顯:渲染效率和 JavaScript 的執(zhí)行能力都比較差,使頁面的加載速度和用戶體驗(yàn)都不盡如人意。
而使用以 React Native(簡稱 RN)為代表的框架時(shí),維護(hù)又成了大難題。RN 使用類 HTML+JS 的 UI 創(chuàng)建邏輯,生成對應(yīng)的原生頁面,將頁面的渲染工作交給了系統(tǒng),所以渲染效率有很大的優(yōu)勢。但由于 RN 代碼是通過 JS 橋接的方式轉(zhuǎn)換為原生的控件,所以受各個(gè)系統(tǒng)間的差異影響非常大,雖然可以開發(fā)一套代碼,但對各個(gè)平臺的適配卻非常的繁瑣和麻煩。
為什么是 Flutter
2018 年 12 月初,Google 正式發(fā)布了開源跨平臺 UI 框架 Flutter 1.0 Release 版本,馬蜂窩電商客戶端團(tuán)隊(duì)進(jìn)行了調(diào)研與實(shí)踐,發(fā)現(xiàn)Flutter能很好的幫助我們解決開發(fā)中遇到的問題。
- 跨平臺開發(fā),針對 Android 與 iOS 的風(fēng)格設(shè)計(jì)了兩套設(shè)計(jì)語言的控件實(shí)現(xiàn)(Material & Cupertino)。這樣不但能夠節(jié)約人力成本,而且在用戶體驗(yàn)上更好的適配 App 運(yùn)行的平臺。
- 重寫了一套跨平臺的 UI 框架,渲染引擎是依靠 Skia 圖形庫實(shí)現(xiàn)。Flutter 中的控件樹直接由渲染引擎和高性能本地 ARM 代碼直接繪制,不需要通過中間對象(Web 應(yīng)用中的虛擬 DOM 和真實(shí) DOM,原生 App 中的虛擬控件和平臺控件)來繪制,使它有接近原生頁面的性能,幫助我們提供更好的用戶體驗(yàn)。
- 同時(shí)支持 JIT 和 AOT 編譯。JIT編譯方式使其在開發(fā)階段有個(gè)備受歡迎的功能——熱重載(HotReload),這樣在開發(fā)時(shí)可以省去構(gòu)建的過程,提高開發(fā)效率。而在 Release 運(yùn)行階段采用 AOT 的編譯方式,使執(zhí)行效率非常高,讓 Release 版本發(fā)揮更好的性能。
于是,電商客戶端團(tuán)隊(duì)決定探索 Flutter 在跨平臺開發(fā)中的新可能,并率先應(yīng)用于商家端 App 中。在本文中,我們將結(jié)合 Flutter 在馬蜂窩商家端 App 中的應(yīng)用實(shí)踐,探討 Flutter 架構(gòu)的實(shí)現(xiàn)原理,有何優(yōu)勢,以及如何幫助我們解決問題。
Flutter 架構(gòu)和實(shí)現(xiàn)原理
Flutter 使用 Dart 語言開發(fā),主要有以下幾點(diǎn)原因:
- Dart 一般情況下是運(yùn)行 DartVM 上,但是也可以編譯為 ARM 代碼直接運(yùn)行在硬件上。
- Dart 同時(shí)支持 AOT 和 JIT 兩種編譯方式,可以更好的提高開發(fā)以及 App 的執(zhí)行效率。
- Dart 可以利用獨(dú)特的隔離區(qū)(Isolate)實(shí)現(xiàn)多線程。而且不共享內(nèi)存,可以實(shí)現(xiàn)無鎖快速分配。
- 分代垃圾回收,非常適合 UI 框架中常見的大量 Widgets 對象創(chuàng)建和銷毀的優(yōu)化。
- 在為創(chuàng)建的對象分配內(nèi)存時(shí),Dart 是在現(xiàn)有的堆上移動(dòng)指針,保證內(nèi)存的增長是程線性的,于是就省了查找可用內(nèi)存的過程。
Dart 主要由 Google 負(fù)責(zé)開發(fā)和維護(hù)。目前 Dart 新版本已經(jīng)是 2.2,針對 App 和 Web 開發(fā)做了很多優(yōu)化。并且對于大多數(shù)的開發(fā)者而言,Dart 的學(xué)習(xí)成本非常低。
Flutter 架構(gòu)也是采用的分層設(shè)計(jì)。從下到上依次為:Embedder(嵌入器)、Engine、Framework。
圖1: Flutter 分層架構(gòu)圖
Embedder 是嵌入層,做好這一層的適配 Flutter 基本可以嵌入到任何平臺上去;
Engine 層主要包含 Skia、Dart 和 Text。Skia 是開源的二位圖形庫;Dart 部分主要包括 runtime、Garbage Collection、編譯模式支持等;Text 是文本渲染。
Framework 在最上層。我們的應(yīng)用圍繞 Framework 層來構(gòu)建,因此也是本文要介紹的重點(diǎn)。
Framework
1.【Foundation】在底層,主要定義底層工具類和方法,以提供給其他層使用。
2.【Animation】是動(dòng)畫相關(guān)的類,可以基于此創(chuàng)建補(bǔ)間動(dòng)畫(Tween Animation)和物理原理動(dòng)畫(Physics-based Animation),類似 Android 的 ValueAnimator 和 iOS 的 Core Animation。
3.【Painting】封裝了 Flutter Engine 提供的繪制接口,例如繪制縮放圖像、插值生成陰影、繪制盒模型邊框等。
4.【Gesture】提供處理手勢識別和交互的功能。
5.【Rendering】是框架中的渲染庫??丶匿秩局饕ㄈ齻€(gè)階段:布局(Layout)、繪制(Paint)、合成(Composite)。
從下圖可以看到,F(xiàn)lutter 流水線包括 7 個(gè)步驟。
圖2: Flutter 流水線
首先是獲取到用戶的操作,然后你的應(yīng)用會因此顯示一些動(dòng)畫,接著 Flutter 開始構(gòu)建 Widget 對象。
Widget 對象構(gòu)建完成后進(jìn)入渲染階段,這個(gè)階段主要包括三步:
- 布局元素:決定頁面元素在屏幕上的位置和大小;
- 繪制階段:將頁面元素繪制成它們應(yīng)有的樣式;
- 合成階段:按照繪制規(guī)則將之前兩個(gè)步驟的產(chǎn)物組合在一起。
末尾的光柵化由 Engine 層來完成。
在渲染階段,控件樹(widget)會轉(zhuǎn)換成對應(yīng)的渲染對象(RenderObject)樹,在 Rendering 層進(jìn)行布局和繪制。
在布局時(shí) Flutter 深度優(yōu)先遍歷渲染對象樹。數(shù)據(jù)流的傳遞方式是從上到下傳遞約束,從下到上傳遞大小。也就是說,父節(jié)點(diǎn)會將自己的約束傳遞給子節(jié)點(diǎn),子節(jié)點(diǎn)根據(jù)接收到的約束來計(jì)算自己的大小,然后將自己的尺寸返回給父節(jié)點(diǎn)。整個(gè)過程中,位置信息由父節(jié)點(diǎn)來控制,子節(jié)點(diǎn)并不關(guān)心自己所在的位置,而父節(jié)點(diǎn)也不關(guān)心子節(jié)點(diǎn)具體長什么樣子。
圖3: 數(shù)據(jù)流傳遞方式
為了防止因子節(jié)點(diǎn)發(fā)生變化而導(dǎo)致的整個(gè)控件樹重繪,F(xiàn)lutter 加入了一個(gè)機(jī)制——Relayout Boundary,在一些特定的情形下Relayout Boundary會被自動(dòng)創(chuàng)建,不需要開發(fā)者手動(dòng)添加。
例如,控件被設(shè)置了固定大小(tight constraint)、控件忽略所有子視圖尺寸對自己的影響、控件自動(dòng)占滿父控件所提供的空間等等。很好理解,就是控件大小不會影響其他控件時(shí),就沒必要重新布局整個(gè)控件樹。有了這個(gè)機(jī)制后,無論子樹發(fā)生什么樣的變化,處理范圍都只在子樹上。
圖4: Relayout Boundary 機(jī)制
在確定每個(gè)空間的位置和大小之后,就進(jìn)入繪制階段。繪制節(jié)點(diǎn)的時(shí)候也是深度遍歷繪制節(jié)點(diǎn)樹,然后把不同的 RenderObject 繪制到不同的圖層上。
這時(shí)有可能出現(xiàn)一種特殊情況,如下圖所示節(jié)點(diǎn) 2 在繪制子節(jié)點(diǎn) 4 時(shí),由于其節(jié)點(diǎn)4需要單獨(dú)繪制到一個(gè)圖層上(如 video),因此綠色的圖層上面多了個(gè)黃顏色的圖層。之后再需要繪制其他內(nèi)容(標(biāo)記 5)就需要再增加一個(gè)圖層(紅色)。再接下來要繪制節(jié)點(diǎn) 1 的右子樹(標(biāo)記 6),也會被繪制到紅色的圖層上。所以如果 2 號節(jié)點(diǎn)發(fā)生改變就會改變紅色的圖層上的內(nèi)容,因此也影響到了毫不相干的 6 號節(jié)點(diǎn)。
圖5: 繪制節(jié)點(diǎn)與圖層的關(guān)系
為了避免這種情況,F(xiàn)lutter 的設(shè)計(jì)者這里基于 Relayout Boundary 的思想增加了 Repaint Boundary。在繪制頁面時(shí)候如果遇見 Repaint Boundary 就會強(qiáng)制切換圖層。
如下圖所示,在從上到下遍歷控件樹遇到 Repaint Boundary 會重新繪制到新的圖層(深藍(lán)色),在從下到上返回的時(shí)候又遇到 Repaint Boundary,于是又增加一個(gè)新的圖層(淺藍(lán)色)。
圖6: Repaint Boundary 機(jī)制
這樣,即使發(fā)生重繪也不會對其他子樹產(chǎn)生影響。比如在 Scrollview 上,當(dāng)滾動(dòng)的時(shí)候發(fā)生內(nèi)容重繪,如果在 Scrollview 以外的地方不需要重繪就可以使用 Repaint Boundary。Repaint Boundary 并不會像 Relayout Boundary 一樣自動(dòng)生成,而是需要我們自己來加入到控件樹中。
6.【W(wǎng)idget】控件層。所有控件的基類都是 Widget,Widget 的數(shù)據(jù)都是只讀的, 不能改變。所以每次需要更新頁面時(shí)都需要重新創(chuàng)建一個(gè)新的控件樹。每一個(gè) Widget 會通過一個(gè) RenderObjectElement 對應(yīng)到一個(gè)渲染節(jié)點(diǎn)(RenderObject),可以簡單理解為 Widget 中只存儲了頁面元素的信息,而真正負(fù)責(zé)布局、渲染的是 RenderObject。
在頁面更新重新生成控件樹時(shí),RenderObjectElement 樹會盡量保持重用。由于 RenderObjectElement 持有對應(yīng)的 RenderObject,所有 RenderObject 樹也會盡可能的被重用。如圖所示就是三棵樹之間的關(guān)系。在這張圖里我們把形狀當(dāng)做渲染節(jié)點(diǎn)的類型,顏色是它的屬性,即形狀不同就是不同的渲染節(jié)點(diǎn),而顏色不同只是同一對象的屬性的不同。
圖7: Widget、Element 和 Render 之間的關(guān)系
如果想把方形的顏色換成黃顏色,將圓形的顏色變成紅色,由于控件是不能被修改的,需要重新生成兩個(gè)新的控件 Rectangle yellow 和 Circle red。由于只是修改了顏色屬性,所以 Element 和 RenderObject 都被重用,而之前的控件樹會被釋放回收。
圖8: 示例
那么如果把紅色圓形變成三角形又會怎樣呢?由于這里發(fā)生變化的是類型,所以對應(yīng)的 Element 節(jié)點(diǎn)和 RenderObject 節(jié)點(diǎn)都需要重新創(chuàng)建。但是由于黃顏色方形沒有發(fā)生改變,所以其對應(yīng)的 Element 節(jié)點(diǎn)和 RenderObject 節(jié)點(diǎn)沒有發(fā)生變化。
圖9: 示例
7. 然后是【Material】 & 【Cupertino】,這是在 Widget 層之上框架為開發(fā)者提供的基于兩套設(shè)計(jì)語言實(shí)現(xiàn)的 UI 控件,可以幫助我們的 App 在不同平臺上提供接近原生的用戶體驗(yàn)。
Flutter 在馬蜂窩商家端
App 中的應(yīng)用實(shí)踐
圖10: 馬蜂窩商家端使用 Flutter 開發(fā)的頁面
開發(fā)方式:Flutter + Native
由于商家端已經(jīng)是一款成熟的 App,不可能創(chuàng)建一個(gè)新的 Flutter 工程全部重新開發(fā),因此我們選擇 Native 與 Flutter 混編的方案來實(shí)現(xiàn)。
在了解 Native 與 Flutter 混編方案前,首先我們需要了解在 Flutter 工程中,通常有以下 4 種工程類型:
1. Flutter Application
標(biāo)準(zhǔn)的 Flutter App 工程,包含標(biāo)準(zhǔn)的 Dart 層與 Native 平臺層。
2. Flutter Module
Flutter 組件工程,僅包含 Dart 層實(shí)現(xiàn),Native 平臺層子工程為通過 Flutter 自動(dòng)生成的隱藏工程(.ios / .android)。
3. Flutter Plugin
Flutter 平臺插件工程,包含 Dart 層與 Native 平臺層的實(shí)現(xiàn)。
4. Flutter Package
Flutter 純 Dart 插件工程,僅包含 Dart 層的實(shí)現(xiàn),往往定義一些公共 Widget。
了解了 Flutter 工程類型后,我們來看下官方提供的一種混編方案(https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps),即在現(xiàn)有工程下創(chuàng)建 Flutter Module 工程,以本地依賴的方式集成到現(xiàn)有的 Native 工程中。
官方集成方案(以 iOS 為例)
a. 在工程目錄創(chuàng)建 FlutterModule,創(chuàng)建后,工程目錄大致如下:
b. 在 Podfile 文件中添加以下代碼:
- flutter_application_path = '../flutter_Moudule/'
- eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
該腳本主要負(fù)責(zé):
- pod 引入 Flutter.Framework 以及 FlutterPluginRegistrant 注冊入口
- pod 引入 Flutter 第三方 plugin
- 在每一個(gè) pod 庫的配置文件中寫入對 Generated.xcconfig 文件的導(dǎo)入
- 修改 pod 庫的 ENABLE_BITCODE = NO(因?yàn)?Flutter 現(xiàn)在不支持 bitcode)
c. 在 iOS 構(gòu)建階段 Build Phases 中注入構(gòu)建時(shí)需要執(zhí)行的 xcode_backend.sh (位于 FlutterSDK/packages/flutter_tools/bin) 腳本:
- "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
- "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
該腳本主要負(fù)責(zé):
- 構(gòu)建 App.framework 以及 Flutter.framework 產(chǎn)物
- 根據(jù)編譯模式(debug/profile/release)導(dǎo)入對應(yīng)的產(chǎn)物
- 編譯 flutter_asset 資源
- 把以上產(chǎn)物 copy 到對應(yīng)的構(gòu)建產(chǎn)物中
d. 與 Native 通信
- 方案一:改造 AppDelegate 繼承自 FlutterAppDelegate
- 方案二:AppDelegate 實(shí)現(xiàn) FlutterAppLifeCycleProvider 協(xié)議,生命周期由 FlutterPluginAppLifeCycleDelegate 傳遞給 Flutter
以上就是官方提供的集成方案。我們最終沒有選擇此方案的原因,是它直接依賴于 FlutterModule 工程以及 Flutter 環(huán)境,使 Native 開發(fā)同學(xué)無法脫離 Flutter 環(huán)境開發(fā),影響正常的開發(fā)流程,團(tuán)隊(duì)合作成本較大;而且會影響正常的打包流程。(目前 Flutter 團(tuán)隊(duì)正在重構(gòu)嵌入 Native 工程的方式)
最終我們選擇另一種方案來解決以上的問題:遠(yuǎn)端依賴產(chǎn)物。
圖11 :遠(yuǎn)端依賴產(chǎn)物
iOS 集成方案
通過對官方混編方案的研究,我們了解到 iOS 工程最終依賴的其實(shí)是 FlutterModule 工程構(gòu)建出的產(chǎn)物(Framework,Asset,Plugin),只需將產(chǎn)物導(dǎo)出并 push 到遠(yuǎn)端倉庫,iOS 工程通過遠(yuǎn)端依賴產(chǎn)物即可。
依賴產(chǎn)物目錄結(jié)構(gòu)如下:
- App.framework : Flutter 工程產(chǎn)物(包含 Flutter 工程的代碼,Debug 模式下它是個(gè)空殼,代碼在 flutter_assets 中)。
- Flutter.framework: Flutter 引擎庫。與編譯模式(debug/profile/release)以及 CPU 架構(gòu)(arm*, i386, x86_64)相匹配。
- lib*.a & .h 頭文件 : FlutterPlugin 靜態(tài)庫(包含在 iOS 端的實(shí)現(xiàn))。
- flutter_assets: 包含 Flutter 工程字體,圖片等資源。在 Flutter1.2 版本中,被打包到 App.framework 中。
Android 集成方案
Android Nativite 集成是通過 Gradle 遠(yuǎn)程依賴 Flutter 工程產(chǎn)物的方式完成的,以下是具體的集成流程。
a.創(chuàng)建 Flutter 標(biāo)準(zhǔn)工程
- $ flutter create flutter_demo
默認(rèn)使用 Java 代碼,如果增加 Kotlin 支持,使用如下命令:
- $ flutter create -a kotlin flutter_demo
b. 修改工程的默認(rèn)配置
- 修改 app module 工程的 build.gradle 配置 apply plugin: 'com.android.application' => apply plugin: 'com.android.library',并移除 applicationId 配置
- 修改 root 工程的 build.gradle 配置
在集成過程中 Flutter 依賴了三方 Plugins 后,遇到 Plugins 的代碼沒有被打進(jìn) Library 中的問題。通過以下配置解決(這種方式略顯粗暴,后續(xù)的優(yōu)化方案正在調(diào)研)。
- subprojects {
- project.buildDir = "${rootProject.buildDir}/app"
- }
- app module 增加 maven 打包配置
c. 生成 Android Flutter 產(chǎn)物
- $ cd android
- $ ./gradlew uploadArchives
官方默認(rèn)的構(gòu)建腳本在 Flutter 1.0.0 版本存在 Bug——最終的產(chǎn)物中會缺少 flutter_shared/icudtl.dat 文件,導(dǎo)致 App Crash。目前的解決方式是將這個(gè)文件復(fù)制到工程的 assets 下( 在 Flutter 最新 1.2.1 版本中這個(gè) Bug 已被修復(fù),但是 1.2.1 版本又出現(xiàn)了一個(gè) UI 渲染的問題,所以只能繼續(xù)使用 1.0.0 版本)。
d. Android Native 平臺工程集成,增加下面依賴配置即可,不會影響 Native 平臺開發(fā)的同學(xué)
- implementation 'com.mfw.app:MerchantFlutter:0.0.5-beta'
Flutter 和 iOS、Android 的交互
使用平臺通道(Platform Channels)在 Flutter 工程和宿主(Native 工程)之間傳遞消息,主要是通過 MethodChannel 進(jìn)行方法的調(diào)用,如下圖所示:
圖12 :Flutter與iOS、Android交互
為了確保用戶界面不會掛起,消息和響應(yīng)是異步傳遞的,需要用 async 修飾方法,await 修飾調(diào)用語句。Flutter 工程和宿主工程通過在 Channel 構(gòu)造函數(shù)中傳遞 Channel 名稱進(jìn)行關(guān)聯(lián)。單個(gè)應(yīng)用中使用的所有 Channel 名稱必須是唯一的; 可以在 Channel 名稱前加一個(gè)唯一的「域名前綴」。
Flutter 與 Native 性能對比
我們分別使用 Native 和 Flutter 開發(fā)了兩個(gè)列表頁,以下是頁面效果和性能對比:
iOS 對比(機(jī)型 6P 系統(tǒng) 10.3.3):
Flutter 頁面:
iOS Native 頁面:
可以看到,從使用和直觀感受都沒有太大的差別。于是我們采集了一些其他方面的數(shù)據(jù)。
Flutter 頁面:
iOS Native 頁面:
另外我們還對比了商家端接入 Flutter 前后包體積的大小:39Mb → 44MB
在 iOS 機(jī)型上,流暢度上沒有什么差異。從數(shù)值上來看,F(xiàn)lutter 在 內(nèi)存跟 GPU/CPU 使用率上比原生略高。 Demo 中并沒有對 Flutter 做更多的優(yōu)化,可以看出 Flutter 整體來說還是可以做出接近于原生的頁面。
下面是 Flutter 與 Android 的性能對比。
Flutter 頁面:
Android Native 頁面:
從以上兩張對比圖可以看出,不考慮其他因素,單純從性能角度來說, 原生要優(yōu)于 Flutter,但是差距并不大,而且 Flutter 具有的跨平臺開發(fā)和熱重載等特點(diǎn)極大地節(jié)省了開發(fā)效率。并且,未來的熱修復(fù)特性更是值得期待。
混合棧管理
首先先介紹下 Flutter 路由的管理:
- Flutter 管理頁面有兩個(gè)概念:Route 和 Navigator。
- Navigator 是一個(gè)路由管理的 Widget(Flutter 中萬物皆 Widget),它通過一個(gè)棧來管理一個(gè)路由 Widget 集合。通常當(dāng)前屏幕顯示的頁面就是棧頂?shù)穆酚伞?/li>
- 路由 (Route) 在移動(dòng)開發(fā)中通常指頁面(Page),這跟 web 開發(fā)中單頁應(yīng)用的 Route 概念意義是相同的,Route 在 Android 中通常指一個(gè) Activity,在 iOS 中指一個(gè) ViewController。所謂路由管理,就是管理頁面之間如何跳轉(zhuǎn),通常也可被稱為導(dǎo)航管理。這和原生開發(fā)類似,無論是 Android 還是 iOS,導(dǎo)航管理都會維護(hù)一個(gè)路由棧,路由入棧 (push) 操作對應(yīng)打開一個(gè)新頁面,路由出棧 (pop) 操作對應(yīng)頁面關(guān)閉操作,而路由管理主要是指如何來管理路由棧。
圖14 :Flutter 路由管理
如果是純 Flutter 工程,頁面棧無需我們進(jìn)行管理,但是引入到 Native 工程內(nèi),就需要考慮如何管理混合棧。并且需要解決以下幾個(gè)問題:
1. 保證 Flutter 頁面與 Native 頁面之間的跳轉(zhuǎn)從用戶體驗(yàn)上沒有任何差異
2. 頁面資源化(馬蜂窩特有的業(yè)務(wù)邏輯)
3. 保證生命周期完整性,處理相關(guān)打點(diǎn)事件上報(bào)
4. 資源性能問題
參考了業(yè)界內(nèi)的解決方法,以及項(xiàng)目自身的實(shí)際場景,我們選擇類似于 H5 在 Navite 中嵌入的方式,統(tǒng)一通過 openURL 跳轉(zhuǎn)到一個(gè) Native 頁面(FlutterContainerVC),Native 頁面通過 addChildViewController 方式添加 FlutterViewController(負(fù)責(zé) Flutter 頁面渲染),同時(shí)通過 channel 同步 Native 頁面與 Flutter 頁面。
- 每一次的 push/pop 由 Native 發(fā)起,同時(shí)通過 channel 保持 Native 與 Flutter 頁面同步——在 Native 中跳轉(zhuǎn) Flutter 頁面與跳轉(zhuǎn)原生無差異
- 一個(gè) Flutter 頁面對應(yīng)一個(gè) Native 頁面(FlutterContainerVC) ——解決頁面資源化
- FlutterContainerVC 通過 addChildViewController 對單例 FlutterViewController 進(jìn)行復(fù)用——保證生命周期完整性,處理相關(guān)打點(diǎn)事件上報(bào)
- 由于每一個(gè) FlutterViewController(提供 Flutter 視圖的實(shí)現(xiàn))會啟動(dòng)三個(gè)線程,分別是 UI 線程、GPU 線程和 IO 線程,使用單例 FlutterViewController 可以減少對資源的占用——解決資源性能問題
Flutter 應(yīng)用總結(jié)
Flutter 一經(jīng)發(fā)布就很受關(guān)注,除了 iOS 和 Android 的開發(fā)者,很多前端工程師也都非??春?Flutter 未來的發(fā)展前景。相信也有很多公司的團(tuán)隊(duì)已經(jīng)投入到研究和實(shí)踐中了。不過 Flutter 也有很多不足的地方,值得我們注意:
- 雖然 1.2 版本已經(jīng)發(fā)布,但是目前沒有達(dá)到完全穩(wěn)定狀態(tài),1.2 發(fā)布完了就出現(xiàn)了控件渲染的問題。加上 Dart 語言生態(tài)小,學(xué)習(xí)資料可能不夠豐富。
- 關(guān)于動(dòng)態(tài)化的支持,目前 Flutter 還不支持線上動(dòng)態(tài)性。如果要在 Android 上實(shí)現(xiàn)動(dòng)態(tài)性相對容易些,iOS 由于審核原因要實(shí)現(xiàn)動(dòng)態(tài)性可能成本很高。
- Flutter 中目前拿來就用的能力只有 UI 控件和 Dart 本身提供能力,對于平臺級別的能力還需要通過 channel 的方式來擴(kuò)展。
- 已有工程遷移比較復(fù)雜,以前沉淀的 UI 控件,需要重新再實(shí)現(xiàn)一套。
- 還有一點(diǎn)比較有爭議,F(xiàn)lutter 不會從程序中拆分出額外的模板或布局語言,如 JSX 或 XM L,也不需要單獨(dú)的可視布局工具。有的人認(rèn)為配合 HotReload 功能使用非常方便,但我們發(fā)現(xiàn)這樣代碼會有非常多的嵌套,閱讀起來有些吃力。
目前阿里的閑魚開發(fā)團(tuán)隊(duì)已經(jīng)將 Flutter 用于大型實(shí)踐,并應(yīng)用在了比較重要的場景(如產(chǎn)品詳情頁),為后來者提供了良好的借鑒。馬蜂窩的移動(dòng)客戶端團(tuán)隊(duì)關(guān)于 Flutter 的探索才剛剛起步,前面還有很多的問題需要我們一點(diǎn)一點(diǎn)去解決。不過無論從 Google 對其的重視程度,還是我們從實(shí)踐中看到的這些優(yōu)點(diǎn),都讓我們對 Flutter 充滿信心,也希望在未來我們可以利用它創(chuàng)造更多的價(jià)值和奇跡。
路途雖遠(yuǎn),猶可期許。
參考文獻(xiàn):
Flutter's Layered Design
https://www.youtube.com/watch?v=dkyY9WCGMi0
Flutter's Rendering Pipeline
https://www.youtube.com/watch?v=UUfXWzp0-DU&t=1955s
Flutter原理與美團(tuán)的實(shí)踐https://juejin.im/post/5b6d59476fb9a04fe91aa778#comment
(題圖來源:網(wǎng)絡(luò))
【本文是51CTO專欄作者馬蜂窩技術(shù)的原創(chuàng)文章,作者微信公眾號馬蜂窩技術(shù)(ID:mfwtech)】