Flutter技術(shù)解析與實(shí)戰(zhàn)_第1頁
Flutter技術(shù)解析與實(shí)戰(zhàn)_第2頁
Flutter技術(shù)解析與實(shí)戰(zhàn)_第3頁
Flutter技術(shù)解析與實(shí)戰(zhàn)_第4頁
Flutter技術(shù)解析與實(shí)戰(zhàn)_第5頁
已閱讀5頁,還剩276頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

AlibabaGroup阿里巴巴集團(tuán)掃一掃二維碼圖案,關(guān)注我吧「阿里技術(shù)」微信公眾號(hào)閑魚技術(shù)微信公眾號(hào)查看更多電子書閑魚技術(shù)團(tuán)隊(duì)發(fā)來電子書稿時(shí),我十分驚喜。國內(nèi)關(guān)于Flutter的中文書籍尚不多見,這本書如此領(lǐng)先且與眾不同。本書并非基礎(chǔ)知識(shí)的簡單羅列,而是從一線問題出發(fā),循序漸進(jìn),娓娓道來。不僅把Flutter的重要理念講得極為清晰,而且給開發(fā)者提供了應(yīng)對眼前各種問題的實(shí)用方法。特別是,本書對單點(diǎn)問題的解讀極具深度,非常具有參考價(jià)值。同時(shí),書中還給出了詳盡的可以融會(huì)貫通、舉一反三的思路,理論陳述和問題分析面面俱到,力求讓讀者可以獲得全面系統(tǒng)的技術(shù)知識(shí)。本書凝聚了閑魚技術(shù)團(tuán)隊(duì)的心血,就像弈局一樣,通過一步步的反復(fù)判斷和思考,給出清晰路徑。唯有經(jīng)歷了與谷歌團(tuán)隊(duì)的長期共建,以及對整個(gè)閑魚規(guī)劃有透徹思考后,才能淬煉出有如此深度的著作。對于如何使用Flutter以及是否要選擇Flutter的開發(fā)者或者規(guī)劃者來說,閱讀本書將大有禪益。湯興(花名:平疇)阿里巴巴集團(tuán)副總裁近年來,隨著移動(dòng)智能設(shè)備的快速普及,移動(dòng)多端統(tǒng)一開發(fā)框架已成為一個(gè)熱點(diǎn)議題。GoogleFIutter通過新的道染引擎、新的編程語言、新的編程框架,提供了一個(gè)更決絕的跨端方案,使其在眾多移動(dòng)多端統(tǒng)一開發(fā)技術(shù)中脫穎而出。我們從2017年起預(yù)研并接觸Flutter技術(shù),經(jīng)過多次的探討驗(yàn)證后正式大規(guī)模地在線上使用,在APP性能、穩(wěn)定性、開發(fā)效率上獲益良多。此外,我們積極協(xié)同GoogleFlutter團(tuán)隊(duì)去反饋和共同解決中國社區(qū)所遇到的各種挑戰(zhàn)。通過這個(gè)過程,形成了大量一手實(shí)踐知識(shí)與技術(shù)沉淀。自2018年起,我們收到博文視點(diǎn)的多次邀請,希望撰寫對移動(dòng)開發(fā)工程師有實(shí)際指導(dǎo)意義的技術(shù)圖書。從那時(shí)起我們始終在思考,應(yīng)該提供一本什么樣的書來幫助移動(dòng)開發(fā)者完善自己的關(guān)注視角,并從解決實(shí)際應(yīng)用開發(fā)問題出發(fā),思考業(yè)務(wù)與技術(shù)架構(gòu)統(tǒng)一的問題。帶著這個(gè)期望,我們系統(tǒng)地精選和編寫了閑魚技術(shù)在實(shí)際開發(fā)中沉淀的經(jīng)驗(yàn)文章,形成本書,以此回饋廣大移動(dòng)開發(fā)者。本書的目標(biāo)讀者是移動(dòng)技術(shù)開發(fā)領(lǐng)域相關(guān)工程技術(shù)人員或以此為職業(yè)目標(biāo)的在校學(xué)生。我們期望通過本書的出版,能夠幫助讀者系統(tǒng)化地理解業(yè)務(wù)問題的定義、問題如何投射到技術(shù)、解決方案的思考以及如何得出解法。因此,本書存在大量相關(guān)背景知識(shí)、工作原理介紹以及側(cè)重原因分析的方案設(shè)計(jì)。這也是我們對"授人以魚不如授人以漁"的思考,希望讀者在閱讀本書的過程中,去體會(huì)這份定義、思考與解決問題的喜悅。本書從通用業(yè)務(wù)工程化開始,進(jìn)而展開Flutter在閑魚整體云端一體化架構(gòu)的創(chuàng)新思考。第1、2章重點(diǎn)關(guān)注混合工程搭建以及關(guān)鍵能力擴(kuò)展和優(yōu)化,第3、4章探討關(guān)于大規(guī)模工程實(shí)踐中遇到的具體問題,如應(yīng)用架構(gòu)設(shè)計(jì)、性能統(tǒng)計(jì)和調(diào)優(yōu)等,并在第5章給出梳理和總結(jié)。以期讀者可以有一個(gè)自頂向下展開的閱讀路徑。本書在選題立項(xiàng)與最后成書過程中,阿里巴巴技術(shù)副總裁湯興(花名:平疇)博士提供了很多建設(shè)性意見;博文視點(diǎn)在出版過程中給予了大力支持和幫助;谷歌Eric及Flutter團(tuán)隊(duì)一直以來高效并愉快地協(xié)同和共同演進(jìn),在此謹(jǐn)向他們表達(dá)誠摯的謝意。最后,衷心感謝閑魚技術(shù)團(tuán)隊(duì)的各位同事,衷心感謝阿里云戰(zhàn)略&合作部總經(jīng)理劉湘妻和她的同事們,恕不——列舉,本書的出版與他們的支持、信任和幫助是分不開的。移動(dòng)多端統(tǒng)一開發(fā)技術(shù)是一個(gè)新的工程領(lǐng)域,發(fā)展?jié)摿薮?知識(shí)更新速度快。由于作者水平有限,書中難免有不當(dāng)之處。我們會(huì)通過閑魚技術(shù)公眾號(hào)、閑魚技術(shù)阿里云開發(fā)者社區(qū)賬號(hào)與讀者交流和更新內(nèi)容,歡迎專家和讀者給予批評指正。孫兵(花名:酒丐)阿里巴巴資深技術(shù)專家第一章混合工程11.1Flutter工程體系11.2混合工程改造實(shí)踐121.3混合工程與持續(xù)集成171.4快速完成混合工程搭建261.5使用混合棧框架開發(fā)32第二章能力增強(qiáng)402.1基于原生能力的插件擴(kuò)展402.2542.3多媒體能力擴(kuò)展實(shí)踐632.4富文本能力應(yīng)用實(shí)踐67第三章業(yè)務(wù)架構(gòu)設(shè)計(jì)723.1應(yīng)用框架設(shè)計(jì)實(shí)踐723.2輕量級動(dòng)態(tài)化渲染引擎的設(shè)計(jì)843.3面向切面編程的設(shè)計(jì)實(shí)踐953.4高性能的動(dòng)態(tài)模板渲染實(shí)踐106第四章數(shù)據(jù)統(tǒng)計(jì)與性能1184.1數(shù)據(jù)統(tǒng)計(jì)框架的設(shè)計(jì)1184.2性能穩(wěn)定性監(jiān)控方案的設(shè)計(jì)1254.3高可用框架的設(shè)計(jì)與實(shí)踐1324.4跨端方案性能對比實(shí)踐141第五章企業(yè)級應(yīng)用實(shí)戰(zhàn)1475.1基于Flutter的端架構(gòu)演進(jìn)與創(chuàng)新1475.2Flutter與Faas云端一體化架構(gòu)1571.1Flutter工程體系1.1.1混合工程研發(fā)體系介紹工程研發(fā)體系的關(guān)鍵點(diǎn)包括:混合工程下的Flutter研發(fā)結(jié)構(gòu)。在混合工程中,一個(gè)全局視角的研發(fā)結(jié)構(gòu)是什么樣的。工程結(jié)構(gòu)。已有的Native工程如何引入Flutter,工程結(jié)構(gòu)如何組織,如何管理Flutter環(huán)境,如何編譯構(gòu)建和集成打包等。構(gòu)建優(yōu)化。如何針對Flutter的工具鏈(flutter_tools、IntelliJ插件等)進(jìn)行調(diào)試與優(yōu)化。Native啟動(dòng)下的Flutter調(diào)試。不同于Flutter啟動(dòng)下的一體化調(diào)試,這種Native啟動(dòng)(xcode、Androidstudio啟動(dòng),或點(diǎn)擊圖標(biāo)打開應(yīng)用)下的Flutter調(diào)試,我們稱為分離式調(diào)試。分離式調(diào)試可以簡化flutter_tools帶來的復(fù)雜度,提高調(diào)試的穩(wěn)定性和靈活性。Native啟動(dòng)下的Flutter熱重載。聯(lián)合調(diào)試。同時(shí)調(diào)試Flutter和Android/ios。持續(xù)集成。混合環(huán)境下的Flutter構(gòu)建與持續(xù)集成。1.1.2混合工程下的Flutter研發(fā)結(jié)構(gòu)如圖1-1所示,在這個(gè)工程研發(fā)體系中,基于Flutter的官方倉庫,開發(fā)者可以獲取引擎依賴,進(jìn)行適當(dāng)修改,以滿足定制化場景下的需求。開發(fā)完畢各模塊2后發(fā)布到私有pub倉庫,再通過pubspec.yaml被業(yè)務(wù)代碼依賴和集成。在構(gòu)建時(shí),首先將Dart代碼編譯成產(chǎn)物(APP.framework或snapshot),再通過標(biāo)準(zhǔn)的pod(ios)依賴或者Gradle(Android)依賴集成到IPA(ios)和APK(Android)中去。對于Native開發(fā)人員,無須關(guān)注Flutter部分的細(xì)節(jié);對于Flutter開發(fā)人員,可以通過啟動(dòng)Flutter工程調(diào)試,也可以在Native工程啟動(dòng)后打開Flutter頁面1.1.3工程結(jié)構(gòu)這部分的核心邏輯是如何在最小改動(dòng)已有ios或Android工程的前提下運(yùn)行Flutter。可以將Flutter部分理解為一個(gè)單獨(dú)的模塊,通過pod庫(ios)或AAR庫(Android)的方式,由cocoapods和Gradle引入主工程,如圖1-2所示。31.1.4構(gòu)建優(yōu)化問題:Android在由Flutter啟動(dòng)時(shí)構(gòu)建緩慢。原因:在Flutter工具鏈(flutter_tools)的邏輯中,當(dāng)未找到androidlapp/build.gradle時(shí),會(huì)運(yùn)行g(shù)radlebuild,從而執(zhí)行多個(gè)編譯配置的構(gòu)建,而不是gradleassembleDebug.解法:重構(gòu)Android工程,使工程應(yīng)用Module對應(yīng)的build.gradle位于androidlapp下,從而符合flutter_tools的邏輯。flutter_tools的調(diào)試方法如下。(1)修改flutter_tools.dart,使之可打印參數(shù)。importimport'package:flutter_tools/executable.dart'asexecutable;voidmain(List<string>args)(print('[KLM]:${args.join('')}');executable.main(args);}##Invalidatecacheif:#*SNAPSHOT_PATHisnotafile,or#*STAMP_PATHisnotafilewithnonzerosize,or#*contentsofSTAMP_PATHisnotourlocalgitHEADrevision,or4并并*pubspec.yamllastmodifiedafterpubspec.lockpubspec.lock"]];thenrm-f"$FLUTTBR_ROOT/version"touch"$FLUTTER_ROOT/bin/cache/.dartignore""$FLUTTBR_ROOT/bin/internal/update_dart_sdk.sh"VERBOSITY="--verbosity=error"echoBuildingfluttertool...PUB_ENVIRONMENT="$PUB_ENVIRONMENT:f1utter_bot"VERBOSITY="--verbosity=normal"fiflutter_instal1"ifII-d"$FLUTTBR_ROOT/.pub-cfiretry_upgrade"$DART"$FLUTTER_TOOL_ARGS--snapshot="$SNAPSHOT_PATH"--packages-echo"$revision">"$STAMP_PATH"fi(3)從Flutter運(yùn)行構(gòu)建,獲取其入口參數(shù)。BuildingBuildingfluttertool...[KMLM]:--no-colorrun--machine--track-widget-creation--device-id=GWY7N16A31002764--start-pausedlib/main.dartRunning"flutterpackagesget"inhello_world..0.4sLaunchinglib/main.dartonMHAAL0oindebugmode...Initializinggradle...Resolvingdependencies...(4)用IntelliJ(或Androidstudio,下同)打開flutter_tools工程,新建DartcommandLineAPP,并基于步驟(3)獲得的入?yún)砼渲?programarguments",如圖1-3所示。5(5)開始fluter_tools調(diào)試,如圖1-4所示。1.1.5Native啟動(dòng)下的Flutter調(diào)試在Flutter模式下,Flutter插件調(diào)用xcodebuild(Gradle)命令構(gòu)建ios(Android)工程。對于具備Native背景的開發(fā)者來說,這不僅有些不適應(yīng),而且常因?yàn)閤codebuild等命令的參數(shù)問題,導(dǎo)致重復(fù)編譯,當(dāng)Native工程規(guī)模龐大時(shí)尤為復(fù)雜。如何解決這個(gè)問題呢?這就涉及Flutter啟動(dòng)和Native啟動(dòng)下的Flutter調(diào)試與熱重載,如圖1-5所示。1.Flutter啟動(dòng)下的Flutter調(diào)試與熱重載邏輯實(shí)際上,當(dāng)Native工程配置好Flutter支持后,在Flutter啟動(dòng)下做的工作主要有:件.flutter-plugins和pubspec.lock.7③基于Flutter配置(如Framework路徑、Debug/Release模式、是否開啟Dart2等),生成Generated.xcconfig(ios)和perties(Android)。4基于Gradle和xcodebuild構(gòu)建應(yīng)用。5基于ADB和LLDB啟動(dòng)應(yīng)用。6等待應(yīng)用中的Flutter啟動(dòng),尋找observatory端口,通過DartDebugger連接以便調(diào)試。尋找到端口后同步HotReload依賴的文件,同時(shí)透過Daemon監(jiān)聽命令(如用戶點(diǎn)擊插件按鈕)實(shí)現(xiàn)FullRestart或HotReload。換個(gè)角度來看,如果能夠解決Native啟動(dòng)下的Dart調(diào)試和HotReload,由fluttertools造成的編譯慢等將不再是問題,且可解決調(diào)試環(huán)境不穩(wěn)定的問題。當(dāng)從xcode啟動(dòng)包含了Debug模式Flutter內(nèi)容的ios(Androidstudio啟動(dòng)Android類似,這里不再重復(fù))應(yīng)用時(shí),我們需要關(guān)注步驟①23?7。而步驟①2③除非驟?7則是研發(fā)人員依賴的調(diào)試與熱重載,必須考慮此模式下如何支持。2.Native啟動(dòng)下的Flutter的調(diào)試與熱重載邏輯kylekylewongakylenongdeMacBOOK-proios%idevicesyslogIgreplisteningAug2614:07:18kylewongs-iphoneRunner(Flutter)[686]<Notice>:flutter:observatorylisteningonhttp://127.0.0.1:56486/OB7rBODQ3vu=/可以看到ios設(shè)備上的observatory啟動(dòng)了一個(gè)x的端口(端口號(hào)隨機(jī)),認(rèn)證碼為y。透過iproxy將ios設(shè)備上的端口x映射到本機(jī)端口z。kylekylewongakylewongdeMacBOOK-proios%iproxy810156486your-ios-device-uuid可以看到waitingforconnection,此時(shí)就可以訪問htp::z/y/#/8可以使用observatory檢查諸多與Dart相關(guān)的內(nèi)存和調(diào)試等,這里不再展開。也可以通過IDE鏈接去調(diào)試,配置DartRemoteDebug,如圖1-7所示。這里需要注意的是,端口要使用剛轉(zhuǎn)發(fā)到計(jì)算機(jī)的端口z,搜索源碼路徑為Flutter工程的根目錄。為了避免出現(xiàn)因?yàn)檎J(rèn)證碼造成的無法連接的問題,啟動(dòng)時(shí)需要傳入'--disable-service-auth-codes'標(biāo)志。配置好之后單擊"調(diào)試"按鈕,連接到調(diào)試端口,如圖1-8所示。成功后可以看到Debugger顯示connected。如果沒有顯示,則再單擊一次"調(diào)試"按鈕,如圖1-9所示。之后便可以正常地使用IDE設(shè)置斷點(diǎn)和調(diào)試Dart(Flutter)代碼了,如圖1-10所示。圖1-10圖1-111.1.7Native與Flutter聯(lián)合調(diào)試除了可以在任意時(shí)刻(Flutter啟動(dòng)后)調(diào)試Flutter,還可以使用Androidstudio的聯(lián)調(diào)。同樣,結(jié)合xcode的Attachtoprocess,可以實(shí)現(xiàn)ios與Flutter聯(lián)調(diào)。1.1.8持續(xù)集成閑魚團(tuán)隊(duì)有Native開發(fā)人員和Flutter開發(fā)人員,因此區(qū)分了Flutter模式和Native模式。有一臺(tái)公共設(shè)備(MacMini)安裝了Flutter環(huán)境并負(fù)責(zé)Flutter相關(guān)的構(gòu)建,構(gòu)建好的產(chǎn)物以AAR(Android)或pod庫(ios)的形式集成到Native工程下(可以認(rèn)為Flutter相關(guān)的代碼就是一個(gè)模塊),用于構(gòu)建最終產(chǎn)物APK(Android)或IPA(ios)的CI平臺(tái)最終也通過產(chǎn)物方式集成Flutter并打包。1.2混合工程改造實(shí)踐當(dāng)使用Flutter實(shí)現(xiàn)跨平臺(tái)開發(fā)時(shí),如果原有的ios和Android工程已相當(dāng)龐大,那么如何將Flutter無縫地橋接到這些大工程中并保證開發(fā)效率不受影響是優(yōu)先要解決的問題。本文給出了一種通用的工程改造方案,希望為準(zhǔn)備轉(zhuǎn)型Flutter的團(tuán)隊(duì)提供參考。1.2.1項(xiàng)目背景及問題Flutter的工程結(jié)構(gòu)比較特殊,由Flutter目錄再分別包含Native工程的目錄(即ios和Android兩個(gè)目錄)組成,如圖1-12所示。在默認(rèn)情況下,引入Flutter的Native工程無法脫離父目錄進(jìn)行獨(dú)立構(gòu)建和運(yùn)行,因?yàn)樗鼤?huì)反向依賴于Flutter相關(guān)的庫和資源。圖1-12很顯然,在擁有了Native工程的情況下,開發(fā)者不太可能去創(chuàng)建一個(gè)全新的Flutter工程并重寫整個(gè)產(chǎn)品,因此Flutter工程將包含已有的Native工程,這樣就帶來了一系列問題。1)構(gòu)建打包問題:引入Flutter后,Native工程因?qū)ζ溆辛艘蕾嚭婉詈?從而無法獨(dú)立編譯和構(gòu)建。在Flutter環(huán)境下,工程的構(gòu)建從FIlutter的構(gòu)建命令開始,執(zhí)行過程中包含了Native工程的構(gòu)建,開發(fā)者要配置完整的FIlutter運(yùn)行環(huán)境才能走通整個(gè)流程。2)混合編譯導(dǎo)致開發(fā)效率的降低:在向Flutter轉(zhuǎn)型的過程中必然有許多業(yè)務(wù)仍使用Native進(jìn)行開發(fā),工程結(jié)構(gòu)的改動(dòng)會(huì)使開發(fā)過程無法在純Native環(huán)境下進(jìn)行,而適配到Flutter工程結(jié)構(gòu)對純Native開發(fā)來說又會(huì)造成不必要的構(gòu)建步驟,導(dǎo)致開發(fā)效率的降低。1.2.2改造目標(biāo)針對以上問題,我們提出了以下改造目標(biāo),力求使Native工程對Flutter相關(guān)文件的依賴最小化。Native工程可以獨(dú)立地編譯構(gòu)建和調(diào)試執(zhí)行,進(jìn)而最大限度地減少對相關(guān)開發(fā)人員的干擾,使打包平臺(tái)不再依賴Flutter環(huán)境及相關(guān)流程。當(dāng)Native工程處在Flutter環(huán)境中時(shí)(即作為ios或Android子目錄)能夠正確依賴相關(guān)庫和文件,正常執(zhí)行各類Flutter功能,如Dart代碼的構(gòu)建、調(diào)試、熱重載等,保證在Flutter環(huán)境下開發(fā)的正確性。1.2.3方案的制定1.兩種模式首先將Native工程處于獨(dú)立目錄環(huán)境下稱為standalone模式,處于Flutter目錄下稱為Flutter模式。純Native開發(fā)或平臺(tái)打包就處于standalone模式,Flutter對開發(fā)人員和打包平臺(tái)來說是透明的,不會(huì)影響構(gòu)建與調(diào)試。而Flutter的代碼則在Flutter模式下進(jìn)行開發(fā),其相關(guān)庫的生成、編譯和調(diào)試都執(zhí)行Flutter定義的流程,如圖1-13所示。圖1-13兩種工程模式2.厘清依賴從模式的定義來看,既然改造的核心就是把standalone模式提取出來,那么就要厘清standalone模式對Flutter的依賴,并將其提取成第三方的庫、資源或源碼文件。以ios為例,通過閱讀Flutter構(gòu)建的源碼,可知xcode工程對Flutter有如下依賴:2)Flutter.framework:Flutter引擎庫文件。自定義的channels(橋接通道)4)flutter_assets:Flutter依賴的靜態(tài)資源,如字體和圖片等。3.依賴引入的策略在改造過程中,閑魚嘗試過兩種依賴引入策略,下面分別進(jìn)行闡述。(1)本地依賴。通過修改Flutter構(gòu)建流程,將其庫文件、源碼和資源直接放置到Native工程的子目錄中進(jìn)行引用,以ios為例,就是將Flutter.framework及相關(guān)插件等做成本地的pod依賴,也將資源復(fù)制到本地進(jìn)行維護(hù)。由此,standalone模式便具備了獨(dú)立構(gòu)建和執(zhí)行的能力,對于純Native開發(fā)人員來說,Flutter只是一些二方庫與資源的合集,無須關(guān)注。而在Flutter模式下,Dart源碼的構(gòu)建流程不變,不影響編譯和調(diào)試。同時(shí),由于是本地依賴,在Flutter模式下的各種改動(dòng)也可以實(shí)時(shí)地同步到Native工程的子目錄中。提交修改后,standalone模式也就擁有了最新的Flutter相關(guān)功能。優(yōu)點(diǎn):將Flutter相關(guān)內(nèi)容的改動(dòng)同步到standalone模式也比較方便;缺點(diǎn):需要對Flutter原有的構(gòu)造流程進(jìn)行稍復(fù)雜的改動(dòng),并且與后續(xù)的Flutter代碼合并會(huì)有沖突,且Native工程與Flutter的代碼、庫及資源等內(nèi)容還是耦合在本地,不夠獨(dú)立。(2)遠(yuǎn)程依賴。遠(yuǎn)程依賴的想法是將Flutter所有依賴內(nèi)容都放在獨(dú)立的遠(yuǎn)端倉庫中,在standalone模式下引用遠(yuǎn)程倉庫中的相關(guān)資源、源碼和庫文件,在Flutter模式下的構(gòu)建流程和引用方式不變,如圖1-14所示。步到standalone模式方能生效。圖1-141.2.4改造的實(shí)現(xiàn)過程1.目錄的組織在Flutter模式下,父工程目錄下的ios和Android的子目錄分別包含對應(yīng)的Native工程。在代碼管理上,子工程可以使用Git的submodule形式,保證目錄間的獨(dú)立。2.遠(yuǎn)程依賴的實(shí)現(xiàn)在standalone模式下,Flutter的依賴內(nèi)容都指向遠(yuǎn)程倉庫中的對應(yīng)文件,而在Flutter模式下依賴的方式不變。(1)向standalone模式同步Flutter的變更。由于遠(yuǎn)程依賴的問題是同步變動(dòng)比較麻煩,為此閑魚開發(fā)了一系列腳本工具,使該過程盡量自動(dòng)完成。假設(shè)Flutter的內(nèi)容(可能是業(yè)務(wù)源碼、引擎庫或某些資源文件)發(fā)生變化,那么在Flutter模式下構(gòu)建結(jié)束后,腳本會(huì)提取生成好的所有依賴文件并將其復(fù)制到遠(yuǎn)程倉庫,提交并打標(biāo)在standalone模式下將Flutter的依賴修改至最新的版本,從而完成整個(gè)同步過程,如圖1-15所示。圖1-15(2)同步的時(shí)機(jī)建議在提測及灰度期間,每次Flutter業(yè)務(wù)的提交都能夠觸發(fā)同步腳本的執(zhí)行和APP打包;在開發(fā)期間,保持每日一次的同步即可。為解決引入Flutter后的工程適配問題,閑魚抽取了Flutter的相關(guān)依賴放到遠(yuǎn)程供純Native工程進(jìn)行引用,從而保證了Flutter與純Native開發(fā)的相互獨(dú)立與并行執(zhí)行。該方案已在閑魚施行了幾個(gè)版本,并反向輸出給了Flutter團(tuán)隊(duì),為其后續(xù)的團(tuán)隊(duì)提供幫助,雖然項(xiàng)目間的差異也會(huì)導(dǎo)致方案的不同,但是實(shí)施的思路依然有借鑒價(jià)值。I1.3混合工程與持續(xù)集成本節(jié)重點(diǎn)介紹Flutter混合工程中解除Native工程對Flutter的直接依賴的具體實(shí)現(xiàn)方法。1.3.1背景思考因?yàn)殚e魚采用的是Flutter和Native混合開發(fā)的模式,所以存在一部分開發(fā)人員只做Native開發(fā),并不熟悉Flutter技術(shù)。(1)如果直接采用Flutter工程結(jié)構(gòu)作為日常開發(fā),則Native開發(fā)人員也需要配置Flutter環(huán)境,了解Flutter技術(shù),成本比較高。(2)目前阿里巴巴集團(tuán)的構(gòu)建系統(tǒng)并不支持直接構(gòu)建Flutter項(xiàng)目,這也要求閑魚解除Native工程對Flutter的直接依賴。基于這兩點(diǎn)考慮,閑魚希望設(shè)計(jì)一個(gè)Flutter依賴抽取模塊,可以將Flutter的依賴抽取為一個(gè)Flutter依賴庫并發(fā)布到遠(yuǎn)程,供純Native工程引用,如圖1-16所示。圖1-161.3.2實(shí)現(xiàn)方法1.Native工程依賴的Flutter分析分析Flutter工程,會(huì)發(fā)現(xiàn)Native工程對Flutter工程的依賴主要有三部分:oFlutter庫和引擎。Flutter的Framework庫和引擎庫。oFlutter工程。我們自己實(shí)現(xiàn)的Flutter模塊功能,主要為在Flutter工程lib目錄下,由Dart代碼實(shí)現(xiàn)的這部分功能。自己實(shí)現(xiàn)的Flutterplugin。解開Android和ios的APP文件,可以發(fā)現(xiàn)Flutter依賴的主要文件如圖1-17所示。圖1-17(1)Android的Flutter依賴的文件oFlutter庫和引擎。包括icudtl.dat、libflutter.so,以及一些class文件。它們都被封裝在flutter.jar中,這個(gè)jar文件位于Flutter庫目錄下的[flutter/bin/cachelartifacts/engine]中.Flutter工程產(chǎn)物。包括isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr和flutter_assets.1.3混合工程與持續(xù)集成<19Flutterplugin.各個(gè)plugin編譯出來的AAR文件,包括:isolate_snapshot_data(應(yīng)用程序數(shù)據(jù)段)、isolate_snapshot_instr(應(yīng)用程序指令段)vm_snapshot_data(虛擬機(jī)數(shù)據(jù)段)vm_snapshot_instr(虛擬機(jī)指令段)。(2)ios的Flutter依賴的文件oFlutter庫和引擎。Flutter.framework。Flutter工程的產(chǎn)物。APP.framework.Flutterplugin。編譯出來的各種plugi其他Framework。我們只需要將編譯結(jié)果抽取出來,打包成一個(gè)SDK依賴的形式提供給Native工程,就可以解除Native工程對Flutter工程的直接依賴。2.Android依賴的Flutter庫抽取(1)Android中Flutter編譯任務(wù)分析Flutter工程的Android打包,其實(shí)只是在Android的Gradle任務(wù)中插入了一個(gè)flutter.gradle任務(wù),而flutter.gradle主要做了三件事(這個(gè)文件可以在Flutter.增加flutter.jar的依賴。.插入Flutterplugin的編譯依賴。.插入Flutter工程的編譯任務(wù),得到的產(chǎn)物包括兩個(gè)isolate_snapshot文件、兩個(gè)vm_snapshot文件和flutter_assets文件夾。然后將產(chǎn)物拷貝到mergeAssets.outputDir,最后合并到APK的assets目錄下。(2)Android的Flutter依賴抽取實(shí)現(xiàn)對Android的Flutter依賴抽取步驟如下:(a)編譯Flutter工程這部分的主要工作是編譯Flutter的Dart和資源部分,可以用AOT和Bundle命令編譯。release"release"TaskmergeFlutterAssets=project.tasks.create(name:"mergeFlutterAssets$.capitalize()}",type:Copy)(dependsonmergeFlutterMD5Assetsfrom(allertAsset){include"flutter_assetg/**"include"m_snapshot_data"include"vm_snapshot_instr"include"isolate_snapshot_data"include"isolate_snapshot_instr"intovariant.mergeAssets.outputDir}variant.outputs[0].processResources.dependson(mergeFlutterAssets)(c)同時(shí)將AAR文件和Flutterplugin編譯出來的AAR文件一起發(fā)布到Maven倉庫發(fā)布Flutter工程產(chǎn)物打包的AAR文件。echoecho'cleanpackflutterinput(flutterbuild)'rm-f-randroid/packflutter/Elutter/#拷貝flutter.jarecho'copyflutterjar'mkdir-pandroid/packflutter/flutter/flutter/android-arm-release&&cpflutter/bin/cache/artifacts/engine/android-arm-release/Elutter.jar"$_"#拷貝assetecho'copyflutterasset'mkdir-pandroid/packflutter/flutter/assets/release&&cp-rbuild/flutteroutput/aot/*"$_"mkdir-pandroid/packflutter/flutter/assets/release/f1utter_assets&cp-rbuild/Elutteroutput/Elutter_assets/*"$_"#將Flutter庫和flutter_app打成AAR文件,同時(shí)發(fā)布到Ali-mavenecho'Buildandpublishidlefishfluttertoaar'cdandroidif[-n"$1"]then./gradlew:packflutter:clean:packflutter:publish-PAAR_VBRSION=$1else./gradlew:packflutter:clean:packflutter:publishficd../231.3.3ios依賴的Flutter庫的抽取1.ios中的Flutter依賴文件是如何產(chǎn)生的執(zhí)行編譯命令"flutterbuildios",最終會(huì)執(zhí)行Flutter的編譯腳本[xcode_backend.sh],而這個(gè)腳本主要做了下面幾件事:獲取各種參數(shù),如project_path、target_path、build_mode等,主要來自Generated.xcconfig的各種定義。刪除Flutter目錄下的APP.framework和app.flx.對比FlutterIFlutter.framework與FLUTTER_ROOT/bin/cachelartifactslenginefartifact_variant}目錄下的Flutter.framework,若不相等,則用后者覆蓋前者。獲取生成APP.framework命令所需參數(shù),包括build_dir、local_engine_flag、preview_dart_2_flag和aot_flags.生成APP.framework,并將生成的APP.framework和APPFrameworkInfo.plist拷貝到xcode工程的Flutter目錄下。2.ios的Flutter依賴抽取實(shí)現(xiàn)編譯Flutter工程,生成APP.framework。echoecho"===清理Elutter歷史編譯===l"l./flutter/bin/fluttercleanecho"===重新生成plugin索引==="./Elutter/bin/flutterpackagesgetecho"===生成APP.framework和flutter_assets==="./flutter/bin/flutterbuildios--release將各插件打包為靜態(tài)庫。這里主要有兩步:一是將插件打包成二進(jìn)制庫文件,二是將插件的注冊入口打包成二進(jìn)制庫文件。echoecho===生成各個(gè)插件的二進(jìn)制庫文件===l"cdios/pods#/usr/bin/envxcrunxcodebuildclean2425多條線并行開發(fā)Flutter時(shí),版本管理混亂,容易出現(xiàn)遠(yuǎn)程庫被覆蓋的問題。需要最少一名開發(fā)人員持續(xù)跟進(jìn)發(fā)布,人工成本較高。針對這些問題,閑魚引入了CI自動(dòng)化框架,從兩方面來解決:一方面是通過自動(dòng)化降低人工成本,也減少人為失誤;另一方面是用自動(dòng)化的形式做好版本控制。首先,在每次需要構(gòu)建純Native工程之前,自動(dòng)完成Flutter工程對應(yīng)的遠(yuǎn)程庫的編譯發(fā)布工作,整個(gè)過程不需要人工干預(yù)。其次,在開發(fā)測試階段,采用五段式的版本號(hào),最后一位自動(dòng)遞增產(chǎn)生,這樣就可以保證測試階段所有并行開發(fā)的Flutter庫的版本號(hào)不會(huì)產(chǎn)生沖突。最后,在發(fā)布階段,采用三段式或四段式的版本號(hào),可以和APP版本號(hào)保持一致,便于后續(xù)問題追溯。整個(gè)流程如圖1-18所示。圖1-181.4快速完成混合工程搭建Flutter的主要開發(fā)模式分成兩種,一種是獨(dú)立APP的模式,以Flutter為主,原生工程會(huì)被包含在Flutter工程下;另一種是讓Flutter以模塊(Flutter模塊)的形式存在,分別集成在已有的ios和Android原生應(yīng)用下,原生工程可以在任何的目錄結(jié)構(gòu)下,和Flutter工程地址不產(chǎn)生關(guān)聯(lián),并需要在原生工程結(jié)構(gòu)中聲明Flutter工程的本地地址。在Flutter能夠以模塊形式存在之前,閑魚進(jìn)行了很長時(shí)間的混合APP架構(gòu)的探索,對原生工程進(jìn)行了比較多的改動(dòng)。在Flutter官方推出Flutter模塊模式后,我們進(jìn)行了大量調(diào)研,最終推出了一套開箱即用的混合工程腳手架flutter-boot,有助于快速搭建混合工程。1.4.1flutter-boot簡介flutter-boot主要解決了混合開發(fā)模式下的兩個(gè)問題:Flutter混合開發(fā)的工程化設(shè)計(jì)和混合棧。那么flutter-boot是如何解決的呢?首先在工程化設(shè)計(jì)的問題上,flutter-boot建立了一套標(biāo)準(zhǔn)的工程創(chuàng)建流程和友好的交互命令。當(dāng)流程執(zhí)行完成后,即擁有了混合開發(fā)的標(biāo)準(zhǔn)工程結(jié)構(gòu)。這一套工程結(jié)構(gòu)能夠幫助開發(fā)者同時(shí)擁有Flutter開發(fā)和原生開發(fā)兩種開發(fā)視角,本地Flutter開發(fā)和云端Flutter構(gòu)建兩種Flutter集成模式,其效果如圖1-19所示。另外,在混合棧方面,flutter-boot能自動(dòng)注入混合棧依賴,同時(shí)將核心的混合棧接入代碼封裝并注入原生工程內(nèi)。用戶按提示插入簡單的幾行模板代碼后,即可看到混合棧的效果。使用flutter-boot搭建的混合工程開箱即可使用,接下來介紹flutter-boot解決這些問題的詳細(xì)過程。27圖1-191.4.2工程化設(shè)計(jì)1.了解官方的AddFluttertoexistingapps項(xiàng)目在了解flutter-boot的工程化設(shè)計(jì)細(xì)節(jié)之前,我們需要對Google官方提供的AddFluttertoexistingapps方案有一個(gè)初步的了解。AddFluttertoexistingapps項(xiàng)目會(huì)引導(dǎo)開發(fā)者以模塊的形式創(chuàng)建Flutter,模塊形態(tài)的FIlutter的工程結(jié)構(gòu)如下所示。some/path/some/path/my_Elutter/lib/main.dart.ios/.android/在官方的工程結(jié)構(gòu)下,.ios和.android是模板工程,當(dāng)在Flutter工程目錄下運(yùn)28行時(shí),即通過這兩個(gè)工程來啟動(dòng)應(yīng)用。我們?nèi)绾巫屧こ毯虵lutter產(chǎn)生關(guān)聯(lián)呢?這里的關(guān)聯(lián)會(huì)分成三個(gè)部分,分別是Flutter的Framework、業(yè)務(wù)代碼和插件庫。其中,Flutter插件庫分成FlutterpluginNative(即插件原生代碼)和FlutterpluginDart(即插件的Dart代碼)兩個(gè)部分。這四部分的差異如表1-1所示。模塊模塊數(shù)量內(nèi)容變更頻率支持調(diào)試FlutterFramework唯一低否FlutterpIuginNative高頻變更低是FlutterpluginDart高頻變更低是Flutter業(yè)務(wù)代碼唯一高是FlutterFramework只需要在依賴管理中聲明即可,FlutterpluginNative可以直接以源碼的方式集成,FlutterpluginDart只有在被業(yè)務(wù)代碼引用時(shí)才有效。和業(yè)務(wù)代碼一樣,需要支持Dart代碼的調(diào)試模式和發(fā)布模式。Dart代碼的關(guān)聯(lián)會(huì)侵入APP的構(gòu)建環(huán)節(jié),根據(jù)APP構(gòu)建的模式來決定Dart代碼的構(gòu)建模式。對于具體的實(shí)現(xiàn),以ios系統(tǒng)為例,在podfile文件中增加一個(gè)自定義的Ruby腳本podfilehelper的調(diào)用,podfilehelper會(huì)聲明FlutterFramework的依賴、FlutterpluginNative的源碼引用和業(yè)務(wù)代碼的路徑。接buildphase內(nèi)加入shell腳本xcodebackend的調(diào)用,xcodebackend會(huì)根據(jù)當(dāng)前構(gòu)建模式產(chǎn)出Dart構(gòu)建產(chǎn)物。2.flutter-boot的補(bǔ)充對于官方的混合工程項(xiàng)目,在體驗(yàn)過程中發(fā)現(xiàn)有如下問題:文件或配置的添加為手動(dòng)添加,流程較長。不支持在Flutter倉庫下運(yùn)行原生工程。不支持Flutter以獨(dú)立代碼倉庫部署時(shí)的遠(yuǎn)端機(jī)器構(gòu)建。因此,在flutter-boot腳手架中,為了解決這些問題,把混合工程的部署分為create、link、remotelink和update四個(gè)過程。(1)createcreate過程在于搭建一個(gè)Flutter模塊,包括Flutter模塊的創(chuàng)建和Git倉庫的部署。在調(diào)用Flutter模塊創(chuàng)建命令前,通過基礎(chǔ)的檢查,讓工程位置和命名的規(guī)范狀態(tài)進(jìn)行檢查。當(dāng)倉庫為空時(shí),直接添加文件;當(dāng)倉庫非空時(shí),會(huì)優(yōu)先清理倉庫。(2)linklink過程在于關(guān)聯(lián)本地的原生工程和Flutter工程。在關(guān)聯(lián)的過程中,會(huì)先請求獲取Flutter工程的地址和原生工程的地址,然后將需要手動(dòng)集成的部分通過腳本的方式自動(dòng)集成。為了獲得Flutter開發(fā)視角(即Flutter工程下運(yùn)行原生工程),將原生工程進(jìn)行了軟鏈接,鏈接到Flutter工程的ios目錄和Android目錄。Flutter在運(yùn)行前會(huì)找到工程下的ios或Android目錄然后運(yùn)行。在Flutter工程下運(yùn)行ios工程會(huì)存在一個(gè)限制,即ios工程的target需要指定為runner。為了解決這個(gè)問題,將原生工程的主target進(jìn)行了復(fù)制,命名為runner的target。同時(shí),為了支持遠(yuǎn)程構(gòu)建的模式,將Flutter倉庫本地路徑的聲明根據(jù)構(gòu)建模式進(jìn)行了區(qū)分,封裝在自定義的依賴腳本中。例如在ios工程內(nèi),會(huì)添加fbpodhelper.rb腳本文件,然后將Flutter倉庫本地路徑添加到配置文件fbconfig.local,json中。(3)remotelink&&update在遠(yuǎn)端構(gòu)建模式下,通過remotelink能夠獲取Flutter倉庫的代碼,并在遠(yuǎn)端機(jī)器上進(jìn)行構(gòu)建。在遠(yuǎn)端構(gòu)建模式下,我們會(huì)侵入依賴管理的過程,在獲取依賴時(shí),拉取Flutter倉庫的代碼,將代碼放置在原生工程的.fbflutter目錄下,并將該目錄聲明為Flutter倉庫本地路徑。對于拉取Flutter代碼并進(jìn)行本地部署的過程,我們稱為update過程。在遠(yuǎn)端構(gòu)建時(shí),就能和本地構(gòu)建如出一轍。那么如何區(qū)分遠(yuǎn)端模式和本地模式呢?我們將遠(yuǎn)端的Flutter倉庫信息記錄在fbconfig.json中,同時(shí)在師運(yùn)行一次remotelink,其他的開發(fā)協(xié)同者將不用關(guān)注遠(yuǎn)端構(gòu)建的配置流程。(4)init為了便于快速搭建,我們提供了一個(gè)命令集合,命名為init,將必備的環(huán)節(jié)以命令行交互的模式集成在了init命令中。1.4.3混合棧混合棧是閑魚開源的一套用于Flutter混合工程下協(xié)調(diào)原生頁面與Flutter頁面交互的框架,目前是混合開發(fā)模式下的主流框架。在混合棧開源后,有大量開發(fā)者在集成混合棧時(shí)會(huì)遇到因各種環(huán)境配置或代碼添加導(dǎo)致的集成問題,為此這里提供一套快速集成的方案。1.集成問題要做到快速集成,面臨兩個(gè)問題:Flutter和混合棧的版本兼容,混合棧Demo代碼封裝及插入。(1)版本兼容問題目前支持的混合棧版本為0.1.52,支持Flutter1.5.4。當(dāng)Flutter升級時(shí),混合棧勢必要進(jìn)行適配,即集成的混合棧版本也需要變更。因此,將混合棧的版本配置通過文件進(jìn)行維護(hù),記錄當(dāng)前Flutter所需要的混合棧版本。在初版的flutter-boot中,我們限定了混合棧的版本號(hào),在發(fā)布新版本混合棧時(shí),將開放版本選擇的功能。(2)代碼封裝及插入問題在調(diào)研了混合棧的使用過程后,將混合棧需要的Demo代碼分成了四個(gè)部分:Flutter引擎的托管,頁面路由的配置,Demo形式的Dart頁面,原生的測試跳轉(zhuǎn)入口。2.解決方案①Flutter引擎的托管對于引擎的托管,依賴于應(yīng)用的初始化。由于初始化過程隨著應(yīng)用的復(fù)雜而復(fù)雜,因此目前提供了一行代碼作為接口,使用者在初始化應(yīng)用時(shí)加入這一行代碼即可完成托管。2頁面路由的配置&&Demo形式的Dart頁面路由配置指路由到某個(gè)標(biāo)識(shí)符時(shí),Flutter頁面或原生頁面需要識(shí)別并跳轉(zhuǎn)到相應(yīng)頁面。路由的配置需要在原生頁面和Flutter頁面兩側(cè)進(jìn)行部署。在原生側(cè),將混合棧的Demo路由代碼進(jìn)行了精簡,然后將其添加到原生工程的固定目錄下。由于ios僅添加代碼文件是不會(huì)被納入構(gòu)建范圍的,因此封裝了一套ios側(cè)的代碼添加工具來實(shí)現(xiàn)文件的插入。在Flutter側(cè),對main.dart文件進(jìn)行了覆蓋,將帶有路由邏輯的main.dart集成進(jìn)來,同時(shí)提供了Demo形成的Dart頁面的創(chuàng)建邏輯。③原生的測試跳轉(zhuǎn)入口為了方便使用者快速看到混合工程的跳轉(zhuǎn)模式,在ios和Android雙端封裝了一個(gè)入口按鈕和按鈕的添加過程,使用者在測試頁面手動(dòng)加入一行代碼,即可看到跳轉(zhuǎn)Flutter的入口。3.最終效果在使用flutter-boot前,開發(fā)者可能要花費(fèi)數(shù)天來進(jìn)行混合工程搭建。現(xiàn)在,開發(fā)者只需要調(diào)用一個(gè)命令,加入兩行代碼即可完成混合工程的搭建,大大降低了開發(fā)成本。但flutter-boot的使命還未達(dá)成,我們期望開發(fā)者能更加流暢地進(jìn)行Fluter開發(fā),未來會(huì)優(yōu)化多人協(xié)同的開發(fā)流程,完善持續(xù)集成環(huán)境的搭建,讓開發(fā)者擁有更佳的開發(fā)體驗(yàn)。32>第一章混合工程1.5使用混合棧框架開發(fā)1.5.1為什么需要混合方案具有一定規(guī)模的APP通常有一套成熟通用的基礎(chǔ)庫,尤其是阿里巴巴APP,一般需要依賴很多體系內(nèi)的基礎(chǔ)庫。使用Flutter重新開發(fā)APP的成本和風(fēng)險(xiǎn)都較高。所以,在NativeAPP進(jìn)行漸進(jìn)式遷移是穩(wěn)健型方式。閑魚在實(shí)踐中沉淀出一套自己了他們的一些建議,同時(shí)也針對自身業(yè)務(wù)情況進(jìn)行方案的選型以及具體的實(shí)現(xiàn)方法。1.基本原理Flutter技術(shù)鏈主要由c++實(shí)現(xiàn)的FlutterEngine和Dart實(shí)現(xiàn)的Framework組成。FlutterEngine負(fù)責(zé)線程管理、DartvM狀態(tài)管理和Dart代碼加載等工作。而Dart代碼所實(shí)現(xiàn)的Framework則是業(yè)務(wù)接觸到的主要API,如widget等概念就是在Dart層面的Framework內(nèi)容。一個(gè)進(jìn)程里面最多只會(huì)初始化一個(gè)DartvM。然而,一個(gè)進(jìn)程可以有多個(gè)FlutterEngine,多個(gè)Engine實(shí)例共享同-個(gè)DartvM。我們來看具體實(shí)現(xiàn)方法,在ios中,每初始化一個(gè)Flutterviewcontroller就會(huì)有一個(gè)引擎隨之初始化,也就意味著會(huì)有新的線程(理論上線程可以復(fù)用)去運(yùn)行Dart2.Google官方給出的建議(1)引擎深度共享在混合方案方面,Flutter官方給出的建議是從長期來看,應(yīng)該支持在同一個(gè)引擎支持多窗口繪制的能力,至少在邏輯上做到Flutterviewcontroller共享同一個(gè)引33擎的資源。換句話說,希望所有的繪制窗口共享同一個(gè)主Isolate。但Google官方給出的長期建議目前來說沒有很好的支持。(2)多引擎模式在混合方案中,我們解決的主要問題是如何處理交替出現(xiàn)的Flutter和Native頁面。Google工程師給出了一個(gè)keepItsimple的方案:對于連(widget),只需要在當(dāng)前Flutterviewcontroller中打開即可,對于間隔的Flutter頁面,選擇初始化新的引擎。例如,進(jìn)行下面一組導(dǎo)航操作:FlutterFlutterpage1l->Flutterpage2->Nativepage1->Flutterpage3只需在Flutterpage1和Flutterpage3中創(chuàng)建不同的Flutter實(shí)例即可。這個(gè)方案的好處是簡單易懂,邏輯清晰;但也有潛在的問題,如果一個(gè)Native頁面和一個(gè)Flutter頁面一直交替進(jìn)行,那么FlutterEngine的數(shù)量會(huì)呈線性增加,(3)多引擎模式的問題冗余的資源問題。多引擎模式下,每個(gè)引擎之間的Isolate是相互獨(dú)立的。在邏輯上這并沒有什么壞處,但是引擎底層其實(shí)是維護(hù)了圖片緩存等比較消耗內(nèi)存的對象。想象一下,若每個(gè)引擎都維護(hù)自己的一份圖片緩存,則內(nèi)存壓力將非常大。.插件注冊的問題。插件依賴Messenger傳遞消息,而Messenger是由Flutterviewcontroller(Activity)實(shí)現(xiàn)的。如果有多個(gè)Flutterviewcontroller,插件的注冊和通信將會(huì)變得混亂、難以維護(hù),消息傳遞的源頭和目標(biāo)也會(huì)變得不可控。.Flutterwidget和Native的頁面差異化問題。Flutter的頁面是widget,Native的頁面是vc。從邏輯上來說,我們希望消除Flutter頁面與Naitve頁面的差異,否則在進(jìn)行頁面埋點(diǎn)和其他一些統(tǒng)一操作的時(shí)候,都會(huì)遇到額外的復(fù)雜度。增加頁面之間通信的復(fù)雜度。如果所有Dart代碼都運(yùn)行在同一個(gè)引擎實(shí)例中,34它們共享一個(gè)Isolate,則可以用統(tǒng)一的編程框架進(jìn)行widget之間的通信,多引擎實(shí)例也增加復(fù)雜度。因此,綜合多方面考慮,閑魚并沒有采用多引擎混合方案。3.現(xiàn)狀與思考考慮到多引擎存在的一些實(shí)際問題,所以閑魚目前采用的混合方案是共享同一個(gè)引擎。這個(gè)方案基于這樣一個(gè)事實(shí):在任何時(shí)候最多只能看到一個(gè)頁面,當(dāng)然對于些特定的場景,可以看到多個(gè)viewcontroller,但是這些特殊場景我們這里不討論。可以這樣簡單地理解這個(gè)方案:把共享的Flutterview當(dāng)成一個(gè)畫布,然后用一個(gè)Native的容器作為邏輯的頁面。每次在打開一個(gè)容器的時(shí)候,通過通信機(jī)制通知Flutterview繪制成當(dāng)前的邏輯頁面,然后將Flutterview放到當(dāng)前容器里面。構(gòu)的特點(diǎn)是每次只能從棧頂操作頁面,每一次在查找邏輯頁面的時(shí)候,如果頁面不在棧頂,則需要往回退棧。如此會(huì)導(dǎo)致中途被退棧的頁面狀態(tài)丟失,這個(gè)方案無法支持同時(shí)存在多個(gè)平級邏輯頁面的情況,因?yàn)樵谇袚Q頁面的時(shí)候必須從棧頂操作,無法在保持狀態(tài)的同時(shí)進(jìn)行平級切換。圖1-2035舉個(gè)例子:有兩個(gè)頁面A和B,當(dāng)前頁面B在棧頂。切換到頁面A需要把頁面B從棧頂POP出去,此時(shí)頁面B的狀態(tài)丟失,如果想切回頁面B,只能重新打開,頁面B之前頁面的狀態(tài)無法維持住。這也是老方案最大的一個(gè)局限。而且基于棧的操作,我們依賴對Flutter框架的一個(gè)屬性修改,讓這個(gè)方案具有了侵入性的特點(diǎn),這是另一個(gè)問題。1.5.3第二代混合技術(shù)方案FlutterBoost1.重構(gòu)計(jì)劃閑魚在推進(jìn)Flutter化過程當(dāng)中,遇到了更加復(fù)雜的頁面場景,也逐漸暴露了老方案的局限性和一些問題。所以,閑魚啟動(dòng)了代號(hào)為FlutterBoost的新混合技術(shù)方案。我們的主要目標(biāo)有:可復(fù)用通用型混合方案。支持更加復(fù)雜的混合模式,例如支持主頁Tab。無侵入性方案,不再依賴修改Flutter的方案。支持通用頁面生命周期。統(tǒng)一明確的設(shè)計(jì)概念。跟老方案類似,新的方案仍采用共享引擎的模式實(shí)現(xiàn)。主要思路是由Native容器通過消息驅(qū)動(dòng)Flutter頁面容器,從而達(dá)到Native容器與Flutter容器的同步目的。希望做到Flutter渲染的內(nèi)容是由Naitve容器來驅(qū)動(dòng)的。簡單地理解,閑魚想把Flutter容器做得像瀏覽器一樣。填寫一個(gè)頁面地址,然后由容器管理頁面的繪制。在Native側(cè),開發(fā)者只需要關(guān)心如何初始化容器,然后設(shè)置容器對應(yīng)的頁面標(biāo)志即可。36>第一章混合工程2.主要概念(如圖1-21)圖1-21(1)Native層.container:Native容器、平臺(tái)controller、Activity和viewcontroller..containerManager:容器的管理者。Adaptor:Flutter是適配層。.Messaging:基于channel的消息通信。(2)Dart層container:Flutter用來容納widget的容器,具體實(shí)現(xiàn)為Navigator的派生類。.containerManager:Flutter容器的管理者,提供show、remove等API.coordinator:協(xié)調(diào)器,接受Messaging消息,負(fù)責(zé)調(diào)用containerManager的狀態(tài)管理。.Messaging:基于channel的消息通信。37(3)關(guān)于頁面的理解在Native和Flutter中,表示頁面的對象和概念是不一致的。在Native中,對頁面概念。換句話說,當(dāng)一個(gè)Native的頁面容器存在的時(shí)候,FlutteBoost保證一在FlutterBoost的概念里說到頁面的時(shí)候,指的是Native容器和它所附屬的的直接操作。無論路由請求來自何方,最終都會(huì)轉(zhuǎn)發(fā)給Native實(shí)現(xiàn)路由操作。這也是接入FlutterBoost時(shí)需要實(shí)現(xiàn)platform協(xié)議的原因。widget。對于業(yè)務(wù)不通過FlutterBoost而直接使用Navigator操作widget的情義的頁面概念。理解這里的頁面概念,對于理解和使用FlutterBoost至關(guān)重要。3.與老方案的主要差別則是在Dart側(cè)引入了container的概念,不再用棧的結(jié)構(gòu)維護(hù)現(xiàn)有的頁面,而是通的ID地址。這種結(jié)構(gòu)很自然地支持了頁面的查找和切換,不再受制于棧頂操作,一些由于pop導(dǎo)致的問題迎刀而解。同時(shí),也不再依賴修改Flutter源碼的形式去實(shí)現(xiàn),避免了實(shí)現(xiàn)的侵入性。這是如何做到的呢?含的頁面也就是當(dāng)前可見容器所對應(yīng)的頁面。38Native容器與Flutter容器(Navigator)是-—對應(yīng)的,生命周期也是同步的。當(dāng)一個(gè)Native容器被創(chuàng)建的時(shí)候,Flutter的一個(gè)容器也被創(chuàng)建,它們通過相同的ID地址關(guān)聯(lián)起來。當(dāng)Native的容器被銷毀的時(shí)候,Flutter的容器也被銷毀。Flutter容換當(dāng)前在屏幕上展示的容器。下面用一個(gè)簡單的例子描述一個(gè)新頁面創(chuàng)建的過程:.創(chuàng)建Native容器(iosviewcontroller,AndroidActivityorFragment)。Native容器通過消息機(jī)制通知Fluttercoordinator新的容器被創(chuàng)建。FluttercontainerManager得到通知,負(fù)責(zé)創(chuàng)建出對應(yīng)的Flutter容器,并且在其中裝載對應(yīng)的widget頁面。當(dāng)Native容器展示到屏幕上時(shí),容器給Fluttercoordinator發(fā)消息,通知要展示頁面的ID地址。.FluttercontainerManager找到對應(yīng)ID地址的Fluttercontainer并將其設(shè)置為前臺(tái)可見容器。這就是一個(gè)新頁面創(chuàng)建的主要邏輯,銷毀和進(jìn)入后臺(tái)等操作也類似由Native容器事件去驅(qū)動(dòng)。目前,FlutterBoost已經(jīng)在生產(chǎn)環(huán)境支撐閑魚客戶端中所有的基于Flutter開發(fā)的業(yè)務(wù),為更加負(fù)復(fù)雜的混合場景提供了支持,同時(shí)也解決了一些歷史遺留問題。閑魚在項(xiàng)目啟動(dòng)之初就希望FlutterBoost能夠解決Flutter這個(gè)通用問題。所以把它做成了一個(gè)可復(fù)用的Flutter插件,希望吸引更多感興趣的朋友參與到Flutter社區(qū)的建設(shè)中來。閑魚的方案可能不是最好的,希望看到社區(qū)能夠涌現(xiàn)出更加優(yōu)秀的組件和方案。1.5.4擴(kuò)展補(bǔ)充1.性能相關(guān)在對兩個(gè)Flutter頁面進(jìn)行切換時(shí),因?yàn)橹挥幸粋€(gè)Flutterview,所以需要對上一個(gè)頁面進(jìn)行截圖保存。如果Flutter頁面較多,則截圖會(huì)占用大量內(nèi)存。這里采用文件內(nèi)存二級緩存策略,在內(nèi)存中最多只保存2~3個(gè)截圖,其余的截圖在寫入文件時(shí)按需加載。這樣一來,可以在保證用戶體驗(yàn)的同時(shí),使內(nèi)存也保持在一個(gè)較為穩(wěn)定的水平。在頁面渲染性能方面,Flutter的AOT優(yōu)勢展露無遺。當(dāng)頁面快速切換的時(shí)候,Flutter能夠很靈敏地進(jìn)行相應(yīng)頁面的切換,在邏輯上創(chuàng)造出一種Flutter有多個(gè)頁面的感覺。2.Release1.0支持在項(xiàng)目開始的時(shí)候,閑魚基于目前使用的Flutter版本進(jìn)行開發(fā),而后進(jìn)行了Release1.0兼容升級測試且沒有發(fā)現(xiàn)問題。3.接入只要是集成了Flutter的項(xiàng)目,都可以用官方依賴的方式,非常方便地以插件形式引入FlutterBoost,只需要對工程進(jìn)行少量代碼接入即可。詳細(xì)接入文檔,請參閱GitHub主頁官方項(xiàng)目文檔。第二章能力增強(qiáng)閑魚在開發(fā)Flutter過程中,經(jīng)常會(huì)需要具備各種Native的能力,如獲取設(shè)備信的問題和解決方案。2.1.1Flutterplugin如圖2-1所示,Flutter的上層能力都是由Engine提供的。Flutter正是通過Engine將各個(gè)platform的差異化抹平。而本章要講的plugin,正是通過Engine提供的platformchannel實(shí)現(xiàn)的通信。2.1.2platformchannel1.FlutterAPP調(diào)用NativeAPIS如圖2-2所示,FlutterAPP通過plugin創(chuàng)建的platformchannel來調(diào)用NativeAPIS。圖2-22.platformchannel架構(gòu)圖(圖2-3)圖2-342(1)platformchanne.FlutterAPP(client),通過Methodchannel類向platform發(fā)送調(diào)用消息;Androidplatform(Host),通過Methodchannel類接收調(diào)用消息;iosplatform(Host),通過FlutterMethodchannel類接收調(diào)用消息。消息編解碼器是JSON格式的二進(jìn)制序列化,所以調(diào)用方法的參數(shù)類型必須是可JSON序列化的。除了方法調(diào)用,也可以反向發(fā)送調(diào)用消息。(2)AndroidplatformFlutterActivity是Android的plugin管理器,它記錄了所有的plugin,并將plugin綁定到Flutterview.(3)iosplatformFlutterAPPDelegate是ios的plugin管理器,它記錄了所有的plugin,并將plugin綁定到Fluterviewcontroller(默認(rèn)是rootviewcontroller).如圖2-4所示。圖2-4431.創(chuàng)建plugin首先,我們創(chuàng)建一個(gè)plugin(flutterpluginbatterylevelplugin也是項(xiàng)目,只是projecttype不同。(1)進(jìn)入IntelliJ歡迎界面,單擊"createNewproject"或者"File"I"New"I"project…"按鈕;(2)在左側(cè)菜單選擇"Flutter",然后單擊"Next"按鈕;(3)輸入projectname和projectlocation,projecttype選擇"plugin";(4)最后單擊"Finish"按鈕。圖2-5其中,projecttype包括:(1)Application:Flutter應(yīng)用;(2)plugin:給Flutter應(yīng)用暴露Android和ios的API;(3)package:封裝一個(gè)Da

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論