qt框架的开发模式_Flutter 混合开发框架模式探索

Flutter 混合开发框架模式探索

由于 Google 官方提供的 Flutter 混合式开发方案过于简单,仅支持打开一个 Flutter View 的能力,而不支持路由间传参、统一的生命周期、路由栈管理等业务开发中必要的能力,因此我们需要借助第三方混合开发框架(如 Flutter Boost、Thrio、QFlutter 等)的整合能力才能将 Flutter 混合开发模式投入与生产环境。本文中,我们来研究一下这类混合开发框架的职能、架构与源码。

1. 核心职能与框架目标

20dfe1064fc86508548c83a82f60212e.png

一个合格的混合开发框架至少需要支持到以下能力:

  1. 混合路由栈的管理:支持打开任意 Flutter 或 Native 页面。
  2. 完善的通知机制:如统一的生命周期,路由相关事件通知机制。

对于以上几点目标,我们以 iOS 为例,来逐步挖掘 Flutter 混合开发模式的最佳实现。

注:因为篇幅问题,本文不探究 Android 的实现,从 iOS 切入只是分析问题的一个角度,因 Android 与 iOS 的实现原理一致,具体实现则大同小异。其次因 Channel 通信层代码实现比较繁杂,此文对于 Channel 通信层的讲解仅停留在使用层面上,具体实现读者可以自行研究。
注:本文的 Flutter Boost 版本为 1.12.13,Thrio 的版本为 0.1.0

2. 从 FlutterViewController 开始

在混合开发中,我们使用 Flutter 作为插件化开发,需要起一个 FlutterViewController,这是一个 UIViewController 的实现,其依附于 FlutterEngine,给 Flutter 传递 UIKit 的输入事件,并展示被 FlutterEngine 渲染的每一帧 Flutter views。而这个 FlutterEngine 则充当 Dart VM 和 Flutter 运行时的环境。

需要注意的是,一个 Flutter Engine 只能最多同时运行一个 FlutterViewController。

FlutterEngine: The FlutterEngine class coordinates a single instance of execution for a FlutterDartProject. It may have zero or one FlutterViewController at a time.

启动 Engine:

self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
[self.flutterEngine run];

创建 FlutterViewController 并展示

FlutterViewController *flutterViewController =
        [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];

创建一个 FlutterViewController 我们既可以用已经运行中的 FlutterEngine 去初始化,也可以创建的时候同时去隐式启动一个 FlutterEngine(不过不建议这样做,因为按需创建 FlutterEngine 的话,在 FlutterViewController 被 present 出来之后,第一帧图像渲染完之前,将会引入明显的延迟),这里我们用前者的方式去创建。

FlutterEngine: https:// api.flutter.dev/objcdoc /Classes/FlutterEngine.html
FlutterViewController: https:// api.flutter.dev/objcdoc /Classes/FlutterViewController.html

至此,我们通过官方提供的方案在 Native 工程中启动 Flutter Engine 并通过 FlutterViewController 展示 Flutter 页面。

在 Flutter 页面中,我们可以使用 Navigator.push 在打开另一个 Flutter 页面(Route):

afbde86b99357e966b5415961ac50e86.png

因此对于这种路由栈我们很容易实现:

c1825ea4633edad30df5ff8dad5f48d0.png

即整个 Flutter 运行在一个单例的 FlutterViewController 容器里,Flutter 内部的所有页面都在这个容器中管理。但如果要实现下面这种 Native 与 Flutter 混合跳转的这种混合路由栈,我们要如何实现呢?

d602d2c5714b733c6a008cbe5a730101.png

最基本的解决思路是,把这个 FlutterViewController 与 NativeViewController 混合起来,直接让 FlutterViewController 在 iOS 的路由栈中来回移动,如下图所示:

7a7c961a5b2a9bbcd5d54fdde4c7f4b7.png

这种方案相对复杂,回到我们上面混合栈的场景,这需要精准记录每个 Flutter 页面和 Native 容器所处的位置,得知道自己 pop 之后应该回到上一层 Flutter 页面,还是切换另一个 NativeViewController,这就得维护好页面索引,并改造原生的pop 时间与 Navigator.pop 事件,使两者统一起来。

我们来看看业界的一些解决方案吧!

3. Flutter Boost

对于混合栈问题,Flutter Boost 会将每个 Flutter 页面用 FlutterViewController 包装起来,使之成为多例,用起来就如同 Webview 一样:

76cb2ce9a5fa38213b562f3c8e1642d2.png

Flutter Boost 的源码之前在另一篇文章中梳理过《Flutter Boost 混合开发实践与源码解析(以 Android 为例)》,那篇文章中梳理了一下 Android 侧打开页面流程的源码,本文中则尽量不重复介绍源码,并且将以 iOS 侧来重点梳理一下 Flutter Boost 究竟是如何实现的。

3.1 从 Native 打开页面

本节分析 Flutter Boost 如何从 Native 打开页面吗,即包含以下两种情况:

  1. Native -> Flutter
  2. Native -> Native

在工程中,我们需要接入以下代码集成 Flutter Boost:

// AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    PlatformRouterImp *router = [PlatformRouterImp new];
    [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router
                                                        onStart:^(FlutterEngine *engine) {
                                                        }];
    self.window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];

    [self.window makeKeyAndVisible];
   
    UINavigationController *rvc = [[UINavigationController alloc] initWithRootViewController:tabVC];

    router.navigationController = rvc;
    self.window.rootViewController = rvc;

    return YES;
}


// PlatformRouterImp.m

- (void)openNativeVC:(NSString *)name
           urlParams:(NSDictionary *)params
                exts:(NSDictionary *)exts{
    UIViewController *vc = UIViewControllerDemo.new;
    BOOL animated = [exts[@"animated"] boolValue];
    if([params[@"present"] boolValue]){
        [self.navigationController presentViewController:vc animated:animated completion:^{
        }];
    }else{
        [self.navigationController pushViewController:vc animated:animated];
    }
}

- (void)open:(NSString *)name
   urlParams:(NSDictionary *)params
        exts:(NSDictionary *)exts
  completion:(void (^)(BOOL))completion
{
    if ([name isEqualToString:@"native"]) { // 模拟打开native页面
        [self openNativeVC:name urlParams:params exts:exts];
        return;
    }
    
    BOOL animated = [exts[@"animated"] boolValue];
    FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
    [vc setName:name params:params];
    [self.navigationController pushViewController:vc animated:animated];
    if(completion) completion(YES);
}

可以发现,我们在工程中首先要启动引擎,对应的 Flutter Boost 源码如下:

// FlutterBoostPlugin.m
- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
                          engine:(FlutterEngine *)engine
           pluginRegisterred:(BOOL)registerPlugin
                         onStart:(void (^)(FlutterEngine * _Nonnull))callback{
    static dispatch_once_t onceToken;
    __weak __typeof__(self) weakSelf = self;
    dispatch_once(&onceToken, ^{
        __strong __typeof__(weakSelf) self = weakSelf;
        FLBFactory *factory = FLBFactory.new;
        self.application = [factory createApplication:platform];
        [self.application startFlutterWithPlatform:platform
                                     withEngine:engine
                                      withPluginRegisterred:registerPlugin
                                       onStart:callback];
    });
}


// FLBFlutterApplication.m

- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
                      withEngine:(FlutterEngine* _Nullable)engine
                        withPluginRegisterred:(BOOL)registerPlugin
                         onStart:(void (^)(FlutterEngine *engine))callback
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        self.platform = platform;
        self.viewProvider = [[FLBFlutterEngine alloc] initWithPlatform:platform engine:engine];
        self.isRunning = YES;
        if(registerPlugin){
            Class clazz = NSClassFromString(@"GeneratedPluginRegistrant");
            FlutterEngine *myengine = [self.viewProvider engine];
            if (clazz && myengine) {
                if ([clazz respondsToSelector:NSSelectorFromString(@"registerWithRegistry:")]) {
                    [clazz performSelector:NSSelectorFromString(@"registerWithRegistry:")
                                withObject:myengine];
                }
            }
        }
        if(callback) callback(self.viewProvider.engine);
    });
}

可以发现启动引擎时 startFlutterWithPlatform 需要传入路由管理类,而在 FLBPlatform.h 中可以看到 open 接口是由业务侧 Native 传过来的实现。

// PlatformRouterImp.m

- (void)open:(NSString *)name
   urlParams:(NSDictionary *)params
        exts:(NSDictionary *)exts
  completion:(void (^)(BOOL))completion
{
    if ([name isEqualToString:@"native"]) { // 模拟打开native页面
        [self openNativeVC:name urlParams:params exts:exts];
        return;
    }
    
    BOOL animated = [exts[@"animated"] boolValue];
    FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
    [vc setName:name params:params];
    [self.naviga
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值