flutter 多實(shí)例實(shí)戰(zhàn)

tags: flutter flutter多實(shí)例

在混合開發(fā)中,我們使用fluter作為插件化開發(fā),即起一個(gè)flutterviewcontroller,這就是一個(gè)插件,該插件與其他模塊并沒有任何交互,用的數(shù)據(jù)源是通過method channel主動(dòng)從從宿主app取得的.

具體的需求是這樣的,在第二個(gè)tab中放入一個(gè)flutter做的的視頻頁(yè)面,另外第三個(gè)tab有兩個(gè)插件的入口,也是用flutter寫的

廣告一下
紙上得來終覺淺,實(shí)踐之后才真懂
建了一個(gè)flutter qq群,群號(hào):217429001 有興趣的加入哦

第二個(gè)tabflutter
兩個(gè)插件
 [原生]  ---> [flutter]

痛點(diǎn)問題

拿到需求第一步就想到,存在幾個(gè)問題

  1. 如何同時(shí)打開多個(gè)插件,或者從一個(gè)插件打開另一個(gè)插件,即保持多個(gè)flutter vc并存
  2. 多個(gè)flutter啟動(dòng)后如何保證內(nèi)存

第一次嘗試 創(chuàng)建

于是只需要使用創(chuàng)建代碼不就完了嗎

   FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithEngine:self.engine nibName:nil bundle:nil];

然而事情并沒有那么簡(jiǎn)單
首先在tab中插入的flutterviewcontroller.view直接拿出來顯示不出來,使用延遲加載也不管用

第二次嘗試 - 解決顯示問題

經(jīng)過大神指點(diǎn)要先使用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];
            }];
        }];

接下來準(zhǔn)備添加多個(gè)flutter
然而在push過程中發(fā)現(xiàn)flutter的第一次顯示的界面竟然是上次tab的頁(yè)面,因?yàn)閑ngine是同一份的,我們創(chuàng)建的時(shí)候會(huì)保存一份engine。

第三次嘗試 顯示錯(cuò)誤

這里有個(gè)前提是1.0 ios flutter engine無法釋放,如果僅僅使用FlutterViewController.new的方式肯定是會(huì)有釋放的,但是官方提供了一種根據(jù)engine創(chuàng)建 fluttervc的方式,所以保留一份engine,或者說讓engine保留成一個(gè)單例狀態(tài)。

至此第三次嘗試失敗

但是從這一次的問題來看,flutter上面的界面并不是跟著fluttervc走的,而是跟著engine走的,fluttervc僅僅提供了一個(gè)手勢(shì)和其他事件入口,所以即使關(guān)閉了fluttervc或者delloc了,只要engine存在,圖形渲染就保留了上一次的界面,到此為止多實(shí)例的fluttervc從根本上就沒有存在的必要了。

第四次嘗試 單實(shí)例實(shí)現(xiàn)多vc樣式

我們知道fluttervc有個(gè)初始routername的方法,在第一次啟動(dòng)的時(shí)候可以設(shè)置這個(gè)routername

- (void)setInitialRoute:(NSString*)route

于是想到通過這個(gè)來設(shè)置不同的路由。
殊不知,這個(gè)方法和initWithEngine 搭配使用時(shí),并沒有起作用,傳入到main.dart里面的window.defaultRouteName一直是 / 根目錄符號(hào)

- (instancetype)initWithEngine:(FlutterEngine*)engine
                       nibName:(NSString*)nibNameOrNil
                        bundle:(NSBundle*)nibBundleOrNil NS_DESIGNATED_INITIALIZER;

另外設(shè)置即使可以起作用,也無法實(shí)現(xiàn)多路由問題。

第五次嘗試 改造setInialRoute

既然官方存在bug,那就解決吧,首先看了一圈flutter engine setInialRoute的實(shí)現(xiàn),最終在shell.cc里面,如果直接改動(dòng)engine編譯有點(diǎn)麻煩,想到的解決方案是在main.dart里面去原生讀取宿主路由
然后渲染對(duì)應(yīng)的頁(yè)面。

    MosNativeHelper.defaultRouteName().then((name){
      setState(() {
        if(name != null){
            widget.defaultRouteName = name;
        }
      });
    });

然而這是第一次讀取,后續(xù)怎么更新新的頁(yè)面呢,這時(shí)候需要宿主主動(dòng)發(fā)通知給flutter了

第六次嘗試 宿主發(fā)消息給flutter

flutter有個(gè)eventchannel就是用于接收宿主的事件回調(diào)的,使用方法是先注冊(cè)事件,發(fā)送一個(gè)參數(shù)給宿主,然后監(jiān)聽event,最后釋放


  // 注冊(cè)一個(gè)通知
  static const EventChannel eventChannel = const EventChannel('com.moschat.app/native_post');

  // 渲染前的操作,類似viewDidLoad
  @override
  void initState() {
    super.initState();
    // 監(jiān)聽事件,同時(shí)發(fā)送參數(shù)12345
    eventChannel.receiveBroadcastStream(12345).listen(_onEvent,onError: _onError);
    print("[flutter]進(jìn)入到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>
// // 這個(gè)onListen是Flutter端開始監(jiān)聽這個(gè)channel時(shí)的回調(diào),第二個(gè)參數(shù) EventSink是用來傳數(shù)據(jù)的載體。
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
                                       eventSink:(FlutterEventSink)events {
    
    // arguments flutter給native的參數(shù)
    // 回調(diào)給flutter, 建議使用實(shí)例指向,因?yàn)樵揵lock可以使用多次
    if (events) {
        self.eventsBlock = [events copy];
        self.eventsBlock (@"我是標(biāo)題");
    }
    return nil;
}

/// flutter不再接收
- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
    // arguments flutter給native的參數(shù)
    return nil;
}

由于eventblock可以回調(diào)多次,可以達(dá)到宿主直接發(fā)消息給flutter的作用。
main.dart中接收到消息,則可以直接在flutter里面刷新根路由界面。

第七次嘗試 優(yōu)化

在切換插件過程中還是會(huì)有閃現(xiàn)前一個(gè)界面的問題,我們?cè)趐op fluttervc的時(shí)機(jī)多flutter發(fā)送一個(gè)刷新一個(gè)黑色界面的指令,則在下次啟動(dòng)時(shí)會(huì)閃過一個(gè)黑色頁(yè)面的過程,這個(gè)時(shí)機(jī)可以看做是啟動(dòng)的過程大概0.3s。
第二個(gè)點(diǎn)是在flutter poproute的過程中,有業(yè)務(wù)需要在中間的route就退出整個(gè)vc,此時(shí)要注意一個(gè)點(diǎn)是,pop vc過程中要主動(dòng)一層層返回到flutter根部頁(yè)面,否則下一次看到的還是上一次的那個(gè)頁(yè)面。

結(jié)語

在多實(shí)例的實(shí)踐過程中,發(fā)現(xiàn)ios的engine除了內(nèi)存問題外,還有根路由設(shè)置不成功的問題,從業(yè)務(wù)方案上使用單engine 單flutterviewcontroller 避免了這一問題,也達(dá)到了體驗(yàn)和內(nèi)存上的最佳效果。

最后編輯于
?著作權(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)容