tags: flutter
flutter多實例
在混合開發中,我們使用fluter作為插件化開發,即起一個flutterviewcontroller,這就是一個插件,該插件與其他模塊并沒有任何交互,用的數據源是通過method channel主動從從宿主app取得的.
具體的需求是這樣的,在第二個tab中放入一個flutter做的的視頻頁面,另外第三個tab有兩個插件的入口,也是用flutter寫的
廣告一下
紙上得來終覺淺,實踐之后才真懂
建了一個flutter qq群,群號:217429001 有興趣的加入哦
[原生] ---> [flutter]
痛點問題
拿到需求第一步就想到,存在幾個問題
- 如何同時打開多個插件,或者從一個插件打開另一個插件,即保持多個flutter vc并存
- 多個flutter啟動后如何保證內存
第一次嘗試 創建
于是只需要使用創建代碼不就完了嗎
FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithEngine:self.engine nibName:nil bundle:nil];
然而事情并沒有那么簡單
首先在tab中插入的flutterviewcontroller.view直接拿出來顯示不出來,使用延遲加載也不管用
第二次嘗試 - 解決顯示問題
經過大神指點要先使用present然后dismiss才能顯示出來
__weak __typeof(self)weakSelf = self;
self.ctr4.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:weakSelf.ctr4 animated:NO completion:^{
[weakSelf dismissViewControllerAnimated:NO completion:^{
[weakSelf addChildViewController:weakSelf.ctr4];
[weakSelf.view bringSubviewToFront:weakSelf.tabbarContainer];
}];
}];
接下來準備添加多個flutter
然而在push過程中發現flutter的第一次顯示的界面竟然是上次tab的頁面,因為engine是同一份的,我們創建的時候會保存一份engine。
第三次嘗試 顯示錯誤
這里有個前提是1.0 ios flutter engine無法釋放,如果僅僅使用FlutterViewController.new
的方式肯定是會有釋放的,但是官方提供了一種根據engine創建 fluttervc的方式,所以保留一份engine,或者說讓engine保留成一個單例狀態。
至此第三次嘗試失敗
但是從這一次的問題來看,flutter上面的界面并不是跟著fluttervc走的,而是跟著engine走的,fluttervc僅僅提供了一個手勢和其他事件入口,所以即使關閉了fluttervc或者delloc了,只要engine存在,圖形渲染就保留了上一次的界面,到此為止多實例的fluttervc從根本上就沒有存在的必要了。
第四次嘗試 單實例實現多vc樣式
我們知道fluttervc有個初始routername的方法,在第一次啟動的時候可以設置這個routername
- (void)setInitialRoute:(NSString*)route
于是想到通過這個來設置不同的路由。
殊不知,這個方法和initWithEngine
搭配使用時,并沒有起作用,傳入到main.dart里面的window.defaultRouteName
一直是 /
根目錄符號
- (instancetype)initWithEngine:(FlutterEngine*)engine
nibName:(NSString*)nibNameOrNil
bundle:(NSBundle*)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
另外設置即使可以起作用,也無法實現多路由問題。
第五次嘗試 改造setInialRoute
既然官方存在bug,那就解決吧,首先看了一圈flutter engine setInialRoute的實現,最終在shell.cc里面,如果直接改動engine編譯有點麻煩,想到的解決方案是在main.dart里面去原生讀取宿主路由
然后渲染對應的頁面。
MosNativeHelper.defaultRouteName().then((name){
setState(() {
if(name != null){
widget.defaultRouteName = name;
}
});
});
然而這是第一次讀取,后續怎么更新新的頁面呢,這時候需要宿主主動發通知給flutter了
第六次嘗試 宿主發消息給flutter
flutter有個eventchannel就是用于接收宿主的事件回調的,使用方法是先注冊事件,發送一個參數給宿主,然后監聽event,最后釋放
// 注冊一個通知
static const EventChannel eventChannel = const EventChannel('com.moschat.app/native_post');
// 渲染前的操作,類似viewDidLoad
@override
void initState() {
super.initState();
// 監聽事件,同時發送參數12345
eventChannel.receiveBroadcastStream(12345).listen(_onEvent,onError: _onError);
print("[flutter]進入到initState");
widget.defaultRouteName = window.defaultRouteName;
MosNativeHelper.defaultRouteName().then((name){
setState(() {
if(name != null){
widget.defaultRouteName = name;
}
});
});
}
在宿主端的代碼
添加eventchannel代理方法
NSString *channelName = @"com.moschat.app/native_post";
FlutterEventChannel *evenChannal = [FlutterEventChannel eventChannelWithName:channelName binaryMessenger:flutterViewController];
// 代理
[evenChannal setStreamHandler:MOSFlutterEngine.sharedInstance];
添加代理 FlutterStreamHandler
#pragma mark - <FlutterStreamHandler>
// // 這個onListen是Flutter端開始監聽這個channel時的回調,第二個參數 EventSink是用來傳數據的載體。
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
eventSink:(FlutterEventSink)events {
// arguments flutter給native的參數
// 回調給flutter, 建議使用實例指向,因為該block可以使用多次
if (events) {
self.eventsBlock = [events copy];
self.eventsBlock (@"我是標題");
}
return nil;
}
/// flutter不再接收
- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
// arguments flutter給native的參數
return nil;
}
由于eventblock可以回調多次,可以達到宿主直接發消息給flutter的作用。
main.dart中接收到消息,則可以直接在flutter里面刷新根路由界面。
第七次嘗試 優化
在切換插件過程中還是會有閃現前一個界面的問題,我們在pop fluttervc的時機多flutter發送一個刷新一個黑色界面的指令,則在下次啟動時會閃過一個黑色頁面的過程,這個時機可以看做是啟動的過程大概0.3s。
第二個點是在flutter poproute的過程中,有業務需要在中間的route就退出整個vc,此時要注意一個點是,pop vc過程中要主動一層層返回到flutter根部頁面,否則下一次看到的還是上一次的那個頁面。
結語
在多實例的實踐過程中,發現ios的engine除了內存問題外,還有根路由設置不成功的問題,從業務方案上使用單engine 單flutterviewcontroller 避免了這一問題,也達到了體驗和內存上的最佳效果。