Flutter混合開發(fā)組件化與工程化架構(gòu)

一、簡(jiǎn)述

對(duì)于構(gòu)建Flutter類型應(yīng)用,因其開發(fā)語言Dart、虛擬機(jī)、構(gòu)建工具與平時(shí)我們開發(fā)Native應(yīng)用不同且平臺(tái)虛擬機(jī)也不支持,所以需要Flutter SDK來支持,如構(gòu)建Android應(yīng)用需要Android SDK一樣,下載Flutter SDK通常有兩種方式:

1.在官網(wǎng)下載構(gòu)建好的zip包,里面包含完整的Flutter基礎(chǔ)Api,Dart VM,Dart SDK等
2.手動(dòng)構(gòu)建,Clone Flutter源碼后,運(yùn)行flutter --packages get或其它具有檢測(cè)類型的命令如build、doctor,這時(shí)會(huì)自動(dòng)構(gòu)建和下載Dart SDK以及Flutter引擎產(chǎn)物

在團(tuán)隊(duì)多人協(xié)作開發(fā)下,F(xiàn)lutter SDK可能遇到的問題

在團(tuán)隊(duì)多人協(xié)作開發(fā)下,這種依賴每個(gè)開發(fā)本地下載Flutter SDK的方式,不能保證Flutter SDK的版本一致性與自動(dòng)化管理,在開發(fā)時(shí)如果Flutter SDK版本不一致,往往會(huì)出現(xiàn)Dart層Api兼容性或Flutter虛擬機(jī)不一致等問題,因?yàn)槊總€(gè)版本的Flutter都有各自對(duì)應(yīng)的Flutter虛擬機(jī),構(gòu)建產(chǎn)物中會(huì)包含對(duì)應(yīng)構(gòu)建版本的虛擬機(jī)。

Flutter工程的構(gòu)建需要Flutter標(biāo)準(zhǔn)的工程結(jié)構(gòu)目錄和依賴于本地的Flutter環(huán)境,
每個(gè)對(duì)應(yīng)Flutter工程都有對(duì)應(yīng)的Flutter SDK路徑,Android在local.properties中,IOS在Generated.xcconfig中。

// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/bd/Documents/development/flutter
FLUTTER_APPLICATION_PATH=/Users/bd/flutter/bcc/cc/biz/flutter_biz/example
FLUTTER_TARGET=/Users/bd/flutter/baidu/bcc/cc/flutter_biz/example/lib/main.dart
FLUTTER_BUILD_DIR=build
SYMROOT=${SOURCE_ROOT}/../build/ios
FLUTTER_FRAMEWORK_DIR=/Users/bd/Documents/development/flutter/bin/cache/artifacts/engine/ios
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
TRACK_WIDGET_CREATION=true

這個(gè)路徑會(huì)在Native工程本地依賴Flutter工程構(gòu)建時(shí)讀取,并從中獲取引擎、資源和編譯構(gòu)建Flutter工程,而調(diào)用flutter命令時(shí)構(gòu)建Flutter工程則會(huì)獲取當(dāng)前flutter命令所在的Flutter SDK路徑,并從中獲取引擎、資源和編譯構(gòu)建Flutter工程,所以flutter命令構(gòu)建環(huán)境與Flutter工程中平臺(tái)子工程的環(huán)境變量一定得保持一致,且這個(gè)環(huán)境變量是隨flutter執(zhí)行動(dòng)態(tài)改變的,團(tuán)隊(duì)多人協(xié)作下這個(gè)得保證,在打包Flutter工程的正式版每個(gè)版本也應(yīng)該有一個(gè)對(duì)應(yīng)的Flutter構(gòu)建版本,不管是本地打包還是在打包平臺(tái)打包

我們知道Flutter應(yīng)用的工程結(jié)構(gòu)都與Native應(yīng)用工程結(jié)構(gòu)不一樣,不一致地方主要是Native工程是作為Flutter工程子工程,外層通過Pub進(jìn)行依賴管理,

這樣通過依賴下來的Flutter Plugin/Package代碼即可與多平臺(tái)共享,(插件共享)

在打包時(shí)Native子工程只打包工程代碼與Pub所依賴庫(kù)的平臺(tái)代碼,

Flutter工程則通過flutter_tools打包lib目錄下以及Pub所依賴庫(kù)的Dart代碼。
打包產(chǎn)物為Flutter.framework和App.framework

回到正題,因工程結(jié)構(gòu)的差異,如果基于現(xiàn)有的Native工程想使用Flutter來開發(fā)其中一個(gè)功能模塊,一般來說混合開發(fā)至少得保證如下特點(diǎn):

  1. 對(duì)Native工程無侵入
  2. 對(duì)Native工程零耦合
  3. 不影響Native工程的開發(fā)流程與打包流程
  4. 易本地調(diào)試

顯然改變工程結(jié)構(gòu)的方案可以直接忽略,官方也提供了一種Flutter本地依賴到現(xiàn)有Native的方案,不過這種方案不加改變優(yōu)化而直接依賴的話,則會(huì)直接影響了其它無Flutter環(huán)境的開發(fā)同學(xué)的開發(fā),影響開發(fā)流程,且打包平臺(tái)也不支持這種依賴方式的打包

二、Flutter四種工程類型

Flutter工程中,通常有以下幾種工程類型,下面分別簡(jiǎn)單概述下:

1. Flutter Application

標(biāo)準(zhǔn)的Flutter App工程,包含標(biāo)準(zhǔn)的Dart層與Native平臺(tái)層

2. Flutter Module

Flutter組件工程,僅包含Dart層實(shí)現(xiàn),Native平臺(tái)層子工程為通過Flutter自動(dòng)生成的隱藏工程

3. Flutter Plugin

Flutter平臺(tái)插件工程,包含Dart層與Native平臺(tái)層的實(shí)現(xiàn)

4. Flutter Package

Flutter純Dart插件工程,僅包含Dart層的實(shí)現(xiàn),往往定義一些公共Widget

三、Flutter工程Pub依賴管理

Flutter工程之間的依賴管理是通過Pub來管理的,依賴的產(chǎn)物是直接源碼依賴,這種依賴方式和IOS中的Pod有點(diǎn)像,都可以進(jìn)行依賴庫(kù)版本號(hào)的區(qū)間限定與Git遠(yuǎn)程依賴等,其中具體聲明依賴是在pubspec.yaml文件中,其中的依賴編寫是基于YAML語法,YAML是一個(gè)專門用來編寫文件配置的語言

聲明依賴后,通過運(yùn)行flutter packages get命名,會(huì)從遠(yuǎn)程或本地拉取對(duì)應(yīng)的依賴,同時(shí)會(huì)生成pubspec.lock文件,這個(gè)文件和IOS中的Podfile.lock極其相似,會(huì)在本地鎖定當(dāng)前依賴的庫(kù)以及對(duì)應(yīng)版本號(hào),只有當(dāng)執(zhí)行flutter packages upgrade時(shí),這時(shí)才會(huì)更新,同樣pubspec.lock文件也需要作為版本管理文件提交到Git中,而不應(yīng)gitignore

四、Flutter鏈接到Native工程原理

官方提供了一種本地依賴到現(xiàn)有的Native工程方式,具體可看官方wiki:Flutter本地依賴,這種方式太依賴于本地環(huán)境和侵入Native工程會(huì)影響其它開發(fā)同學(xué),且打包平臺(tái)不支持這種方式的打包,所以肯定得基于這種方式進(jìn)行優(yōu)化改造,這個(gè)后面再說,先說說Native兩端本地依賴的原理

1. iOS

在iOS中本地依賴方式為:

  1. 在Podfile中通過eval binding特性注入podhelper.rb腳本,在pod install/update時(shí)會(huì)執(zhí)行它
  2. 在iOS構(gòu)建階段Build Phases中注入構(gòu)建時(shí)需要執(zhí)行的xcode_backend.sh腳本

對(duì)于iOS的本地依賴,主要是由podhelper.rb和xcode_backend.sh這兩個(gè)腳本負(fù)責(zé)Flutter的Pod本地依賴和產(chǎn)物構(gòu)建

1. podhelper.rb

因Podfile是通過ruby語言寫的,所以該腳本也是ruby腳本,該腳本在pod install/update時(shí)主要做了三件事:

  1. Pod本地依賴Flutter引擎(Flutter.framework)與Flutter插件注冊(cè)表(FlutterPluginRegistrant)
  1. Pod本地源碼依賴.flutter-plugins文件中包含的Flutter工程路徑下的ios工程
  1. 在pod install執(zhí)行完后post_install中,獲取當(dāng)前target工程對(duì)象,導(dǎo)入Generated.xcconfig配置,這些配置都為環(huán)境變量配置,主要為構(gòu)建階段xcode_backend.sh腳本執(zhí)行做準(zhǔn)備

上述事情即可保證Flutter工程以及傳遞依賴的都通過pod本地依賴進(jìn)Native工程了,接下來就是構(gòu)建了

2. xcode_backend.sh

該Shell腳本位于Flutter SDK中,該腳本主要就做了兩件事:

  1. 調(diào)用flutter命令編譯構(gòu)建出產(chǎn)物(App.framework、flutter_assets)
  1. 把產(chǎn)物(*.framework、flutter_assets)拷貝到對(duì)應(yīng)XCode構(gòu)建產(chǎn)物中

五、Flutter與Native通信

Flutter與Native通信有三種方式,這里只簡(jiǎn)單介紹下:

  1. MethodChannel:方法調(diào)用
  2. EventChannel:事件監(jiān)聽
  3. BasicMessageChannel:消息傳遞

Flutter與Native通信都是雙向通道,可以互相調(diào)用和消息傳遞

六、Flutter版本一致性與自動(dòng)化管理

七、Flutter混合開發(fā)組件化架構(gòu)

上述說的如果我們要利用Flutter來開發(fā)我們現(xiàn)有Native工程中的一個(gè)模塊或功能,肯定得不能改變Native的工程結(jié)構(gòu)以及不影響現(xiàn)有的開發(fā)流程,那么,以何種方式進(jìn)行混合開發(fā)呢?

前面說到Flutter的四種工程模型:

1.Flutter App我們可以直接忽略,因?yàn)檫@是一個(gè)開發(fā)全新的Flutter App工程。

2.對(duì)于Flutter Module,官方提供的本地依賴便是使用Flutter Module依賴到Native App的,而對(duì)于Flutter工程來說,構(gòu)建Flutter工程必須得有個(gè)main.dart主入口,恰好Flutter Module中也有主入口。

Flutter Module模式產(chǎn)生的問題:

于是,我們進(jìn)行組件劃分,通過Flutter Module作為所有通過Flutter實(shí)現(xiàn)的模塊或功能的聚合入口,通過它進(jìn)行Flutter層到Native層的雙向關(guān)聯(lián)。而Flutter開發(fā)代碼寫在哪里呢?當(dāng)然可以直接寫在Flutter Module中,這沒問題,而如果后續(xù)開發(fā)了多個(gè)模塊、組件,我們的Dart代碼總不可能全部寫在Flutter Module中l(wèi)ib/吧,如果在lib/目錄下再建立子目錄進(jìn)行模塊區(qū)分,這不失為一種最簡(jiǎn)單的方式,不過這會(huì)帶來一些問題,所有模塊共用一個(gè)遠(yuǎn)程Git地址,首先在組件開發(fā)隔離上完全耦合了,其次各個(gè)模塊組件沒有單獨(dú)的版本號(hào)或Tag,且后續(xù)模塊組件的增多,帶來更多的測(cè)試回歸成本

正確的組件化方式為一個(gè)組件有一個(gè)獨(dú)立的遠(yuǎn)程Git地址管理,這樣各個(gè)組件在發(fā)正式版時(shí)都有一個(gè)版本號(hào)和Tag,且在各個(gè)組件開發(fā)上完全隔離,后續(xù)組件的增多不影響其它組件,某個(gè)組件新增需求而不需回歸其它組件,帶來更低的測(cè)試成本

  1. Flutter Plugin模式

前面提到Flutter Plugin可以有對(duì)應(yīng)Dart層代碼與平臺(tái)層的實(shí)現(xiàn),所以可以這樣設(shè)計(jì),一個(gè)組件對(duì)應(yīng)一個(gè)Flutter Plugin,一個(gè)Flutter Plugin為一個(gè)完整的Flutter工程,有獨(dú)立的Git地址,而這些組件之間不能互相依賴,保持零耦合,所以這些組件都在業(yè)務(wù)層,可以叫做業(yè)務(wù)組件,這些業(yè)務(wù)組件之間的通信和公共服務(wù)可以再劃分一層基礎(chǔ)層,可以叫做基礎(chǔ)組件,所有業(yè)務(wù)組件依賴基礎(chǔ)層,而Flutter Module作為聚合層依賴于所有Flutter組件,這些Flutter工程之間的依賴正是通過Pub依賴進(jìn)行管理的

所以,綜合上述,整體的組件化架構(gòu)可以設(shè)計(jì)為:

image.png

盡量讓Native平臺(tái)層成為服務(wù)層,讓Flutter層成為消費(fèi)層調(diào)用Native層的服務(wù),即Dart調(diào)用Native的Api,這樣當(dāng)兩端開發(fā)人員編寫好一致基礎(chǔ)的服務(wù)接口后,F(xiàn)lutter的開發(fā)人員即可平滑使用和開發(fā)

而對(duì)于基礎(chǔ)組件中的公共服務(wù)組件Dart Api層的設(shè)計(jì),因?yàn)楣卜?wù)主要調(diào)用Native層的服務(wù),在Flutter中提供公共的Dart Api,作為Native到Flutter的一個(gè)橋梁,對(duì)于Native的服務(wù),會(huì)有很有多種,而對(duì)應(yīng)Api的設(shè)計(jì)為一個(gè)dart文件對(duì)應(yīng)一個(gè)種類的服務(wù),整個(gè)公共服務(wù)組件提供一個(gè)統(tǒng)一個(gè)對(duì)外暴露的Dart,內(nèi)部的細(xì)粒度的Dart實(shí)現(xiàn)通過export導(dǎo)入,這種設(shè)計(jì)思想正是Flutter官方Api的設(shè)計(jì),即統(tǒng)一對(duì)外暴露的Dart為common_service.dart:

library common_service;

export 'network_plugin.dart';
export 'messager_plugin.dart';
...

而上層業(yè)務(wù)組件調(diào)用Api只需要import一個(gè)dart即可,這樣對(duì)上層業(yè)務(wù)組件開發(fā)人員是透明的,上層不需要了解有哪些Api可用:

import 'package:common_service/common_service.dart';

八、Flutter混合開發(fā)工程化架構(gòu)

基本組件化的架構(gòu)我們搭建好了,接下來是如何讓Flutter混合開發(fā)進(jìn)行完整的工程化管理,我們都知道,對(duì)于官方的本地依賴這種方式,我們不能直接用,因?yàn)檫@會(huì)直接影響Native工程、開發(fā)流程與打包流程,所以我們得基于官方這種依賴方式進(jìn)行優(yōu)化改造,于是我們衍生出兩種Flutter鏈接到Native工程的方式:

  1. 本地依賴(源碼依賴)
  2. 遠(yuǎn)程依賴(產(chǎn)物依賴)

為什么要有這兩種方式,首先本地依賴對(duì)于打包平臺(tái)不支持,現(xiàn)有打包平臺(tái)的環(huán)境,只能支持標(biāo)準(zhǔn)的Gradle工程結(jié)構(gòu)進(jìn)行打包,且本地依賴對(duì)于無需開發(fā)Flutter相關(guān)業(yè)務(wù)的同學(xué)來說是災(zāi)難性的,所以便有了遠(yuǎn)程依賴,遠(yuǎn)程依賴直接依賴于打包好的Flutter產(chǎn)物,Android通過Gradle依賴,IOS通過Pod遠(yuǎn)程依賴,這樣對(duì)其它業(yè)務(wù)開發(fā)同學(xué)來說是透明的,他們無需關(guān)心Flutter也不需要知道Flutter是否存在

對(duì)于這兩種依賴模式的使用環(huán)境也各不一樣

  1. 本地依賴
    本地依賴主要用于需要進(jìn)行Flutter開發(fā)的同學(xué),通過在對(duì)應(yīng)Native工程中配置文件配置是否打開本地Flutter Module依賴,以及配置鏈接的本地Flutter Module地址,這樣Native工程即可自動(dòng)依賴到本地的Flutter工程,整個(gè)過程是無縫的,同時(shí)本地依賴是通過源碼進(jìn)行依賴的,也可以很方便的進(jìn)行Debug調(diào)試
  1. 遠(yuǎn)程依賴
    遠(yuǎn)程依賴是把Flutter Module的構(gòu)成產(chǎn)物發(fā)布到遠(yuǎn)程,然后在Native工程中遠(yuǎn)程依賴,這種依賴方式是默認(rèn)的依賴方式,這樣對(duì)其它開發(fā)同學(xué)來說是透明的,不影響開發(fā)流程和打包平臺(tái)

上述說到的兩種依賴方式,接下來主要說怎么進(jìn)行這兩種依賴方式的工程化管理和定制化

  1. 遠(yuǎn)程依賴產(chǎn)物打包流程

iOS上的打包相比Android來說更復(fù)雜一些,我們借助.ios/Runner來打包出靜態(tài)庫(kù)等產(chǎn)物,所以還需要設(shè)置簽名,通過在Flutter Module中直接執(zhí)行./flutterw build ios --release,該命令會(huì)自動(dòng)執(zhí)行pod install,所以我們不必再單獨(dú)執(zhí)行它,IOS中構(gòu)建出的產(chǎn)物獲取也相對(duì)繁瑣些,除了獲取Flutter的相關(guān)產(chǎn)物,還需要獲取所依賴的各組件的靜態(tài)庫(kù)以及頭文件,需要獲取的產(chǎn)物如下:

Flutter.framework
App.framework
FlutterPluginRegistrant
flutter_assets
所有依賴的Plugin的.a靜態(tài)庫(kù)以及頭文件

其中Flutter.framework為Flutter引擎,類似Android中的flutter.so,而App.framework則是Flutter中Dart編譯后的產(chǎn)物(Debug模式下它僅為一個(gè)空殼,具體Dart代碼在flutter_assets中,Release模式下為編譯后的機(jī)器指令),F(xiàn)lutterPluginRegistrant是所有插件Channel的注冊(cè)表,也是自動(dòng)生成的,flutter_assets含字體等資源,剩下一些.a靜態(tài)庫(kù)則是各組件在IOS平臺(tái)層的實(shí)現(xiàn)了

而收集IOS產(chǎn)物除了在.ios/Flutter目錄下收集*.framework靜態(tài)庫(kù)和flutter_assets外,剩下的就是收集.a靜態(tài)庫(kù)以及對(duì)應(yīng)的頭文件了,而這些產(chǎn)物則是在構(gòu)建Runner工程后,在Flutter Module下的

build/ios/$variant-iphoneos

目錄下,variant對(duì)應(yīng)所構(gòu)建變體名,我們還是通過解析.flutter-plugins文件,來獲取對(duì)應(yīng)所依賴Flutter插件的名稱,進(jìn)而在上述的輸出目錄下找到對(duì)應(yīng)的.a靜態(tài)庫(kù),但是對(duì)應(yīng)的頭文件而不在對(duì)應(yīng).a靜態(tài)庫(kù)目錄下,所以對(duì)于頭文件單獨(dú)獲取,因?yàn)榻馕隽?flutter-plugins獲取到了KV鍵值對(duì),對(duì)應(yīng)的V則是該Flutter插件工程地址,所以頭文件我們從里面獲取

最后還需要獲取FlutterPluginRegistrant注冊(cè)表的靜態(tài)庫(kù)以及頭文件

參考:
http://zhengxiaoyong.com/2018/12/16/Flutter%E6%B7%B7%E5%90%88%E5%BC%80%E5%8F%91%E7%BB%84%E4%BB%B6%E5%8C%96%E4%B8%8E%E5%B7%A5%E7%A8%8B%E5%8C%96%E6%9E%B6%E6%9E%84/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容