簡介: 分手快樂,祝你快樂~git
做者:祈晴編程
===============segmentfault
閒魚是第一個使用Flutter混合開發的大型應用,但閒魚客戶端開發最深刻體會的痛點就是編譯時長影響開發體驗。在Flutter+Native這種開發模式下,Native編譯速度慢,模塊開發沒法突破。閒魚集成了集團衆多中間件,不少功能沒法經過flutter直接調用,需使用各類channel到native去調用對應功能。總而言之,閒魚目前Flutter開發面臨以下幾個痛點:架構
此項目從立項至今已經很長一段時間,因爲業務迭代快,native插件滿天飛狀況下,想要作到工程模塊化拆分難度可想而知;以下圖是項目立項爲模塊化拆分,業務方須要將各個業務拆分解耦合,拆分集團中間件,業務封裝組件,Native業務代碼,Flutter橋代碼,Flutter組件庫,Flutter側業務代碼等多個模塊;項目初衷就是整理代碼,提供一個Flutter可運行的乾淨環境,同時須要讓flutter能夠獲取到native幾乎全部能力,可是編譯開發調試時候有想要速度快,效率高。能想到的最直接解決方案就是拆包,從0-1創建一個最小殼工程,而後拆分集團基本中間件,封裝業務組件,Flutter插件等,以下是整個項目架構: socket
平常模塊化單頁面級須要使用最小殼工程,其內部又channel的聲明和實現,經過運行最小殼工程運行獲得結果,Flutter側模塊開發經過IOC調用到最小殼工程的channel獲得返回結果,最後將模塊化開發以一種pub或者git依賴方式集成到閒魚FWN主工程便可;async
業務模塊化拆分歷來都是一種吃力不討好的活,明知道拆出來有收益,可是投入產出比不足,所以歷史包袱代碼愈來愈厚重,以致於下一個接收的人都不敢輕易修改代碼;在模塊化拆分時候,開始項目時候提出過新起一個乾淨的工程,而後一步步拆分集團中間件,期間拆出了Mtop/Login/FlutterBoost/UI Plugin,耗時3周/2人,獲得部分結果就是新業務,新界面開發知足基本快速迭代開發,缺點也很明顯以下所示:模塊化
(1)若本身是業務方,須要爲Flutter側去拆分包,去構建一個最小化殼工程,其成本是巨大的。
(2)Fass工程一體化依賴一個最小化殼工程的Native運行環境去運行Flutter側代碼,但是並不是全部的業務方都會提供一個最小化殼工程去運行Fass,那麼Fass工程一體化/模塊開發若是在集團其餘運行環境下進展?
(3)最小化殼工程運行環境沒法緊跟Native側的各類版本,會致使運行結果不一致狀況下也不敢隨便使用;性能
若是解決此問題呢?我的提出過跨進程實現方式,在Android端側跨進程調用實現方式一直很常見的場景,client訪問server得結果,而Flutter側和Native側不就是client和server雙端麼?以下圖所示,其實Flutter獲取數據就是經過MethodChannel/EventChannel獲取,所以能夠換一種方式思考? 測試
期間在Android側我使用過Android Binder去實現,新起一個APP作爲殼工程,其內部實現了各類插件去訪問主工程服務,獲取結果真後返回給殼工程的Flutter調用,可是維護成本依然在;同時iOS側沒有對應的實現機制,所以此方式被拋棄;spa
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就能夠驗證結果;
好比以下的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數據正常收發的可行性,後續還須要在業務場景具體實驗耕田;
結果對比:
沒法方案1和方案2最終均可以解決編譯運行時長的問題,但方案1在拆分模塊和維護模塊時候都有很高的成本,運行時長雖然下降了,可是模塊化工做量卻加大不少,方案2能夠完美解決拆分紅本和維護成本,可是不足之處就是運行環境苛刻,可操做性不足,其須要2部手機做爲運行環境,另針對於一些頁面跳轉邏輯,可能客戶端手機A觸發到服務端手機B上,操做性不在同一臺手機上;固然方案二雖然有必定缺陷,卻能夠解決不少問題,所以後續在閒魚模塊化拆分落地項目中,在思考是否有更加完美的解決方法。