乾貨|Flutter 原理與閒魚深度實踐

王康(正物)—— Flutter 官方成員 阿里巴巴技術專家,之前主要負責 Flutter 在閒魚中的混合開發體系,目前重點關注 Flutter 深入度以及生態相關的工作。本文將分享三方面內容, Flutter 的原理、 Flutter 在閒魚中的應用,最後介紹我們在深度方面的一些探索。

01、Flutter 原理

當我們談到跨平臺框架時,可能會想到很多備選方案。包括早期的 HTML 和 Cordova , 後來的 React Native , Weex ,以及這兩年很是流行的 Flutter ,它們都在不同階段不同程度上解決了我們對跨平臺的訴求。如果我們從一些關鍵指標包括動態性、性能來觀察,他們的區別還比較明顯。

HTML 和 Cordova 具有最好的動態性,但他們的性能卻是最差的,RN / Weex 具有良好的動態性。Flutter 則是一個純原生的設計,其設計使它天生具有很好的性能與跨端一致性。

Flutter 是如何實現優秀的性能和跨端一致性的呢?從設計上可以看出 Flutter 在操作系統之上包含了三個層次。最下面是平臺相關的嵌入層,其向上提供一個 Surface 用以繪製,建立了相關的線程模型和事件循環機制。在此之上則是一個平臺無關的引擎,包括用於繪製的 Skia ;Dart 的運行時,開發模式下包括一個解釋器;還有一部分是文本繪製相關內容。最上面就是用 Dart 語言編寫的 Flutter 框架,也是我們最常接觸到的內容。Flutter 框架包含一個完整分層的 UI 框架,從基礎的 Foundation 庫,到動畫手勢,再到渲染,之上又提供了各種豐富的 Widget 庫。爲了方便開發者使用, Flutter 還提供了兩套不同風格的組件庫,針對安卓的 Material Design 的組件庫和針對 iOS 的 Cupertino 風格的組件庫。

從這個設計可以看出,Flutter 和平臺相關的內容,其實只提供 Surface 和線程/事件循環模型的嵌入層部分。這種類似用遊戲引擎的方式來開發應用的設計很好解釋了爲什麼它具有優秀的跨端一致性。

我們常常說 Flutter 具有這樣的幾個特點:

  • 精美

豐富的 Widget 庫、 Material Design 和 Cupertino 風格的系統庫、組合式的 API 、像素級的控制力可以使開發者便捷地構建精美的應用。

  • 高效開發&執行快速

Dart 語言是爲數不多同時支持 JIT 和 AOT 編譯的語言。開發期使用 JIT 編譯,支持了廣受歡迎的熱重載功能,開發者可以像 PS 圖片一樣來開發應用,開發效率高。發佈後 Flutter 使用 AOT 編譯, Dart 代碼最終被編譯成 ARM 彙編指令,運行快速。

  • 開放

Flutter 是開源項目,其整個的開發,工作流都是完全遵循開源項目的運作來完成的。

02、Flutter 在閒魚中的應用

說完 Flutter 原理,我們來看 Flutter 在閒魚中的應用。在我們的研發中有幾個核心關注的問題。

  • 開發效率

閒魚技術是一個相對較小的團隊,但業務需求比較重,開發人員少需求多,這種情況下效率就非常重要。還有部分原因是 iOS 安卓兩端開發資源不均衡的問題,

  • 用戶體驗

我們的設計要求我們具有很好的跨端一致性,其設計也是同一套風格。

  • 執行性能

不管是複雜的交互還是動畫,都要求其具有不錯的性能。

Flutter 的設計很好地滿足了閒魚業務研發關注的這些問題,這也是我們採用它的原因。

我們怎麼樣讓 Flutter 從無到有地在閒魚中落地與上線?這裏麪包括前期的調研,研發期的混合開發體系,以及如何保質保量在線上運行。

2017 年,我們去接觸和調研 Flutter ,其當時還處在 Alpha 階段。

我們需要去了解它的原理,看它是如何具有所宣稱的諸多優勢;對其性能做測試,看能否滿足要求;包大小的增加怎麼樣,這個量是否可承受;音視頻調的出發點在於業務場景,我們的詳情和發佈頁面包括了很多圖片和視頻;工具鏈上 Flutter的開發體驗如何;我們甚至做了一個 MVP Demo ,這個產品中我們實現了包括髮布,詳情、我的頁面等主要設計與邏輯;此外我們還很關注其社區的成熟度,確保在遇到問題時,可以通過自己對原理的理解或者社區去解決問題。

要使用 Flutter 我們就會面臨一個混合研發體系的問題。其最開始的設計是面向純 Flutter 開發的應用。而我們的應用是在原生項目中嵌入 Flutter ,也就是 Add2App 。事實上在國內,即便是一個純 Flutter 的應用,很多時候,因爲二方、三方庫的原因,也會成爲一個混合工程。通過我們的實踐與影響, Add2App 也變成了 Flutter 演進的重要方向。

工程層面上,我們的團隊中存在兩個視角。一個是傳統的 Native 開發的視角,一個是 Flutter 視角。我們並沒有直接使用 Github 上的 Flutter 項目,主要是因爲可控性的問題。因爲國內的安卓碎片化以及 ROM 中 opengl 的實現不規範,使我們不得不存在一些針對性的處理邏輯,也就是引擎定製。其產物通過定製的 Flutter 最終被 fwn 工程所使用。fwn 工程包含了實際 Flutter 業務,其 Flutter 代碼通過產物集成的方式,通過 pod( iOS ) ,和 gradle ( Android ),最終集成到原生項目中。

混合開發不僅包括工程體系,也包括頁面側的邏輯。我們一開始就涉及到了混合頁面體系,以 Android 爲例簡要介紹下原理。每個 Flutter 頁面對應了一個原生的 BoostFlutterActivity , BoostFlutterActivity 通過各自的 BoostFlutterView 去綁定單例的 FlutterEngine 。當 Flutter 頁面在做切換時 BoostFlutterActivity 也會同步切換,將 FlutterEngine 動態地 Detach 和 Attach 。Native 和 Flutter 之間可以互相關閉和打開頁面, Native 的生命週期也會同步到 Flutter 側。

Flutter 支持 UI 構建以及邏輯實現,但可能我們還需要去獲取 wifi 狀態或電量等系統狀態。

面對這樣的場景, Flutter 提供了很多機制去擴展應用。以獲取電量爲例, Flutter 提供了 Channel 機制用於同 Native 之間的雙向通信。Dart 代碼在 Release 下會變成彙編代碼,直接調用到 Engine ( C++ ),再調用到 OC (直接)或 JAVA (通過 JNI 間接),這種設計下的性能是原生體驗。

Flutter 不僅在邏輯側提供了 Channel 機制去擴展應用,在渲染相關也提供了一些機制去做更多。我們寫的 Flutter 視圖佈局最終會通過 DartRuntime 調用到 Engine 中的 LayerTree->Paint 方法。在這個渲染管線中, LayerTree 會調用到 SkCanva->Draw ,最終通過 PresentRenderBuffer / SwapBuffer 將內容繪製到 GPU 上。LayerTree 中,包含很多種 Layer ,其中有一個特殊的 TextureLayer 可用於擴展。

以 Android 爲例, TextureLayer 可以在 SurfaceTexture 的幫助下,同一個 Surface 相關聯。基於 Surface 可以將視頻播放的內容傳入並完成渲染,或者結合 VirtualDisplay 和 Presentation ,完成 NativeView 的嵌入。需要注意的是,因爲 VirtualDisplay API 的限制,此部分的邏輯需要 API Level 在 20 及其以上。

也就是說 Flutter 提供了 Channel 機制用來擴展系統特性相關的邏輯,通過 Texture 機制來支持視頻播放器等場景和原生視圖的嵌入。

在開發過程中我們其實也遇到很多問題,不管是混合棧,或者是視頻嵌入, iOS 兼容等,很多今天已經不再是問題, 但我還是想和大家分享下其中的一些思路。首先了解各層面原理是很重要的, Flutter 本身是一整套龐大完整的內容,針對不同層次團隊中都應該有相關的同學有一定理解;要有能力識別出關鍵問題;可以復現和定位問題,提供最小化的 Demo 用於復現它,在通過社區解決問題的場景下,最小的可復現的 Demo 是尤其重要的;還有就是要同社區有緊密的聯繫與合作。

我們不僅要關注技術本身,也一定要保證業務穩定。這裏有一些手段來和大家分享。用灰度來發現那些容易發現的問題,用分桶和降級策略逐漸增加 Flutter 的業務比例,通過線上 APM 去監控質量,這些手段來保障質量。

總的來看,目前我們有 20 多個頁面來使用 Flutter 構建, Crash 水平在萬分之一的數量級,詳情頁等幀率在 52 幀以上,可交互的頁面加載時長是 300 毫秒。

這些是我們目前使用 Flutter 開發的部分頁面,包括詳情發佈、我的等。涉及到的設計元素較多,包括視頻、圖片、聊天、評論、拍攝等比較完備的內容。我們最早使用詳情和發佈來驗證 Flutter ,也是要看 Flutter 能否支撐業務場景最複雜的情況,此外也需要分桶與降級的機制來保障最壞情況下的業務可用性。背後的演進並不容易,也是個逐漸改善解決的過程,但這並不是原理上的大問題,很多是細節上的問題,典型如安卓上面的碎片化問題。

除了業務落地外,我們也做了一些體系化的建設,這裏面很多內容都同 Native 側。就 Flutter 而言,從下往上,包括一些針對 Flutter 的 SDK ,像其特有的 APM 採集;大量 SDK 的橋接;在其上,構建用於 Flutter 開發的編程框架,如 Fish Redux , FlutterBoost 等,最終去支撐各個 Flutter 業務的研發。

03、Flutter 深度實踐

分享完 Flutter 在閒魚中的應用,接下來介紹我們的一些深度實踐。

在 Engine 側,主要解決 Android 碎片化所帶來的問題以及 iOS 上的內存優化。

在 Dart 側的工作主要圍繞着 Dill 展開,我們開發了一個基於 Dill 編織的 AOP 框架,提供了一種新的方式來實現中間語言層面的代碼編織。基於此,我們也正在做 JSON 轉換,輕量級反射等部分的內容。

在 Flutter 側,我們的工作包括 APM , FlutterBoost , FishRedux 等面向業務研發的開發框架。

在 UI 部分,我們也在做一些圖片轉代碼部分的工作。

簡單介紹下 AOP 框架和 UI To Code 兩部分的工作。

如果想去解決 AOP For Flutter 的問題,有哪些問題需要解決呢?首先我們要描述清楚這段代碼,是想對哪個庫、哪個類的哪個方法去做怎麼樣的操作;要讓 AOP 代碼沒有侵入性,使原生代碼和 AOP 代碼可以分開編寫,並最終在合適層面進行編織;我們還需要一種機制,去提取散落各處的註解形式的切面邏輯,並將其應用到目標方法中去。

在 AspectD 的設計中,通過提供面向 Dart 的 Aspect 設計,我們解決了描述切面的問題;通過提供基於 Kernel to Kernel Transform的Transformer ,我們解決了提取註解和編織的問題。

相對於傳統 AOP 框架所提供的 Call 和 Execute 的語法, AspectD 還提供了 Inject 的語法,這主要是因爲 Flutter 禁止反射造成的。

目前還有一個問題就是對於構建過程的侵入性。Flutter 的構建過程( flutter tools )和我們以前的習慣有區別,並沒有提供太多的擴展點。目前 AspectD 本身有一點對於構建流程的修改,用於攔截原始的 Dill 構建,並用操作過的 Dill 文件替換原始 Dill 文件,這一侵入性同 AOP 本身沒有什麼關係,我們正在和 Flutter 團隊去解決這一問題。

還有就是我們在做的 UI To Code 。即通過 UI 去分析版面,識別組件屬性和佈局,生成中間的 DSL 描述。後端基於此,完成針對 Flutter 的佈局推導,樹的構建優化與最終代碼轉化。

04、總結 & 展望

回顧一下,本文我們分享了跨平臺方案與 Flutter 的原理。在 Flutter 的業務落地中如何去調研問題,如何完成混合開發體系和能力擴展,如何去解決關鍵問題和保證線上質量;也展開介紹了我們在 AOP 和 UI To Code 等領域做得一些深入性工作。

展望未來,我們談談 Flutter 的一些未來的趨勢。

首先 Flutter 原理很自然地支持了 Mobile 和 Desktop ,以及神祕的 Fuchsia 系統。針對 Flutter For Web ,我最近也寫了一點分析,這是一個實驗性的項目,從原理上來說可以支持 Flutter 代碼無成本地運行在 Web 上,但可能存在性能的損失。當然如果業務不是很複雜,或不是很高的性能要求的話,可以考慮嘗試下。

除了我們自身的實踐外,我們也希望一些大的應用,可以更多地進來。更復雜的應用場景和生態鏈的支持可以讓 Flutter 的社羣更加完善。

目前我們現在也在做一些 Flutter China 的工作,核心目標就是完善國內的生態降低大家的開發門檻,讓更多的團隊能夠受益。最後,大家如果有什麼問題可以在下方評論區進行交流。

精彩回顧


本文作者:王康(正物)

閱讀原文

本文爲雲棲社區原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章