GMTC2019|闲鱼-基于Flutter的架构演进与创新
2012年應屆畢業加入阿里巴巴,主導了閑魚基于Flutter的新混合架構,同時推進了Flutter在閑魚各業務線的落地。未來將持續關注終端技術的演變及趨勢
Flutter的優勢與挑戰
Flutter是Google開源的跨端便攜UI工具包,除了具有非常優秀的跨端渲染一致性,還具備非常高效的研發體驗,豐富的開箱即用的UI組件,以及跟Native媲美的性能體驗。由于它的眾多優勢,也使得Flutter成為了近些年來熱門的新技術。
通過以上的特點可以看出,Flutter可以極大的加速客戶端的研發效率,與此同時得到優秀的性能體驗,基于我的思考,Flutter會為以下團隊帶來較大的收益:
- 中小型的客戶端團隊非常適合Flutter開發,不僅一端編寫雙端產出,還有效的解決了小團隊需要雙端人員(iOS:Android)占比接近1:1的限制,在項目快速推進過程中,能讓整個團隊的產能最大化。
- App在Android市場占比遠高于iOS的團隊,比如出海東南亞的一些App,Android市場整體占比在90%以上,通過Flutter可以將更多的人力Focus在Android市場上,同時通過在iOS端較小的投入,在結果上達到買一送一的效果。
- 以量產App為主要策略的團隊,不論是量產ToB的企業App,還是有針對性的產出不同領域的ToC的App的公司,都可以通過一端開發多端產出的Flutter得到巨大的產能提升。
閑魚在以上的場景中屬于第一種場景,服務3億用戶的閑魚App的背后,開發資源投入很少,與競對相比,我們是一只再小不過的團隊,在這種場景下,Flutter為閑魚業務的穩定發展以及提供更多的創新產品給予了很大的幫助。
但與此同時,Flutter在設計上帶來的優勢同時又會帶來新的問題。所有的新技術都是脫胎于老技術的,Flutter也不例外,其身上帶有很多Chrome的影子。我們再做一層簡化,如果我們認為Flutter是一個使用Dart語言的瀏覽器容器,請大家思考一下兩個問題如何解決。
- 如果在一個已經存在的App中加入Flutter,如何讓Native與Flutter進行無縫的銜接,同時保證相互開發之間的隔離性
- 如果在Flutter的容器中,使用已有的Native UI組件,在Flutter與Native渲染機制不同的情況下,怎么保證兩者的無縫銜接以及高性能。
閑魚的架構演進與創新
帶著上面兩個問題,我們來到閑魚場景下的具體Case以及解決方案的演進過程。
已有App+Flutter容器
在這種情況下,閑魚需要考慮的是首先要考慮引入Flutter容器后的內存壓力,保證不要產生更多的內存溢出。與此同時我們希望能讓Flutter和Native之間的頁面切換是順暢的,對不同技術棧之間的同學透明。因此我們有針對性的進行了多次迭代。
在沒有任何改造的情況下以iOS為例,你可以通過創建新的FlutterViewController來創建一個新的Flutter容器,這個方案下,當創建多個FlutterViewController時會同時在內存中創建多個Flutter Engine的Runtime(雖然底層Dart VM依然只有一個),這對內存消耗是相當大的,同時多個Flutter Engine的Runtime會造成每個Runtime內的數據無法直接共享,造成數據同步困難。
這種情況下,閑魚選擇了全局共享同一個FlutterViewController的方式保證了內存占用的最小化,同時通過基礎框架Flutter Boost提供了Native棧與Flutter棧的通信與管理,保證了當Native打開或關閉一個新的Flutter頁面時,Dart側的Navigator也做到自動的打開或關閉一個新的Widget。目前Google官方的提供的方案上就是參考閑魚早先的這個版本進行的實現的。
然而在這種情況下,如果出現如閑魚圖中所示多個Tab的場景下,整個堆棧邏輯就會產生混亂,因此閑魚在這個基礎上對Flutter Boost的方案進行了升級并開源,通過在Dart側提供一個BoostContainerManager的方式,提供了對多個Navigator的管理能力,如果打比方來看這件事,就相當于,針對Flutter的容器提供了一個類似WebView的OpenWindow的能力,每做一次OpenWindow的調用,就會產生一個新的Navigator,這樣開發者就可以自由的選擇是在Navigator里進行Push和Pop,還是直接通過Flutter Boost新開一個Navigator進行獨立管理。
Flutter Boost目前已在github開源,由于閑魚目前線上版本只支持Flutter 1.2的版本,因此需要支持1.5的同學等稍等,我們會在近期更新支持1.5的Flutter Boost版本。
Flutter頁面+Native UI
由于閑魚是一個閑置交易社區,因此圖片和視頻相對較多,對圖片視頻的線上性能以及內存占用有較嚴格的要求。目前Flutter已提供的幾種方案中(Platform View以及Flutter Plugin),不論是對內存的占用還是整個的線上流暢度上還存在一定的問題,這就造成了當大部分同學跟閑魚一樣實現一個復雜的圖文Feed推薦場景的時候,非常容易產生內存溢出。而實際上,閑魚在以上的場景下有針對性的做出了較大的優化。
在整個的Native UI到Flutter渲染引擎橋接的過程中,我們選用了Flutter Plugin中提供的FlutterTextureRegistry的能力,在去年上半年我們優先針對視頻的場景進行了優化,優化的思路主要是針對Flutter Engine底層的外接紋理接口進行修改,將原有接口中必須傳入一個PixelBuffer的內存對象這一限制做了擴展,增加一個新的接口保證其可以傳入一個GPU對象的TextureID。
如圖中所示,優化后的整個鏈路Flutter Engine可以直接通過Native端已經生成好的TextureID進行Flutter側的渲染,這樣就將鏈路從Native側生成的TextureID->copy的內存對象PixelBuffer->生成新的TextureID->渲染,轉變為Native側生成的TextureID->渲染。整個鏈路極大的縮短,保證了整個的渲染效率以及更小的內存消耗。閑魚在將這套方案上線后,又嘗試將該方案應用于圖片渲染的場景下,使得圖片的緩存,CDN優化,圖片裁切等方案與Native歸一,在享受已有集團中間件的性能優化的同時,也得到了更小的內存消耗,方案落地后,內存溢出大幅減少。
目前該方案由于需要配合Flutter Engine的修改,因此暫時無法提供完整的方案至開源社區,我們正在跟google積極溝通整個修改方案,相信在這一兩個月內會將試驗性的Engine Patch開源至社區,供有興趣的同學參考。
復雜業務場景的架構創新實踐
將以上兩個問題解決以后,閑魚開始了Flutter在業務側的全面落地,然而很快又遇到新的問題,在多人協作過程中:
- 如何提供一些標準供大家進行參考保證代碼的一致性
- 如何將復雜業務進行有效的拆解變成子問題
- 如何保證更多的同學可以快速上手并寫出性能和穩定性都不錯的代碼
在方案的前期,我們使用了社區的Flutter Redux方案,由于最先落地的詳情,發布等頁面較為復雜,因此我們有針對性的對View進行了組件化的拆分,但由于業務的復雜性,很快這套方案就出現了問題,對于單個頁面來說,State的屬性以及Reducer的數量都非常多,當產生新需求堆疊的時候,修改困難,容易產生線上問題。
針對以上的情況,我們進行了整個方案的第二個迭代,在原有Page的基礎上提供了Component的概念,使得每個Component具備完整的Redux元素,保證了UI,邏輯,數據的完整隔離,每個Component單元下代碼相對較少,易于維護和開發,但隨之而來的問題是,當頁面需要產生數據同步時,整個的復雜性飆升,在Page的維度上失去了統一狀態管理的優勢。
在這種情況下閑魚換個角度看端側的架構設計,我們參考React Redux框架中的Connect的思想,移除掉在Component的Store,隨之而來的是新的Connector作為Page和Component的數據聯通的橋梁,我們基于此實現了Page State到Component State的轉換,以及Component State變化后對Page State的自動同步,從而保證了將復雜業務有效的拆解成子問題,同時享受到統一狀態管理的優勢。與此同時基于新的框架,在統一了大家的開發標準的情況下,新框架也在底層有針對性的提供了對長列表,多列表拼接等case下的一些性能優化,保證了每一位同學在按照標準開發后,可以得到相對目前市面上其他的Flutter業務框架相比更好的性能。
目前這套方案Fish Redux已經在github開源,目前支持1.5版本,感興趣的同學可以去github進行了解。
研發智能化在閑魚的應用
閑魚在去年經歷了業務的快速成長,在這個階段上,我們同時進行了大量的Flutter的技術改造和升級,在嘗試新技術的同時,如何能保證線上的穩定,線下的有更多的時間進行新技術的嘗試和落地,我們需要一些新的思路和工作方式上的改變。
以我們日常工作為例,Flutter的研發同學,在每次開發完成后,需要在本地進行Flutter產物的編譯并上傳到遠端Repo,以便對Native同學透明,保證日常的研發不受Flutter改造的干擾。在這個過程中,Flutter側的業務開發同學面臨著很多打包上傳更新同步等繁瑣的工作,一不小心就會出錯,后續的排查等讓Flutter前期的開發變成了開發5分鐘,打包測試2小時。同時Flutter到底有沒有解決研發效率快的問題,以及同學們在落地過程中有沒有Follow業務架構的標準,這一切都是未知的。
在痛定思痛以后,我們認為數據化+自動化是解決這些問題的一個較好的思路。因此我們首先從源頭對代碼進行管控,通過commit,將代碼與后臺的需求以及bug一一關聯,對于不符合要求的commit信息,不允許進行代碼合并,從而保證了后續數據報表分析的數據源頭是健康的。
在完成代碼和任務關聯后,通過webhook就可以比較輕松的完成后續的工作,將每次的commit有效的關聯到我們的持續集成平臺的任務上來,通過閑魚CI工作平臺將日常打包自動化測試等流程變為自動化的行為,從而極大的減少了日常的工作。粗略統計下來,在去年自動化體系落地的過程中單就自動打Flutter包上傳以及觸發最終的App打包這一流程就讓每位同學每天節省一個小時以上的工作量,效果非常明顯。另外,基于代碼關聯需求的這套體系,可以相對容易的構建后續的數據報表對整個過程和結果進行細化的分析,用數據驅動過程改進,保證新技術的落地過程的收益有理有據。
總結與展望
回顧一下上下文
- Flutter的特性非常適合中小型客戶端團隊/Android市場占比較高的團隊/量產App的團隊。同時由于Flutter的特性導致其在混合開發的場景下面存在一定劣勢。
- 閑魚團隊針對混合開發上的幾個典型問題提供了對應的解決方案,使整個方案達到上線要求,該修改會在后續開放給google及社區。
- 為全面推動Flutter在業務場景下的落地,閑魚團隊通過多次迭代演進出Fish Redux框架,保證了每位同學可以快速寫出相對優秀的Flutter代碼。
- 新技術的落地過程中,在過程中通過數據化和自動化的方案極大的提升了過程中的效率,為Flutter在閑魚的落地打下了堅實的基礎。
除了本文提及的各種方案外,閑魚目前還在多個方向上發力,并對針對Flutter生態的未來進行持續的關注,分享幾個現在在做的事情
- Flutter整個上層基礎設施的標準化演進,混合工程體系是否可以在上層完成類似Spring-boot的完整體系構架,幫助更多的Flutter團隊解決上手難,無行業標準的問題。
- 動態性能力的擴展,在符合各應用商店標準的情況下,助力業務鏈路的運營效率提升,保證業務效果。目前閑魚已有的動態化方案會后續作為Fish-Redux的擴展能力提供動態化組件能力+工具鏈體系。
- Fish-Redux + UI2Code,打通代碼生成鏈路和業務框架,保證在團隊標準統一的情況下,將UI工作交由機器生成。
- Flutter + FaaS,讓客戶端同學可以成為全棧工程師,通過前后端一體的架構設計,極大的減少協同,提升效率。
讓工程師去從事更多創造性的工作,是我們一直努力的目標。閑魚團隊也會在新的一年更多的完善Flutter體系的建設,將更多已有的沉淀回饋給社區,幫助Flutter社區一起健康成長。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的GMTC2019|闲鱼-基于Flutter的架构演进与创新的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为了帮助卖家成交,闲鱼工程师做了些什么?
- 下一篇: Dataphin的代码自动化能力如何助力