乐趣区

关于android:Flutter-Boost-混合开发框架初探

一、Flutter Boost 简介

家喻户晓,Flutter 是一个由 C ++ 实现的 Flutter Engine 和由 Dart 实现的 Framework 组成的跨平台技术框架。其中,Flutter Engine 负责线程治理、Dart VM 状态治理以及 Dart 代码加载等工作,而 Dart 代码所实现的 Framework 则负责下层业务开发,如 Flutter 提供的组件等概念就是 Framework 的领域。

随着 Flutter 的倒退,国内越来越多的 App 开始接入 Flutter。为了升高危险,大部分 App 采纳渐进式形式引入 Flutter,在 App 里选几个页面用 Flutter 来编写,但都碰到了雷同的问题,在原生页面和 Flutter 页面共存的状况下,如何治理路由,以及原生页面与 Flutter 页面之间的切换和通信都是混合开发中须要解决的问题。然而,官网没有提供明确的解决方案,只是在混合开发时,官网倡议开发者,应该应用同一个引擎反对多窗口绘制的能力,至多在逻辑上做到 FlutterViewController 是共享同一个引擎外面的资源。换句话说,官网心愿所有的绘制窗口共享同一个主 Isolate,而不是呈现多个主 Isolate 的状况。不过,对于当初曾经呈现的多引擎模式问题,Flutter 官网也没有提供好的解决方案。除了内存耗费重大外,多引擎模式还会带来如下一些问题。

  • 冗余资源问题。多引擎模式下每个引擎的 Isolate 是互相独立的,尽管在逻辑上这并没有什么害处,然而每个引擎底层都保护了一套图片缓存等比拟耗费内存的对象,因而设施的内存耗费是十分重大的。
  • 插件注册问题。在 Flutter 插件中,消息传递须要依赖 Messenger,而 Messenger 是由 FlutterViewController 去实现的。如果一个利用中同时存在多个 FlutterViewController,那么插件的注册和通信将会变得凌乱且难以保护。
  • Flutter 组件和原生页面的差异化问题。通常,Flutter 页面是由组件形成的,原生页面则是由 ViewController 或者 Activity 形成的。逻辑上来说,咱们心愿打消 Flutter 页面与原生页面的差别,否则在进行页面埋点和其它一些操作时减少一些额定的工作量。
  • 减少页面通信的复杂度。如果所有的 Dart 代码都运行在同一个引擎实例中,那么它们会共享同一个 Isolate,能够用对立的框架实现组件之间的通信,然而如果存在多个引擎实例会让 Isolate 的治理变得更加简单。

如果不解决多引擎问题,那么混合我的项目的导航栈如下图所示。

目前,对于原生工程混编 Flutter 工程呈现的多引擎模式问题,国内次要有两种解决方案,一种是字节跳动的批改 Flutter Engine 源码计划,另一种是闲鱼开源的 FlutterBoost。因为字节跳动的混合开发的计划没有开源,所以当初能应用的就剩下 FlutterBoost 计划。

FlutterBoost 是闲鱼技术团队开发的一个可复用页面的插件,旨在把 Flutter 容器做成相似于浏览器的加载计划。为此,闲鱼技术团队为心愿 FlutterBoost 能实现如下的基本功能:

  • 可复用的通用型混合开发计划。
  • 反对更加简单的混合模式,比方反对 Tab 切换的场景。
  • 无侵入性计划,应用时不再依赖批改 Flutter 的计划。
  • 反对对页面生命周期进行对立的治理。
  • 具备对立明确的设计概念。

并且,最近 Flutter Boost 降级了 3.0 版本,并带来了如下的一些更新:

  • 不侵入引擎,兼容 Flutter 的各种版本,Flutter sdk 的降级不须要再降级 FlutterBoost,极大升高降级老本。
  • 不辨别 Androidx 和 Support 分支。
  • 简化架构和接口,和 FlutterBoost2.0 比,代码缩小了一半。
  • 双端对立,包含接口和设计上的对立。
  • 反对关上 Flutter 页面,不再关上容器场景。
  • 页面生命周期变动告诉更不便业务应用。
  • 解决了 2.0 中的遗留问题,例如,Fragment 接入艰难、页面敞开后不能传递数据、dispose 不执行,内存占用过低等。

二、Flutter Boost 集成

在原生我的项目中集成 Flutter Boost 只须要将 Flutter Boost 看成是一个插件工程即可。和其余 Flutter 插件的集成形式一样,应用 FlutterBoost 之前须要先增加依赖。应用 Android Studio 关上混合工程的 Flutter 工程,在 pubspec.yaml 中增加 FlutterBoost 依赖插件,如下所示。

flutter_boost:
    git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: 'v3.0-hotfixes'

须要阐明的是,此处的所依赖的 FlutterBoost 的版本与 Flutter 的版本是对应的,如果不对应应用过程中会呈现版本不匹配的谬误。而后,应用 flutter packages get 命令将 FlutterBoost 插件拉取到本地。

2.1 Android 集成

应用 Android Studio 关上新建的原生 Android 工程,在原生 Android 工程的 settings.gradle 文件中增加如下代码。

setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir.parentFile,
  'flutter_library/.android/include_flutter.groovy'))

而后,关上原生 Android 工程 app 目录下的 build.gradle 文件,持续增加如下依赖脚本。

dependencies {implementation project(':flutter_boost')
  implementation project(':flutter')
}

从新编译构建原生 Android 工程,如果没有任何谬误则阐明 Android 胜利了集成 FlutterBoost。应用 Flutter Boost 之前,须要先执行初始化。关上原生 Android 工程,新建一个继承 FlutterApplication 的 Application,而后在 onCreate() 办法中初始化 FlutterBoost,代码如下。

public class MyApplication extends FlutterApplication {


    @Override
    public void onCreate() {super.onCreate();

        FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {

            @Override
            public void pushNativeRoute(String pageName, HashMap<String, String> arguments) {Intent intent = new Intent(FlutterBoost.instance().currentActivity(), NativePageActivity.class);
                FlutterBoost.instance().currentActivity().startActivity(intent);
            }

            @Override
            public void pushFlutterRoute(String pageName, HashMap<String, String> arguments) {Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class, FlutterBoost.ENGINE_ID)
                        .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
                        .destroyEngineWithActivity(false)
                        .url(pageName)
                        .urlParams(arguments)
                        .build(FlutterBoost.instance().currentActivity());
                FlutterBoost.instance().currentActivity().startActivity(intent);
            }

        },engine->{engine.getPlugins();
        } );
    }
}

而后,关上原生 Android 工程下的 AndroidManifest.xml 文件,将 Application 替换成自定义的 MyApplication,如下所示。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          package="com.idlefish.flutterboost.example">

    <application
        android:name="com.idlefish.flutterboost.example.MyApplication"
        android:label="flutter_boost_example"
        android:icon="@mipmap/ic_launcher">

        <activity
            android:name="com.idlefish.flutterboost.containers.FlutterBoostActivity"
            android:theme="@style/Theme.AppCompat"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize" >
            <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background"/>

        </activity>
        <meta-data android:name="flutterEmbedding"
                   android:value="2">
        </meta-data>
    </application>
</manifest>

因为 Flutter Boost 是以插件的形式集成到原生 Android 我的项目的,所以咱们能够在 Native 关上和敞开 Flutter 模块的页面。

FlutterBoost.instance().open("flutterPage",params);
FlutterBoost.instance().close("uniqueId");

而 Flutter Dart 的应用如下。首先,咱们能够在 main.dart 文件的程序入口 main() 办法中进行初始化。

void main() {runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
   static Map<String, FlutterBoostRouteFactory>
       routerMap = {'/': (settings, uniqueId) {
      return PageRouteBuilder<dynamic>(settings: settings, pageBuilder: (_, __, ___)
          => Container());
    },
    'embedded': (settings, uniqueId) {
      return PageRouteBuilder<dynamic>(
          settings: settings,
          pageBuilder: (_, __, ___) =>
          EmbeddedFirstRouteWidget());
    },
    'presentFlutterPage': (settings, uniqueId) {
      return PageRouteBuilder<dynamic>(
          settings: settings,
          pageBuilder: (_, __, ___) =>
          FlutterRouteWidget(
                params: settings.arguments,
                uniqueId: uniqueId,
              ));
    }};
   Route<dynamic> routeFactory(RouteSettings settings, String uniqueId) {FlutterBoostRouteFactory func =routerMap[settings.name];
    if (func == null) {return null;}
    return func(settings, uniqueId);
  }

  @override
  void initState() {super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FlutterBoostApp(routeFactory);
  }

当然,还能够监听页面的生命周期,如下所示。

class SimpleWidget extends StatefulWidget {
  final Map params;
  final String messages;
  final String uniqueId;

  const SimpleWidget(this.uniqueId, this.params, this.messages);

  @override
  _SimpleWidgetState createState() => _SimpleWidgetState();
}

class _SimpleWidgetState extends State<SimpleWidget>
    with PageVisibilityObserver {
  static const String _kTag = 'xlog';
  @override
  void didChangeDependencies() {super.didChangeDependencies();
    print('$_kTag#didChangeDependencies, ${widget.uniqueId}, $this');

  }

  @override
  void initState() {super.initState();
   PageVisibilityBinding.instance.addObserver(this, ModalRoute.of(context));
   print('$_kTag#initState, ${widget.uniqueId}, $this');
  }

  @override
  void dispose() {PageVisibilityBinding.instance.removeObserver(this);
    print('$_kTag#dispose, ${widget.uniqueId}, $this');
    super.dispose();}

  @override
  void onForeground() {print('$_kTag#onForeground, ${widget.uniqueId}, $this');
  }

  @override
  void onBackground() {print('$_kTag#onBackground, ${widget.uniqueId}, $this');
  }

  @override
  void onAppear(ChangeReason reason) {print('$_kTag#onAppear, ${widget.uniqueId}, $reason, $this');
  }

  void onDisappear(ChangeReason reason) {print('$_kTag#onDisappear, ${widget.uniqueId}, $reason, $this');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('tab_example'),
      ),
      body: SingleChildScrollView(physics: BouncingScrollPhysics(),
          child: Container(
              child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Container(margin: const EdgeInsets.only(top: 80.0),
                child: Text(
                  widget.messages,
                  style: TextStyle(fontSize: 28.0, color: Colors.blue),
                ),
                alignment: AlignmentDirectional.center,
              ),
              Container(margin: const EdgeInsets.only(top: 32.0),
                child: Text(
                  widget.uniqueId,
                  style: TextStyle(fontSize: 22.0, color: Colors.red),
                ),
                alignment: AlignmentDirectional.center,
              ),
              InkWell(
                child: Container(padding: const EdgeInsets.all(8.0),
                    margin: const EdgeInsets.all(30.0),
                    color: Colors.yellow,
                    child: Text(
                      'open flutter page',
                      style: TextStyle(fontSize: 22.0, color: Colors.black),
                    )),
                onTap: () => BoostNavigator.of().push("flutterPage",
                    arguments: <String, String>{'from': widget.uniqueId}),
              )
              Container(
                height: 300,
                width: 200,
                child: Text(
                  '',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                ),
              )
            ],
          ))),
    );
  }
}

而后,运行我的项目,就能够从原生页面跳转到 Flutter 页面,如下图所示成果。

2.2 iOS 集成

和 Android 的集成步骤一样,应用 Xcode 关上原生 iOS 工程,而后在 iOS 的 AppDelegate 文件中初始化 Flutter Boost,如下所示。

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{MyFlutterBoostDelegate* delegate=[[MyFlutterBoostDelegate alloc] init];
    [[FlutterBoost instance] setup:application delegate:delegate callback:^(FlutterEngine *engine) {}];

    return YES;
}
@end

上面是自定义的 FlutterBoostDelegate 的代码,如下所示。

@interface MyFlutterBoostDelegate : NSObject<FlutterBoostDelegate>
@property (nonatomic,strong) UINavigationController *navigationController;
@end

@implementation MyFlutterBoostDelegate

- (void) pushNativeRoute:(FBCommonParams*) params{BOOL animated = [params.arguments[@"animated"] boolValue];
    BOOL present= [params.arguments[@"present"] boolValue];
    UIViewControllerDemo *nvc = [[UIViewControllerDemo alloc] initWithNibName:@"UIViewControllerDemo" bundle:[NSBundle mainBundle]];
    if(present){[self.navigationController presentViewController:nvc animated:animated completion:^{}];
    }else{[self.navigationController pushViewController:nvc animated:animated];
    }
}

- (void) pushFlutterRoute:(FBCommonParams*)params {FlutterEngine* engine =  [[FlutterBoost instance] getEngine];
    engine.viewController = nil;

    FBFlutterViewContainer *vc = FBFlutterViewContainer.new ;

    [vc setName:params.pageName params:params.arguments];

    BOOL animated = [params.arguments[@"animated"] boolValue];
    BOOL present= [params.arguments[@"present"] boolValue];
    if(present){[self.navigationController presentViewController:vc animated:animated completion:^{}];
    }else{[self.navigationController pushViewController:vc animated:animated];

    }
}

- (void) popRoute:(FBCommonParams*)params
         result:(NSDictionary *)result{FBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;

    if([vc isKindOfClass:FBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: params.uniqueId]){[vc dismissViewControllerAnimated:YES completion:^{}];
    }else{[self.navigationController popViewControllerAnimated:YES];
    }

}

@end

如果要在原生 iOS 代码中关上或敞开 Flutter 页面,能够应用上面的形式。

[[FlutterBoost instance] open:@"flutterPage" arguments:@{@"animated":@(YES)}  ];
[[FlutterBoost instance] open:@"secondStateful" arguments:@{@"present":@(YES)}];

三、Flutter Boost 架构

对于混合工程来说,原生端和 Flutter 端对于页面的定义是不一样的。对于原生端而言,页面通常指的是一个 ViewController 或者 Activity,而对于 Flutter 来说,页面通常指的是 Flutter 组件。FlutterBoost 框架所要做的就是对立混合工程中页面的概念,或者说弱化 Flutter 组件对应容器页面的概念。换句话说,当有一个原生页面存在的时候,FlutteBoost 就能保障肯定有一个对应的 Flutter 的容器页面存在。

FlutterBoost 框架其实就是由原生容器通过音讯驱动 Flutter 页面容器,从而达到原生容器与 Flutter 容器同步的目标,而 Flutter 渲染的内容是由原生容器去驱动的,上面是 Flutter Boost 给的一个 Flutter Boost 的架构示意图。


能够看到,Flutter Boost 插件分为平台和 Dart 两端,两头通过 Message Channel 连贯。平台侧提供了 Flutter 引擎的配置和治理、Native 容器的创立 / 销毁、页面可见性变动告诉,以及 Flutter 页面的关上 / 敞开接口等。而 Dart 侧除了提供相似原生 Navigator 的页面导航接口的能力外,还负责 Flutter 页面的路由治理。

总的来说,正是基于共享同一个引擎的计划,使得 FlutterBoost 框架无效的解决了多引擎的问题。简略来说,FlutterBoost 在 Dart 端引入了容器的概念,当存在多个 Flutter 页面时,FlutterBoost 不须要再用栈的构造去保护现有页面,而是应用扁平化键值对映射的模式去保护以后所有的页面,并且每个页面领有一个惟一的 id

四、FlutterBoost3.0 更新

4.1 不入侵引擎

为了解决官网引擎复用引起的问题,FlutterBoost2.0 拷贝了 Flutter 引擎 Embedding 层的一些代码进行革新,这使得前期的降级老本极高。而 FlutterBoost3.0 采纳继承的形式扩大 FlutterActivity/FlutterFragment 等组件的能力,并且通过在适当机会给 Dart 侧发送 appIsResumed 音讯解决引擎复用时生命周期事件错乱导致的页面卡死问题,并且,FlutterBoost 3.0 也兼容最新的官网公布的 Flutter 2.0。

4.2 不辨别 Androidx 和 Support 分支

FlutterBoost2.0 通过本人实现 FlutterActivityAndFragmentDelegate.Host 接口来扩大 FlutterActivity 和 FlutterFragment 的能力,而 getLifecycle 是必须实现的接口,这就导致对 androidx 的依赖。这也是为什么 FlutterBoostView 的实现没有被放入 FlutterBoost3.0 插件中的起因。而 FlutterBoost3.0 通过继承的形式扩大 FlutterActivity/FlutterFragment 的能力的额定收益就是,能够做到不依赖 androidx。

4.3 双端设计对立,接口对立

很多 Flutter 开发者只会一端,只会 Android 或者只会 IOS,但他须要接入双端,所以双端对立能升高他的 学习老本和接入老本。FlutterBoost3.0,在设计上 Android 和 IOS 都做了对齐,特地接口上做到了参数级的对齐。

4.4 反对【关上 flutter 页面不再关上容器】场景

在 Flutter 模块外部,Flutter 页面跳转 Flutter 页面是能够不须要再关上 Flutter 容器的,不关上容器,能节俭内存开销。在 FlutterBoost3.0 上,关上容器和不关上容器的区别体现在用户接口上仅仅是 withContainer 参数是否为 true 就好。

InkWell(
  child: Container(
      color: Colors.yellow,
      child: Text(
        '关上内部路由',
        style: TextStyle(fontSize: 22.0, color: Colors.black),
      )),
  onTap: () => BoostNavigator.of().push("flutterPage",
      arguments: <String, String>{'from': widget.uniqueId}),
),
InkWell(
  child: Container(
      color: Colors.yellow,
      child: Text(
        '关上外部路由',
        style: TextStyle(fontSize: 22.0, color: Colors.black),
      )),
  onTap: () => BoostNavigator.of().push("flutterPage",
      withContainer: true,
      arguments: <String, String>{'from': widget.uniqueId}),
)

4.5 生命周期的精准告诉

在 FlutterBoost2.0 上,每个页面都会收到页面生命周期告诉,而 FlutterBoost3.0 只会告诉页面可见性理论产生了变动的页面,接口也更合乎 flutter 的设计。

4.6 其余 Issue

除了下面的一些个性外,Flutter Boost 3.0 版本还解决了如下一些问题:

  • 页面敞开后参数的传递,之前只有 iOS 反对,android 不反对,目前在 dart 侧实现,Ios 和 Android 都反对。
  • 解决了 Android 状态栏字体和色彩问题。
  • 解决了页面回退 willpopscope 不起作用问题。
  • 解决了不在栈顶的页面也收到生命周期回调的问题
  • 解决了屡次 setState 耗性能问题。
  • 提供了 Framgent 多种接入形式的 Demo,不便 tab 场景的接入。
  • 生命周期的回调代码,能够用户代码外面 with 的形式接入,应用更简略。
  • 全面简化了,接入老本,包含 dart 侧,android 侧和 ios
  • 丰盛了 demo,蕴含了根本场景,不便用户接入 和测试回归
退出移动版