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

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

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

溫馨提示

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

評論

0/150

提交評論