flutter 多实例实战

tags: flutter flutter多实例ios

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

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

第二个tabflutter

两个插件

[原生]  ---> [flutter]
复制代码

痛点问题

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

  1. 如何同时打开多个插件,或者从一个插件打开另外一个插件,即保持多个flutter vc并存
  2. 多个flutter启动后如何保证内存

第一次尝试 建立

因而只须要使用建立代码不就完了吗数据库

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

然而事情并无那么简单 首先在tab中插入的flutterviewcontroller.view直接拿出来显示不出来,使用延迟加载也无论用bash

第二次尝试 - 解决显示问题

通过大神指点要先使用present而后dismiss才能显示出来app

__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。ide

第三次尝试 显示错误

这里有个前提是1.0 ios flutter engine没法释放,若是仅仅使用FlutterViewController.new的方式确定是会有释放的,可是官方提供了一种根据engine建立 fluttervc的方式,因此保留一份engine,或者说让engine保留成一个单例状态。post

至此第三次尝试失败优化

可是从这一次的问题来看,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 避免了这一问题,也达到了体验和内存上的最佳效果。

YY Flutter技术积累相关连接


flutter多实例实战 by共田君

一行代码教你解决FlutterPlatformViews内存泄露 by AShawn

手把手教你在Flutter项目优雅的使用ORM数据库 by williamwen1986

flutter通用基础库flutter_luakit_plugin by williamwen1986

github - flutter_luakit_plugin使用例子 by williamwen1986

手把手教你编译Flutter engine by 共田君

手把手教你解决 Flutter engine 内存泄漏 by 共田君

github - 编译产物下载 修复内存泄漏后的flutter engine(可直接使用)by 共田君

github demo - 修复内存泄漏后的flutter engine by 共田君

持续更新中...