體驗(yàn) Flutter
從 App Store 下載或更新頭條(6.9.2 或以上版本),找到 懂車帝 -> 熱門車型,點(diǎn)擊打開(kāi)后即可體驗(yàn) Flutter 的頁(yè)面效果延旧。
由于前期業(yè)務(wù)改造順利埋酬,線上 Crash 少,性能良好碘勉,目前我們正在進(jìn)行小視頻模塊的 Flutter 重構(gòu)巷挥,即將上線。
本文主要介紹頭條 iOS 端在接入 Flutter 的過(guò)程中验靡,選擇的技術(shù)方案倍宾,遇到的問(wèn)題和未來(lái)的計(jì)劃雏节。雖然是以 iOS 為例,但很多內(nèi)容都是雙端通用的高职。
Flutter 基礎(chǔ)
為了方便對(duì) Flutter 零基礎(chǔ)讀者閱讀本文钩乍,首先介紹一些 Flutter 的基礎(chǔ)知識(shí)。
當(dāng)我們寫(xiě)完 Dart 代碼后怔锌,需要編譯后才能給客戶端使用寥粹。Flutter 的產(chǎn)物分為兩種模式,一個(gè)是 Debug 模式埃元,采用 JIT(Just In Time)的方式涝涤,類似于解釋型語(yǔ)言,好處是可以支持熱更新岛杀,方便調(diào)試阔拳。另一種是 AOT(Ahead Of Time)模式,類似于編譯型語(yǔ)言类嗤,好處是性能比較好糊肠。
因此在開(kāi)發(fā)中,我們一般使用 Debug 模式的產(chǎn)物來(lái)提升調(diào)試效率遗锣,正式上線后再換成 Release 模式獲得極致的性能货裹。不管是哪種模式,在 iOS 下的產(chǎn)物都是三個(gè)(安卓類似):
- App.framework:和 flutter 的業(yè)務(wù)邏輯相關(guān)精偿,在 Debug 模式下就是一個(gè)很小的空殼弧圆,在 Release 模式下包含全部業(yè)務(wù)邏輯
- flutter_assets:也和 flutter 的業(yè)務(wù)邏輯相關(guān),在 Debug 模式下包含全部業(yè)務(wù)邏輯还最,在 Release 模式下很小墓阀。
- Flutter.framework:實(shí)現(xiàn) Flutter 框架自己的邏輯
iOS 工程接入 Flutter 的已有方案
目前主流的方案中主要有兩種,一種在 Google 的官方文檔里 拓轻,它最大的問(wèn)題在于斯撮, 需要?jiǎng)?chuàng)建一步 BUILD PHASE 調(diào)用 shell 腳本去編譯 Flutter。編譯 Flutter 的過(guò)程需要本機(jī)有 Flutter 環(huán)境才行扶叉。
這顯然是不能接受的勿锅。其實(shí)我們先不考慮 Flutter 這件事,不管是搭建什么類型的混合工程枣氧,都一定要保證以下兩點(diǎn):
- 對(duì)原生工程無(wú)侵入:原生工程可以增加組件的依賴溢十,但不能修改主工程的配置,更不能讓原生工程對(duì)環(huán)境產(chǎn)生新的依賴达吞。
- 方便調(diào)試:不管是原生工程開(kāi)發(fā)张弛,調(diào)試新接入的模塊(比如 Flutter),還是模塊開(kāi)發(fā)調(diào)試原生工程,都應(yīng)該支持?jǐn)帱c(diǎn)調(diào)試吞鸭。
很明顯寺董,官網(wǎng)的方案不滿足第一點(diǎn)要求,我們不能讓每個(gè)參與原生工程開(kāi)發(fā)的同學(xué)刻剥,本機(jī)都安裝 Flutter 環(huán)境遮咖。
從 Flutter 的官網(wǎng)教程 中可以看到,它支持 flutter run
命令或者從 IDE 啟動(dòng) iOS 工程造虏。其實(shí)只要觀察一下 ios 目錄下的 Runner.xcworkspace
工程就會(huì)發(fā)現(xiàn)御吞,F(xiàn)lutter 項(xiàng)目運(yùn)行的就是這個(gè)工程。當(dāng)然如果直接把我們的 iOS 工程拷貝過(guò)來(lái)是無(wú)法運(yùn)行的漓藕。
至少在目前最新的 dev 分支(Tag:v0.10.0)上是不行的陶珠,因?yàn)?flutter 的代碼中寫(xiě)死了工程的名字就是 Runner,可以驗(yàn)證一下:
這也就催生了目前主流的撵术,也是閑魚(yú)最先介紹的方案背率,魔改 Flutter 源碼,讓它能運(yùn)行自己的工程嫩与。然而個(gè)人覺(jué)得這種方式還是太 trick,主要有兩個(gè)問(wèn)題:
- 后續(xù)如果 Flutter 升級(jí)了交排,還得把修改的部分應(yīng)用到新的分支上去划滋,并且修復(fù)沖突。
- 如果公司或者業(yè)內(nèi)有別的團(tuán)隊(duì)要用埃篓,接入成本比較高处坪。
頭條的混合工程方案
觀察一下 Runner 的工程結(jié)構(gòu):
再看一眼 Flutter 編譯腳本就會(huì)發(fā)現(xiàn),這一步并不是必須放在主工程的 BUILD 階段進(jìn)行架专。只要擁有 Flutter 的編譯產(chǎn)物同窘,項(xiàng)目就可以接入 Flutter 的功能了。因此頭條選擇的方案是:
- 利用
flutter build ios
和flutter build ios --debug
命令部脚,先后構(gòu)建兩種模式下的產(chǎn)物并上傳到 CDN - 構(gòu)建一個(gè) Pod想邦,并且讓 Pod 依賴上述產(chǎn)物
在第一步中,運(yùn)行 flutter build ios
實(shí)際上會(huì)依賴 Runner 工程委刘,相當(dāng)于是我們借助這個(gè)空殼去得到產(chǎn)物丧没。不過(guò)要想編譯通過(guò),還需要做如下兩個(gè)微小的改動(dòng):
- 修改項(xiàng)目的 Bundle ID锡移,使用自己的證書(shū)呕童。否則無(wú)法通過(guò)簽名
- 打開(kāi) Xcode -> File -> Workspace -> Setting,把 BUILD_SYSTEM 切換為 Legacy淆珊,否則會(huì)產(chǎn)生編譯錯(cuò)誤
第二步中夺饲,重點(diǎn)在于如何讓 Pod 動(dòng)態(tài)的下載資源,因?yàn)樾枰付ㄊ褂媚膫€(gè)版本,哪種模式的 Flutter 產(chǎn)物往声。我們目前的策略是在打包機(jī)器上使用 Release 模式產(chǎn)物擂找,在開(kāi)發(fā)機(jī)器上使用 Debug 模式產(chǎn)物。
至于動(dòng)態(tài)指定版本號(hào)烁挟,可以這樣寫(xiě):
s.prepare_command = "bash ./BDFlutterDownload.sh"
s.vendored_frameworks = 'Framework/*.framework'
s.resources = 'Framework/flutter_assets'
通過(guò) prepare_command
語(yǔ)法婴洼,可以在 Pod Install 的過(guò)程中執(zhí)行 shell 腳本,把產(chǎn)物下載到 Framework
文件夾中撼嗓,只要把版本號(hào)寫(xiě)到腳本中柬采,并且及時(shí)更新就可以了。
調(diào)試 Flutter
上述方案解決了 Flutter 工程接入的問(wèn)題且警,因?yàn)槭蔷幾g后的資源接入粉捻,我們還需要保證 Flutter 開(kāi)發(fā)的同學(xué)可以正常調(diào)試。這里以最新的 dev 分支舉例(Tag:v0.10.0)斑芜。
如果是 Flutter 開(kāi)發(fā)的同學(xué)肩刃,不關(guān)心 iOS 開(kāi)發(fā)的細(xì)節(jié),可以找到打包后的 app 文件杏头,然后使用 ? flutter run --use-application-binary?
指定被調(diào)試應(yīng)用的路徑:
如果是 iOS 開(kāi)發(fā)的同學(xué)盈包,使用 Xcode 運(yùn)行項(xiàng)目即可。這兩種方式下醇王,都可以看到本地調(diào)試的端口(圖中的 57674)呢燥。
如果是使用編譯好的 app 來(lái)運(yùn)行,可以直接使用 r/R 來(lái)實(shí)現(xiàn)熱刷新/熱重啟寓娩。如果使用 Xcode 運(yùn)行項(xiàng)目叛氨,需要先使用命令: ?flutter attach --debug-port=57674
?。
如果需要在 iOS 側(cè)打斷點(diǎn)棘伴,必須用 Xcode 運(yùn)行項(xiàng)目寞埠。如果需要在 Dart 側(cè)打斷點(diǎn),可以利用 IDE 的 Debug 功能焊夸。這里以 VSCode 為例仁连,首先安裝 Dart 插件。