美文网首页Flutter学习
flutter 多实例实战

flutter 多实例实战

作者: 共田君 | 来源:发表于2019-02-21 21:16 被阅读1758次

    tags: flutter flutter多实例

    在混合开发中,我们使用fluter作为插件化开发,即起一个flutterviewcontroller,这就是一个插件,该插件与其他模块并没有任何交互,用的数据源是通过method channel主动从从宿主app取得的.

    具体的需求是这样的,在第二个tab中放入一个flutter做的的视频页面,另外第三个tab有两个插件的入口,也是用flutter写的

    第二个tabflutter 两个插件
     [原生]  ---> [flutter]
    

    痛点问题

    拿到需求第一步就想到,存在几个问题

    1. 如何同时打开多个插件,或者从一个插件打开另一个插件,即保持多个flutter vc并存
    2. 多个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 避免了这一问题,也达到了体验和内存上的最佳效果。

    相关文章

      网友评论

        本文标题:flutter 多实例实战

        本文链接:https://www.haomeiwen.com/subject/zufjyqtx.html