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