Flutter新銳專家之路:工程研發體系篇

閒魚技術發表於2018-08-06

作者:閒魚技術-正物

寫在前面

當前,閒魚客戶端已經實現了基於Flutter的商品詳情頁的全量重構,線上效果良好。從alpha一路走來,我們遇到了很多問題,或基於原理,或透過社群,或與官方合作,都一個個解決了,是時候梳理和總結下,也希望為其他的開發者們,尤其是已有工程中引入Flutter(混合場景)實現漸進式重構帶來啟發和幫助。 鑑於存在多個問題一個原因或解法的情況,而本系列的重點在於說明各種問題的解決方案與思路,就不一一列出問題。所有除錯/熱過載相關的Flutter均為Debug模式的Flutter,不再特殊說明。

本系列文章包含三篇:引入篇,執行篇,上線篇。引入篇重點介紹工程研發體系;執行篇介紹混合情景下的棧管理與能力補齊等;上線篇介紹相容/穩定性保障及方法。

工程研發體系的關鍵點包括:

a.混合工程下的Flutter研發結構

混合工程中一個全域性視角的的研發結構如何。

b.工程結構

已有的Native工程如何引入Flutter,工程結構如何組織,如何管理Flutter環境,如何去編譯構建,整合打包等。

c.構建優化

這裡主要介紹如何去針對Flutter的工具鏈(flutter_tools,Intellij外掛等)進行除錯與優化。

d.Native啟動下的Flutter除錯

不同於Flutter啟動下的一體化除錯,這種Native啟動(Xcode/Android Studio啟動,或點選圖示開啟應用)下的Flutter除錯,我稱之為分離式除錯。分離式除錯可以簡化flutter_tools帶來的複雜度,提高除錯的穩定性和靈活性。

e.Native啟動下的Flutter熱過載

同d。

f.聯合除錯

即同時除錯Flutter和Android/iOS。

g.持續整合

即混合環境下的Flutter構建與持續整合。

環境說明

本系列使用的環境

#混合工程下的Flutter研發結構

混合工程下的Flutter研發結構

工程結構

這部分的核心邏輯是如何在最小改動已有iOS/Android工程的前提下執行Flutter。我們可以將Flutter部分理解成為一個單獨的模組,通過pod庫(iOS),aar庫(Android)的方式,由CocoaPods和Gradle引入到主工程。

具體的原理與實踐請參見:

深入理解flutter的編譯原理與優化

Flutter混合工程改造實踐

Add Flutter to existing apps

其中,我們將整套Flutter環境作為Git Submodule統一管理,以保證團隊內環境一致,遇到的個性化的問題/需求能夠統一處理。

Flutter Project Structure

構建優化篇

編譯速度的優化(Android)

問題:Android在由Flutter啟動時構建緩慢。

原因:在flutter工具鏈(flutter_tools)的邏輯中,未找到android/app/build.gradle時,會執行gradle build從而執行多個編譯配置的構建,而不是gradle assembleDebug。

解法:重構Android工程,使工程應用Module對應的build.gradle位於android/app下,從而符合flutter_tools的邏輯。

原理:flutter_tools的除錯

a.修改flutter_tools.dart,使之可列印引數

修改flutter_tools列印引數

b.刪除flutter/bin/cache/flutter_tools.stamp使得flutter_tools可以被重建

flutter_tools build principle

c.從flutter執行構建,獲取其入口引數

flutter_tools_arguments_print.png

d.用Intellij(或Android Studio下同)開啟flutter_tools工程,新建Dart Command Line App,並基於步驟c獲得的入參配置"Program arguments"

Dart-Command-Line-App-Flutter_Tools_Debugging

e.開始你的flutter_tools除錯之旅吧‘

flutter_tools_debugger_frame_variables

編譯速度的優化(iOS)

問題:Flutter構建報"Observatory connection never became ready.",造成構建中斷

原因:重構前我們的工程全量編譯時間較長(1000+檔案全量編譯時長>10min?),而Flutter Intellij外掛有個超時邏輯,使得構建中斷。

解法a(下策):定製Flutter Intellij外掛(修改下面程式碼中的超時時間),編譯外掛,並替換Android Studio中的Flutter外掛。更合理的解法是提PR,但這一路基本上都是在馬不停蹄地解決各種產品化中的問題,所以...(最新版本已去除此邏輯)

原理:

前往檢視Flutter Intellij原始碼

出錯邏輯

事實上,我們使用IDE開發Flutter時,有下面的一個邏輯流程:

Flutter workflow code level

解法b(中策):iOS工程的模組拆分和Pod(Framework)化,主工程構建依賴編譯好的Framework,大大加快了構建時間。

原理:模組化+預編譯Framework

解法c(上策):Native視角下的Flutter除錯

原理:Native啟動下,Flutter的除錯與熱過載

Native視角下的Flutter除錯

Flutter啟動下的Flutter的除錯與熱過載邏輯

實際上,當Native工程配置好Flutter支援後,Flutter啟動下做的事情主要有:

a.檢查是否需要重新生成flutter_tools.snapshot。

b.基於pubspec.yaml獲取依賴(pub packages get),並生成外掛描述檔案.flutter-plugins和pubspec.lock。

c.基於Flutter配置(如Framework路徑,Debug/Release模式,是否開啟Dart2等),生成Generated.xcconfig(iOS)和local.properties(Android)。

d.基於gradle和xcodebuild構建應用(Flutter相關構建請參見前文中深入理解flutter的編譯原理與優化)。

e.基於adb和lldb啟動應用。

f.等待應用中Flutter啟動,尋找Observatory埠,通過Dart Debugger連線以便除錯。

g.尋找到埠後同步Hot Reload依賴的檔案,同時透過Daemon監聽命令(如使用者點選外掛按鈕)實現Full Restart或Hot Reload。

換個角度來看,如果我們能夠解決Native啟動下的Dart除錯和Hot Reload,由flutter_tools造成的編譯慢等問題將不是問題,且可解決除錯環境不穩定的情況(如我們的場景下,應用啟動後,僅當使用者點選進入詳情頁面的時候才會啟動Flutter,此時flutter_tools才能去發現Observatory埠,除錯和熱過載,常有不好用的情況)。當從Xcode啟動(或點選桌面圖示啟動,不再重複)包含了Debug模式Flutter內容的iOS(Android Studio啟動Android類似,這裡不再重複)應用時,我們需要關注abcfg。而abc除非flutter_tools或pubspec.yaml或Flutter配置變化等,否則都不需要重新執行。fg則是研發依賴的除錯與熱過載,必須考慮此模式下如何支援。

Native啟動下的Flutter的除錯與熱過載邏輯

a.尋找iOS裝置上Observatory埠

observatory-log-from-xcode

或者命令列通過idevicesyslog獲取,此處涉及到libimobiledevice庫,其包含了idevicesyslog,iproxy等命令。

observatory-log-from-command-line

可以看到iOS裝置上Observatory啟動了一個xxxx的埠(埠號隨機)。

b.透過iproxy將iOS裝置上埠xxxx對映到本機埠yyyy

using-iproxy-to-forward-ios-debug-port

c.可以看到waiting for connection,此時就可以訪問http://127.0.0.1:yyyy/#/vm開啟Observatory如下:

observatory-snapshot

可以使用Observatory去檢查諸多dart相關的記憶體,除錯等,這裡不展開。

也可以通過IDE連結去除錯:

d.配置Dart Remote Debug

dart-remote-debug

這裡需要注意的是埠要使用剛轉發到電腦的埠yyyy,搜尋原始碼路徑是Flutter工程的根目錄。

e.配置好之後點選Debug按鈕,連線到除錯埠

dart-remote-debugger-debug

f.成功後可以看到Debugger顯示Connected(如果沒有顯示,再點選一次綠色的除錯按鈕?‍♀️)

dart-remote-debugger-connected

g.之後便可以正常地使用IDE設定斷點和除錯dart(Flutter)程式碼

dart-debugger-remote-debug-connected-frame-info

#Native視角下的Flutter熱過載

a.啟動App,進入Flutter頁面,查詢Observatory埠xxxx,並轉發到電腦yyyy(同上面ab)

b.在Flutter工程目錄下,執行flutter attach --debug-port=yyyy

flutter-attach-command

c.修改dart原始碼,然後在b中Terminal中輸入r(這一輸入位於上圖中'To quit,press"q"'之後)

flutter-attach-hotreaload-code-changes

這裡我們將超讚文案換成了贊。

d.可以看到Terminal顯示"Initializing hot reload...Reloaded...",結束後,裝置上變更生效(左下角文案變成了贊)

flutter-hot-reload-effected-result

Android下,Native啟動的的Flutter除錯/熱過載類似iOS,不同的是獲取埠時可通過IDE logcat或者adb logcat | grep Observatory,埠轉發使用adb forward。 #Native與Flutter聯調 上文中已經介紹瞭如何在任意時刻(Flutter啟動後)除錯Flutter。此外我們還可以使用Android Studio的Attach Debugger to Android Process來除錯Android,這就實現了Android與Flutter聯調。同樣,結合Xcode的Attach to Process,可以實現iOS與Flutter聯調。

持續整合

目前團隊包括Native同學和Flutter同學,因此我們區分了Flutter模式和Native模式。有一臺公共裝置(Mac Mini)安裝了Flutter環境並負責Flutter相關的構建,構建好的產物以aar(Android)或pod庫(iOS)的形式整合到Native工程下(可以認為Flutter相關的程式碼就是一個模組),用於構建最終產物apk(Android)或ipa(iOS)的CI平臺最終也通過產物方式整合Flutter並打包。

更多細節請參見:

閒魚flutter混合工程持續整合的最佳實踐

寫在後面

本文著重介紹了混合場景下的工程研發體系。解決這一問題後,接下來就要解決實際業務開發中遇到的問題。比如Native與Flutter互相跳轉場景下的棧如何管理,Flutter不能實現的功能(平臺特性等)如何去補全,Flutter Plugin/Dart Package包管理的方式有哪些等,這些敬請關注本系列的執行篇。

聯絡我們

如果對文字的內容有疑問或指正,歡迎告知我們。

閒魚技術團隊是一隻短小精悍的工程技術團隊。我們不僅關注於業務問題的有效解決,同時我們在推動打破技術棧分工限制(android/iOS/Html5/Server 程式設計模型和語言的統一)、計算機視覺技術在移動終端上的前沿實踐工作。作為閒魚技術團隊的軟體工程師,您有機會去展示您所有的才能和勇氣,在整個產品的演進和使用者問題解決中證明技術發展是改變生活方式的動力。

簡歷投遞:guicai.gxy@alibaba-inc.com

相關文章