微店的Flutter混合栈管理技术实践

前景介绍

Flutter 是谷歌开发的一款能够跨平台开发的 UI框架,它的原理接近于游戏引擎,目的在于统一Android/iOS 两端开发,Flutter页面有本身的栈,正常状况下,若是一个App彻底由 Flutter构成,那么只须要一个 FlutterView 便可。android

上述方案只适用于一些新构建的App,对于一些已有的App,是不可能用 flutter来重构的,成本太大,周期太长,因此这里须要实现一套 Native 页面栈和Flutter页面栈的管理方案,即混合栈git

关于混合栈的管理,闲鱼出过一篇文章,可是对于它的兼容性问题和截图问题,没有采用,不过做者对闲鱼的混合栈源码作了参考,这里感谢闲鱼的源码分享。本文是在 android 的基础上讲解实现方式的,iOS 目前使用的仍是截图方案,其他的原理差很少github

方案探索

方案一

不进行任何处理,直接使用 FlutterActivity 来打开页面:此方法最接近原生,交替打开几个页面后会呈现出如下页面结构 缓存

每一个 FlutterActivity 都有本身的 flutter 栈,此时若是用户点击了返回按钮的时候页面退出的呈现形式是正常的,可是若是App用了侧滑返回的话工做就会不正常。框架

侧滑结束 FlutterActivit2 会一会儿结束三个 flutter widget 页面async

除了上述问题外,还存在一个严重的问题:FlutterView1FlutterView2 属于两个 isolate,二者至关于两个 flutter engine 实例,在内存上隔离的,不共享函数

总结: 该方案有如下缺点布局

  • 不兼容现有的侧滑返回
  • 页面的生命周期埋点须要在 dart 层从新实现一套
  • 不一样 FlutterView 之间没法共享内存(图片缓存,全局单例都不可公用)
  • 资源占用大:每次启动一个 FlutterActivity 都会启动一个新的 Flutter 实例
  • 界面切换体验有差异:Native 页面之间的切换动画和 flutter 页面之间的切换动画有差异

方案二

全局共用一个 FlutterView,每一个 flutter 页面都有一个对应的 native 页面:此方案能够解决方案一中的内存共享浪费问题post

此方案的大体原理以下: 测试

-w884

关键步骤是 2 这个操做,当要打开一个新的 flutter 页面时,native 会启动一个新的 FlutterActivity,而后把当前 FlutterActivity1 中的 FlutterView 移除,而且添加到 FlutterActivity2 中。

退出页面的时候也同样,先让 FlutterView 从 FlutterActivity2 中 remove 移走,而后 add 到 FlutterActivity1 中。

你可能会想:“切换页面的时候,FlutterView 从 FlutterActiviy 移除了,显示不是会变成空白了吗?

什么都不作,的确存在上述问题,这里想把此方案实现,还须要考虑两点:

  • FlutterView 从 FlutterActivity1 移除的时候,显示的内容不会被移除
  • FlutterView 从 FlutterActivity1 移除添加到 FlutterActivity2 的以前,必须保证新的 flutter page 已经 push 到 flutter 的栈中,不然 FlutterActivity2 显示的仍是 FlutterActivity1 中显示的界面

这里要实现第一点的话只能使用截图方案,在 FlutterView 移除前先保存一份当前页面的截图快照,而后移除,这样就不会出现空白的问题

方案三

全局共用一个 FlutterNativeView,每一个 flutter 页面都有一个对应的 native 页面:此方案和方案二想接近,最大的区别就是复用的东西变成了 FlutterNativeView

此方案的结构图以下:

屏幕快照 2019-01-14 下午8.18.37

和方案二不一样的是,方案三中 FlutterViewFlutterActivity 绑定在一块儿了,这样能够避免 FlutterView 单例化形成的 context 泄漏。

并且相比于方案二,要实现此方案只须要知足一条规则便可:

  • FlutterNativeView 从 FlutterActivity1 detach 而后 attach 到 FlutterActivity2 的以前,必须保证新的 flutter page 已经 push 到 flutter 的栈中,不然 FlutterActivity2 显示的仍是 FlutterActivity1 中显示的界面

你会发现,这里不须要 FlutterNativeView 在 detach 的时候构造一份当前页面的快照而后占位显示.

由于在页面切换的时候 FlutterView 并无从 FlutterActivity 中移除,FlutterNativeViewFlutterView detach 的时候,FlutterView 显示的内容就不会再更新了,至关于 Android 上的 onPreDraw 函数返回 false, 因此这里不必截图保存快照。

实现

通过上述方案的探索,决定在 android 上使用第三套方案

iOS 由于有侧滑返回,没法避免截图,由于在侧滑的时候,页面不必定结束。因此我这里抛弃了 android 上侧滑返回(原本 android 的侧滑返回就很奇怪,不支持合理)

实现关键点:

-w454

  • 整个布局为多 FlutterViewFlutterNativeView 实例
  • 每个 flutter 页面对应一个 native 的 activity,并经过一个 id 关联,作到栈同步
  • Flutter 和 Native 基于 url 的方式开管理页面
  • 禁用 flutter 自带的页面切换动画,使用 native 自带的动画来实现
  • 使用一个空白的 widget 做为 flutter 页面的栈底
  • 当打开新页面或者退出页面的时候,必须先让 FlutterNativeViewFlutterView 脱离,才能够在 flutter 栈操做页面的进退

整个页面启动跳转打大体流程图以下:

Flutter混合开发

左侧是 flutter 的执行流程,右侧是 android native activity 的执行流程

页面的传参和数据返回

上述代码的设计尚未考虑页面之间的数据传递,原生 flutter 的页面数据传递是这样的:

void jumpToSettings(BuildContext context) async {
    String result = await Navitor.of(context).pushNamed("settings");
    print("page return: $result");
}
复制代码

因此在设计页面数据传递的时候向原生的看齐,以下所示:

final result = await VDRouter.instance().openUrlFromNative(
    context: context,
    routerOption: RouterOption(
        url: "native://example", args: {"message": "Open from Flutter"}));
复制代码

当 native 端的 MethodCallHandler 被调用时,有个参数是 result,只有给这个 result 设置告终果 (result.success(xxx)),上面的 await 才会有返回,顺着这个思路去实现很简单。

-w722

只要 FlutterActivity 把由当前 flutter 发起打开页面请求的 result 对象保存起来,而后调用 startActivityForResult 来启动页面,等页面结束后会回调到 onActivityResult 中,此时再经过保存的 result 对象,把结果返回给 flutter 端。

传参直接使用 intent 传参便可。

沉浸式同步的问题

每次启动一个新的 FlutterActivity 都须要和 flutter 端同步下当前状态栏的沉浸式状态,这里经过 native 主动调用 channel 来同步

// 请求更新主题色到 native 端,这里使用了一个测试接口,之后要注意
var preTheme = SystemChrome.latestStyle;
if (preTheme != null) {
    SystemChannels.platform.invokeMethod("SystemChrome.setSystemUIOverlayStyle", _toMap(preTheme));
}
复制代码

FlutterNativeView 的 detach 和 attach

FlutterNativeView 的 detach 和 attach 的时候,须要注意 FlutterActivity 的生命周期和 FlutterView 中 surface 的建立状态,保证 FlutterActivityFlutterView 的生命周期同步到 FlutterNativeView

总结

总的来讲,咱们微店基于上述理论实现了一个混合栈插件,没有反射 flutter sdk,没有内存泄漏,不须要截图,支持页面间的数据传递(源码后续会开放),看似简单实际实现过程当中仍是遇到过不少小问题的,好比页面白屏,返回键无效之类的,这些都是 native 栈和 flutter 栈不一样步致使的。

后续计划

后续咱们微店混合栈的问题继续跟进的问题以下:

  1. 首次打开白屏时间长
  2. 不支持 Hero 动画
  3. iOS 没法避免截图方案
  4. 没法和 Navigator.of(context).pop() 结合

其中1.的话目前没有什么好的思路,可是2.3.4.点 已经有了想法,待实现验证,敬请期待。

做者简介

qigengxin,@WeiDian,2016年加入微店,目前主要负责微店App的基础支撑开发工做。

欢迎关注微店App技术团队官方公众号

微店App技术团队
相关文章
相关标签/搜索