闲鱼对Flutter-Native混合工程解耦的探索
簡介:?分手快樂,祝你快樂~
作者:祈晴
1. 閑魚Flutter現狀
閑魚是第一個使用Flutter混合開發的大型應用,但閑魚客戶端開發最深入體會的痛點就是編譯時長影響開發體驗。在Flutter+Native這種開發模式下,Native編譯速度慢,模塊開發無法突破。閑魚集成了集團眾多中間件,很多功能無法通過flutter直接調用,需使用各種channel到native去調用對應功能。總而言之,閑魚目前Flutter開發面臨如下幾個痛點:
- Flutter側混合編譯速度慢,Android首次編譯10min+,iOS首次編譯20min+;
- 混合棧編程中歷史包袱導致IOS/Android雙端返回給Flutter側的數據可能存在不一致性;
- 集成模塊開發效率相比模塊開發較低,單模塊頁面測試性能數據無法展開;
2.解決方案一
2.1方案概述
此項目從立項至今已經很長一段時間,由于業務迭代快,native插件滿天飛情況下,想要做到工程模塊化拆分難度可想而知;如下圖是項目立項為模塊化拆分,業務方需要將各個業務拆分解耦合,拆分集團中間件,業務封裝組件,Native業務代碼,Flutter橋代碼,Flutter組件庫,Flutter側業務代碼等多個模塊;項目初衷就是整理代碼,提供一個Flutter可運行的干凈環境,同時需要讓flutter可以獲取到native幾乎所有能力,但是編譯開發調試時候有想要速度快,效率高。能想到的最直接解決方案就是拆包,從0-1建立一個最小殼工程,然后拆分集團基本中間件,封裝業務組件,Flutter插件等,如下是整個項目架構:
日常模塊化單頁面級需要使用最小殼工程,其內部又channel的聲明和實現,通過運行最小殼工程運行得到結果,Flutter側模塊開發通過IOC調用到最小殼工程的channel得到返回結果,最后將模塊化開發以一種pub或者git依賴方式集成到閑魚FWN主工程即可;
2.2 階段性產出
業務模塊化拆分從來都是一種吃力不討好的活,明知道拆出來有收益,但是投入產出比不足,因此歷史包袱代碼越來越厚重,以至于下一個接收的人都不敢輕易修改代碼;在模塊化拆分時候,開始項目時候提出過新起一個干凈的工程,然后一步步拆分集團中間件,期間拆出了Mtop/Login/FlutterBoost/UI Plugin,耗時3周/2人,得到部分結果就是新業務,新界面開發滿足基本快速迭代開發,缺點也很明顯如下所示:
- 拆分梳理Native的中間件繁瑣,工作量巨大,最小化殼工程耗時3周/2人
- 推動業務方拆分基礎組件庫更難,目前項目進展不順
- 維護成本高,拆分殼工程運行結果和主工程可能不一致
- 業務迫切其結果,但投入產出比不足,比如Flutter單頁面性能測試,Flutter側模塊化拆分,Fass工程一體基石
3.解決方案二
3.1 換位思考
(1)若自己是業務方,需要為Flutter側去拆分包,去構建一個最小化殼工程,其成本是巨大的。
(2)Fass工程一體化依賴一個最小化殼工程的Native運行環境去運行Flutter側代碼,可是并非所有的業務方都會提供一個最小化殼工程去運行Fass,那么Fass工程一體化/模塊開發如果在集團其他運行環境下進展?
(3)最小化殼工程運行環境無法緊跟Native側的各種版本,會導致運行結果不一致情況下也不敢隨便使用;
如果解決此問題呢?個人提出過跨進程實現方式,在Android端側跨進程調用實現方式一直很常見的場景,client訪問server得結果,而Flutter側和Native側不就是client和server雙端么?如下圖所示,其實Flutter獲取數據就是通過MethodChannel/EventChannel獲取,因此可以換一種方式思考?
3.2 IPC跨進程通信,Android Binder
期間在Android側我使用過Android Binder去實現,新起一個APP做為殼工程,其內部實現了各種插件去訪問主工程服務,獲取結果然后返回給殼工程的Flutter調用,但是維護成本依然在;同時iOS側沒有對應的實現機制,因此此方式被拋棄;
3.3 具體方案:Hook代理+Socket服務
Android開發應該都熟悉hook和插件化技術,其實從之前的Flutter到Native的Chanel架構就可以想到一種思路,既然解決不了Native問題,那就解決Channel的問題吧,Native端側的IPC方式無法實現,換到Flutter側和Native側的Channel通信側去實現IPC吧。參考業務對于插件化hook機制/IPC機制的理解,結合自身對于flutter channel的理解,可以實現一種利用socket服務去hook method channel和event channel實現方式,去代理客戶端的method channel和event channel,將處理結果通過socket交給服務端去處理拿到服務端真正的method channel和event channel數據即可,這才是我心中想要的實現方式就是如此,整個架構圖如下:
客戶端是一臺手機,服務端也是一臺手機,服務端跑閑魚FWN主工程,客戶端跑一個干凈的Flutter工程;客戶端先通過Flutter側代碼去找使用本端有對應的Channel,如果有則使用返回結果,如果沒有則通過Socket請求結果到服務端主工程上,主工程根據Socket定義的協議字段去解析然后發起一個channel拿結果,之后通過socket將解決返回給客戶端,客戶端拿到了socket結果數據后執行想要的渲染方式即可;
或許你有質疑點:比如為什么要用2臺手機,使用一臺不可以么?
這里我推薦使用2臺手機有如下2個原因:
(1)一臺手機運行2個APP,如果server在后臺可能會導致進程資源被回收,Socket通信中斷;
(2)使用2臺手機有一個極大好處是,你運行Android的Flutter側Client代碼,但是往往你需要驗證Native側雙端Server代碼數據,如果客戶端手機/服務端手機是2臺,只需要改下客戶端的IP地址去請求Android手機的Server還是IOS手機的Server就可以驗證結果;
3.4 嘗試驗證
比如如下的method channel代碼如下:
Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {assert(method != null);final ByteData result = await binaryMessenger.send(name,codec.encodeMethodCall(MethodCall(method, arguments)),);if (result == null) {throw MissingPluginException('No implementation found for method $method on channel $name');}final T typedResult = codec.decodeEnvelope(result);return typedResult;}修復result == null的場景,如果是我們指定的客戶端,則通過socket去拿server數據,重點理解Fish MOD:START到Fish MOD:END代碼思想就理解了;
Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {assert(method != null);final ByteData result = await binaryMessenger.send(name,codec.encodeMethodCall(MethodCall(method, arguments)),);if (result == null) {//Fish MOD:START//throw MissingPluginException(// 'No implementation found for method $method on channel $name');//socket從服務端手機獲取值final dynamic serverData =await SocketClient.methodDataForClient(clientParams);//Fish MOD:END}final T typedResult = codec.decodeEnvelope(result);return typedResult;}最后通過此中方式驗證了MethodChannel/EventChannel數據正常收發的可行性,后續還需要在業務場景具體實驗耕田;
4.結果對比和展望
結果對比:
無法方案1和方案2最終都可以解決編譯運行時長的問題,但方案1在拆分模塊和維護模塊時候都有很高的成本,運行時長雖然降低了,但是模塊化工作量卻加大很多,方案2可以完美解決拆分成本和維護成本,但是不足之處就是運行環境苛刻,可操作性不足,其需要2部手機作為運行環境,另針對于一些頁面跳轉邏輯,可能客戶端手機A觸發到服務端手機B上,操作性不在同一臺手機上;當然方案二雖然有一定缺陷,卻可以解決很多問題,因此后續在閑魚模塊化拆分落地項目中,在思考是否有更加完美的解決方法。
?
?
原文鏈接
本文為阿里云原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的闲鱼对Flutter-Native混合工程解耦的探索的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云上安全保护伞--SLS威胁情报集成实战
- 下一篇: Flink 1.12 资源管理新特性回顾