[TOC]ios
爲了項目能夠支持Flutter和Native混合開發的模式,咱們須要在對原生項目無侵入的條件下接入flutter,原生項目直接依賴flutter項目產物,以下圖所示:git
安裝flutter,自行百度;任意目錄下執行flutter create -t module my_flutter
,"my_flutter"
是要建立的 Flutter 工程的名稱。shell
在Podfile
添加如下下代碼json
flutter_application_path = "xxx/xxx/my_flutter"
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
複製代碼
而後執行pod install
這個ruby腳本主要作下面4件事情:xcode
Build Phases
,點擊頂部的 + 號,選擇 New Run Script Phase
,而後輸入如下腳本"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
複製代碼
這裏執行的flutter包根目錄shell腳本的做用:ruby
這裏就有了一個問題,Flutter 工程依賴 Native工程來執行編譯,影響Native工程的開發流程與打包流程,開發Native的人也須要安裝Flutter環境才能調試APPbash
以上操做能夠簡單的理解爲,Native工程配置好腳本後,運行時會先編譯Flutter項目,Flutter項目會在本身的相應目錄生成Flutter.framework、依賴的Native插件等產物,最終在pod中配置好路徑等參數,經過pod本地依賴的方式集成了flutter。網絡
基於官方的方案,爲了實現這個目標,須要實現如下2點:app
在項目目錄中加入build_ios.sh文件,腳本自動打包 Flutter 工程大體分爲一下幾個步驟:框架
flutter_get_packages()
:檢查 Flutter 環境,拉取 Flutter pluginbuild_flutter_app()
:編譯 Flutter 工程獲得產物並copy到特定文件路徑下,主要邏輯和官方提供的xcode_backend.sh
腳本差很少flutter_copy_packages()
:獲得 Flutter 產物中的 Native 插件,並copy到特定文件路徑下upload_product()
:release模式中將產物同步上傳到git中執行./build_ios.h -m debug
./build_ios.h -m release
獲得不一樣環境的產物,並上傳遠程倉庫
這部分咱們須要實現獲取 Flutter 工程 release 產物,並集成到 Native 項目,並保留能夠依賴本地 Flutter 工程的能力。 在原生項目中加入flutterhelper.rb
腳本,分爲以下幾個步驟:
install_release_flutter_app
:clone遠程倉庫中的Flutter產物到本地install_debug_flutter_app
:在 Flutter工程路徑下,執行 build_ios.sh -m debug 進行打包,而後獲得 debug 產物目錄install_release_flutter_app_pod
:遍歷Flutter產物目錄,使用pod sub, :path=>sub_abs_path
依賴Flutter.FrameWork、Native插件等podfile
中配置以下:
# 爲true時,debug環境 爲false時,release環境
FLUTTER_DEBUG_APP=true
# 若是指定了FLUTTER_APP_PATH,則此配置失效
FLUTTER_APP_URL= "http://appinstall.aiyoumi.com:8282/flutter/iOS_flutter_product.git"
# flutter git 分支,默認爲master
# 若是指定了FLUTTER_APP_PATH,則此配置失效
FLUTTER_APP_BRANCH="master"
# flutter本地工程目錄,絕對路徑或者相對路徑,若是有值則git相關的配置無效
FLUTTER_APP_PATH="/Users/zouyongfeng/ac_flutter_module"
eval(File.read(File.join(__dir__, 'flutterhelper.rb')), binding)
複製代碼
最後在jenkins中配置好打包job便可,以下:
cd ${WORKSPACE}
if [[ ! -d "${FLUTTER_PROJECT_Name}" ]]; then
git clone ${FLUTTER_PROJECT_GIT_REPO} ${FLUTTER_PROJECT_Name} -b ${PROJECT_GIT_BRANCH}
fi
if [[ ! -d "${FLUTTER_PRODUCT_Name}" ]]; then
git clone ${FLUTTER_PRODUCT_GIT_REPO} ${FLUTTER_PRODUCT_Name} -b ${PROJECT_GIT_BRANCH}
fi
cd ${WORKSPACE}/${FLUTTER_PRODUCT_Name}
git fetch
git reset --hard
git checkout ${PROJECT_GIT_BRANCH}
git pull --no-commit --all
cd ${WORKSPACE}/${FLUTTER_PROJECT_Name}
git fetch
git reset --hard
git checkout ${PROJECT_GIT_BRANCH}
git pull --no-commit --all
source ~/.bash_profile
sh build_ios.sh -m release
複製代碼
Flutter提供了FlutterMethodChannel實現了Flutter調用原生方法的功能,以下:
//native中
FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
[flutterViewController setInitialRoute:@"myApp"];
__weak __typeof(self) weakSelf = self;
// 要與main.dart中一致
NSString *channelName = @"com.pages.your/native_get";
FlutterMethodChannel *messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController];
[messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqualToString:@"iOSFlutter"]) {
TargetViewController *vc = [[TargetViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
if (result) {
result(@"返回給flutter的內容");
}
}
}];
//flutter中
// 建立一個給native的channel
static const methodChannel = const MethodChannel('com.pages.your/native_get');
_iOSPushToVC() async {
dynamic result;
result = await methodChannel.invokeMethod('iOSFlutter', '參數');
}
複製代碼
Flutter提供了FlutterEventChannel來完成原生調用Flutter
// native中
FlutterEventChannel *evenChannal = [FlutterEventChannel eventChannelWithName:channelName binaryMessenger:flutterViewController];
// 代理FlutterStreamHandler
[evenChannal setStreamHandler:self];
#pragma mark - <FlutterStreamHandler>
// 這個onListen是Flutter端開始監聽這個channel時的回調,第二個參數 EventSink是用來傳數據的載體。
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
eventSink:(FlutterEventSink)events {
// arguments flutter給native的參數
if (events) {
events(@"push傳值給flutter的vc");
}
return nil;
}
// flutter中
// 註冊一個通知
static const EventChannel eventChannel = const EventChannel('com.pages.your/native_post');
// 監聽事件,同時發送參數
eventChannel.receiveBroadcastStream(12345).listen(_onEvent,onError: _onError);
String naviTitle = 'title' ;
// 回調事件
void _onEvent(Object event) {
setState(() {
naviTitle = event.toString();
});
}
複製代碼
以上就是官方提供的混合開發方案了,這個方案有一個巨大的缺點,就是在原生和Flutter頁面疊加跳轉時內存不斷增大,由於FlutterView和FlutterViewController每次跳轉都會新建一個對象,建立的Flutter頁面越多內存就會暴增,尤爲是在iOS上還有內存泄露的問題。
Flutter View
當成一個畫布,而後用一個
Native
的容器做爲邏輯的頁面。每次在打開一個容器的時候咱們經過通訊機制通知
Flutter View
繪製成當前的邏輯頁面,而後將Flutter View放到當前容器裏面。
頁面棧徹底由原生控制,每個flutter
頁面對應一個原生容器(ViewController
和Activity
),原生端建立FlutterRouter
實現FLBPlatform
中的接口,flutter和原生的相互調用都會執行FlutterRouter
中的openPage
接口。代碼以下:
// iOS: FlutterRouter
- (void)openPage:(NSString *)name params:(NSDictionary *)params animated:(BOOL)animated completion:(void (^)(BOOL finished))completion {
[ACRouter openWithURLString:name userInfo:params completion:^(ACRouterOutModel * _Nonnull outModel) {
[FlutterBoostPlugin.sharedInstance onResultForKey:[params objectForKey:requestIdKey] resultData:outModel.data params:@{}];
if(completion) completion(YES);
}];
}
複製代碼
flutter端創建ACRouter
封裝flutterboost
,flutter跳轉原生頁面直接調用原生項目中的路由
// flutter中:
// 傳遞協議名和頁面所需初始化參數
ACRouter.openUrl("mizlicai://product/normalProductDetail", {'serial': 'PI_11221'},
routeCallback: (Map<dynamic, dynamic> result) {
// 處理回調結果
print("did recieve second route result $result");
});
// Native中:
// TODO:普通產品詳情
[ACRouter registerWithURLString:@"mizlicai://product/normalProductDetail" handler:^(NSDictionary * _Nullable paramsIn) {
ProductDetailViewController *vc = [[ProductDetailViewController alloc] init];
vc.serial = [paramsIn valueForKey:@"serial"];
vc.origin = [paramsIn valueForKey:@"origin"];
[[UIViewController mz_topController].navigationController pushViewController:vc animated:YES];
}];
複製代碼
flutter端和原生打開flutter頁面
// 原生中
[ACRouter registerWithURLString:@"mizlicai://flutter/open" handler:^(NSDictionary * _Nullable paramsIn) {
NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithDictionary:paramsIn[@"params"]];
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:paramsIn[@"pageName"] params:params];
[[UIViewController mz_topController].navigationController pushViewController:vc animated:animated];
ACRouterCompletionBlock action = paramsIn[ACRouterParameterCompletion];
if (action) {
ACRouterOutModel *outModel = [[ACRouterOutModel alloc] init];
action(outModel);
}
}];
//flutter中
ACRouter.openUrl("mizlicai://flutter/open", {'pageName': 'userCenter','params':{},
routeCallback: (Map<dynamic, dynamic> result) {
// 處理回調結果
print("did recieve second route result $result");
});
複製代碼
#####2.協議支持 flutter能夠調用原生項目組件化的路由協議(米莊iOS路由協議),來跳轉原生頁面、調用原生接口等。 #####3.網絡數據請求 爲了保持和原生請求框架保持同一份邏輯,使用抽象類的方式封裝請求工具,Flutter啓動時判斷環境,使用真實請求類仍是Mock請求類。
// main.dart
if (ApiClient.isProduction) {
ApiClient.request = RealRequest();
} else {
ApiClient.request = MockRequest();
}
複製代碼
MockRequest和RealRequest分別實現父類send方法,RealRequest經過ACRouter調用原生髮起網絡請求,MockRequest解析本地json
// 發起請求
ApiClient.request.send(Api.userCenter, HttpRequest.GET, {},
(Map response) {
});
// RealRequest
void send(String url, String requestType, Map param, Function callback) {
param.addAll({'url': url, 'requestType': requestType});
ACRouter.openUrl(RouteCst.httpFlutterRequest, param,
routeCallback: (Map<dynamic, dynamic> result) {
callback(result);
});
}
// MockRequest
void send(String url, String requestType, Map param, Function callback) {
dynamic responseJson =
MockRequest.mock(action: getJsonName(url), param: param);
callback(responseJson);
}
複製代碼
Flutter頁面棧由原生控制,使用本身的導航欄。關閉不一樣頁面的方法
// 關閉返回上一頁
static Future<bool> closeCurPage()
// 返回到特定頁面,使用openUrl交互
ACRouter.openUrl('mizlicai://product/closeToRoot', param,
routeCallback: (Map<dynamic, dynamic> result) {
callback(result);
});
複製代碼
在Podfile
中添加配置,能夠切換本地,遠程,debug等環境
platform :ios, '9.0'
# 爲true時,debug環境 爲false時,release環境
FLUTTER_DEBUG_APP=false
# 若是指定了FLUTTER_APP_PATH,則此配置失效
FLUTTER_APP_URL= "http://appinstall.aiyoumi.com:8282/flutter/iOS_flutter_product.git"
# flutter git 分支,默認爲master
# 若是指定了FLUTTER_APP_PATH,則此配置失效
FLUTTER_APP_BRANCH="master"
# flutter本地工程目錄,絕對路徑或者相對路徑,若是有值則git相關的配置無效
FLUTTER_APP_PATH="/Users/zouyongfeng/ac_flutter_module"
eval(File.read(File.join(__dir__, 'flutterhelper.rb')), binding)
複製代碼
AppDelegate中,初始化flutterboost
,傳入FlutterRouter
#import "FlutterRouter.h"
- (void)startFlutter {
[FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:[FlutterRouter sharedRouter]
onStart:^(FlutterViewController *fvc) {
}];
}
複製代碼