天天看點

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

作者:閑魚技術——鹿緣

閑魚社群主要頁面采用Native實作,部分使用Flutter和Weex承接。文章、話題等固定資料結構的處理,點贊、評論等使用者互動和狀态同步,這些資料邏輯大部分是重複的,而且在多技術棧實作性價極低。由此我們想,能否在端上實作這樣的一套工具,解放勞動力的同時,擺脫對服務端BFF層的依賴,保證研發效能。

抽象地看,實際上我們需要的正是一個邏輯跨平台工具。邏輯跨平台概念由來已久,也有一些很優秀的方案可以參考:

  • C++語言在Objective-C/Java等Native語言都有成熟接口可以調用,這使得C++有天然的跨平台優勢。同時不可否認的,C++入門門檻會比較高,導緻後期維護成本大。
  • KMM是JetBrains推出的用于跨平台移動開發SDK,提供了一個語言級别的邏輯跨平台解決方案,可以将直接代碼編譯為與目标平台完全相同的格式。

結合團隊内現有的一些技術基建,最終我們使用Dart完成了這一設計,在不同平台保證資料和邏輯處理的一緻性,節約人力資源的同時保證使用者體驗。取名Flutter Worker。

整體架構設計

在進行整體的設計之前,首先限定worker的業務場景:目标是提供一個多端可複用的邏輯進行中心,端上發起資料請求,所有的邏輯處理在FlutterWorker收口,執行完成傳回回調給端上。設想中,在worker寫Handler隻需要關注邏輯處理,對于資料的傳入和在端上的接收,開發者隻需要指定類型,worker會自動把資料轉化成該類型。

由此,在整體的架構設計中,我們主要考慮一下幾個方面:

  1. FlutterWorker需要一個穩定的運作環境,由于涉及到資料處理等可能會比較耗時的操作,需要保證不影響UI界面的繪制。
  2. 處理後的資料要供給多端使用,資料結構必須對齊。
  3. 提供足夠的切面,友善接入方拓展。
  4. 為了保證運作時穩定可靠,需要添加監控及時發現問題,并且在上線前時,對性能進行測試評估。

整體的架構圖如下:

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

運作容器層:容器是worker運作的基礎,在保證性能的前提下,要盡可能的利用閑魚現有的技術基建。

狀态管理層:worker在這裡完成資料的存儲和狀态的同步。端上存儲狀态資料,通過訂閱狀态消息來接收資料的變動。考慮到狀态管理層在目前沒有業務的強需求,這部分僅停留在Demo階段還未落地,後面不再進行闡述。

資料處理層:包含了worker所有的中間層資料處理部分,主要包括Model對象同步、資料轉換和資料類型同步。基礎資料類型在Dart語言中天然支援,在iOS端Androiddr端都有對應的類型,是以,worker工作主要在後兩者。

監控層:worker線上上運作,需要有監控來保障,及時發現問題及時響應。

下面對各個子產品進行詳細的介紹。

運作容器層

FlutterWorker的運作環境強依賴FlutterEngine和Isolate,從這個角度來考慮,對以下三種方案做了梳理和對比。

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

值得一提的是,随着Flutter2.0推出,我們在更新日志中注意到有一項對多開Flutter Engine執行個體記憶體消耗的優化。據官方文檔稱,額外Flutter引擎的靜态記憶體占用量降低了約 99%,使每個執行個體的占用量大約為180KB。結合源碼來看,這裡是新提供了FlutterEngineGroup類來建立Flutter Engine。

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

相較于原來的API,FlutterEngineGroup 生成的FlutterEngine具有常用共享資源(例如 GPU 上下文、字型度量和隔離線程的快照)的性能優勢,進而加快首次渲染的速度、降低延遲并降低記憶體占用。

同時 EngineGroup 本身和其生成的 FlutterEngine不需要持續保活,隻要有 1 個可用的FlutterEngine,就可以随時在各個 FlutterEngine 之間共享資源。EngineGroup銷毀後,已生成的FlutterEngine也不受影響,隻是無法繼續在現有共享的基礎上建立新引擎。

雖然現在FlutterEngineGroup還不是一個穩定API,但是這為FlutterWorker提供了另一種可能。綜合考慮,目前實作選擇了方案3來實作。

資料處理層

從使用者調用開始,worker的資料流圖如下圖所示:

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

資料在三端的流通,是通過調用NatiaveWorker,執行到對應的WorkerHandler,處理完成後再以異步的方式回調到Native方法。中間主要涉及Model對象同步和資料轉換過程,worker對這裡分别實作了處理庫。

Model對象同步

worker首先要實作的是,是OringinDataModel ==> TargetDataModel。TargetDataModel的定義在 iOS/Android/Flutter 三端的表現形式要保持一緻,直接用代碼生成是最好的選擇。這裡參考一些硬體協定,指定IDL格式來生成三端Model并導入工程。封裝的dartGen庫可以解析這些yaml節點,生成我們需要的胖Model。

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

友善開發者使用,dartGen打包成了cli工具。模仿GsonFormat插件,json資料在IDE中可以友善轉成對應的dartGen可以解析的yaml協定。對于其他生成Model的需求,可以以很小的代價修改模闆,定制化高。

資料轉換

通過Flutter的MethodChannel向Native傳輸資料,需要經過一層messager編碼,而且編碼資料為基礎類型。是以在BinaryMessager的基礎上,封裝了WorkerBinaryMessenger友善做定制化處理。

iOS端和Android端有成熟的model轉換庫(YYModel和FastJson)。在Dart這裡,基于fish_serializable同樣做了一些定制處理,在生成的Model裡提供toJson和fromJson方法等,保證各種類型資料,包括Map和List等内包泛型類,能夠很友善地進行轉換。

監控方案設計

每個監控方案都有其業務場景的局限性,在目前階段,主要考慮邏輯一緻性。worker内單次的邏輯閉環類似tcp握手和揮手的過程,應用内的資料流轉類似消息隊列,其傳遞過程如圖:

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

由此我們确定workerMonitor運作的流程圖如下:

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

性能監控

在該模式下的主流監控方案,會根據時間切片來觀察 發起方/接收方的隊列資料情況,時間切片有兩種方式,第一種是設定定時器,第二種是在每次有資料調用時處理。

切片方案 優點 缺點
定時器切片 切片實作簡單,切片内資料精準 性能損耗大,會有很多的無效統計
每次調用切片 性能損耗小

切片内資料為平均值

丢失應用内最後一段切片資料

首段資料阻塞時無法監控

另外上報的方式也分為實時上報和聚合上報。考慮到目前worker的使用場景有閑時和忙時的明顯區分,并且端上的長駐的頻繁的定時上報任務對ui繪制會有一定影響,最終采用每次資料調用時統計隊列内資料,同時在每次調用後增加逾時定時器,如果有下次調用則取消逾時定時器。

名額

線上上監控名額建設方面,參考rabbitmq監控,先定義初期需要觀測的名額。初期核心關注消息丢失的情況和消息時延長的情況。由此我們确定名額如下:

Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

Exception監控

參考ui引擎對crash的監控,會在入口函數處統一接受異常資訊以及異常堆棧,然後統一上報處理。這裡在閑魚公衆号Flutter高可用相關文章中有詳細介紹。

可行性驗證

最後實作完成,調用代碼及對應的Handler示例如下:

  • iOS端:
Flutter Worker —— 閑魚這樣實作“邏輯跨平台”
  • Android端:
Flutter Worker —— 閑魚這樣實作“邏輯跨平台”
  • Handler:
Flutter Worker —— 閑魚這樣實作“邏輯跨平台”

為了測試我們的方案能否達到上線标準,在會玩首頁場景下做暴力測試來驗證。對比正常Native代碼和worker處理資料,測試多次并發通路/有序通路條件下CPU、記憶體以及時延等表現。

通過多次對比試驗,FlutterWorker方案在常見和高壓的請求場景下短期記憶體和cpu水位增量少于30M,長期記憶體和cpu水位增量小于5M,并且時延低于原有方案的5%。

三端接入後,資料處理隻需要投入一個人力就可供多端使用,大量減少了重複工作,提升了研發效能。

總結和展望

目前跨平台是移動開發的趨勢之一,各個公司、團隊各有建樹,百家争鳴。究其根本目的都是想提升研發效率,降低維護成本。各個方案都各有自己的獨特優勢,應該理性分析團隊和業務的現狀,選擇适合的方案。

Flutter Worker從邏輯跨平台的視角切入,取得了一些不錯的效果。不過我們目前隻完成了小部分的工作,仍有很多未完善的地方,在未來主要會從下面幾個方面繼續深入研究:

  1. MethodChannel優化。資料通道的通信強依賴MethodChannel,從目前測試結果來其性能表現可以cover目前的業務場景,但長遠來看,通道會成為worker性能瓶頸。從dart_native庫得到啟發,以類型映射指針的方式來替代原生的通道是一條可行的路。
  2. 單元測試。邏輯處理函數有明确的輸入和輸出,寫單測的效率很高。而且快速疊代的産品,很多是在原有的邏輯基礎上添加新的邏輯,寫單測的成本效益較高。
  3. 前端容器支援。先行版本隻支援了iOS、Android和Flutter,對于Weex和H5前端頁面并沒有很好的支援,未來期望能夠提供優雅的方式接入前端容器。

這是閑魚在Flutter探索道路上做的一點小嘗試,歡迎大家保持關注。