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
除了上述问题外,还存在一个严重的问题:FlutterView1
和 FlutterView2
属于两个 isolate,二者至关于两个 flutter engine 实例,在内存上隔离的,不共享函数
总结: 该方案有如下缺点布局
FlutterView
之间没法共享内存(图片缓存,全局单例都不可公用)FlutterActivity
都会启动一个新的 Flutter 实例全局共用一个 FlutterView
,每一个 flutter 页面都有一个对应的 native 页面:此方案能够解决方案一中的内存共享浪费问题post
此方案的大体原理以下: 测试
关键步骤是 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
此方案的结构图以下:
和方案二不一样的是,方案三中 FlutterView
和 FlutterActivity
绑定在一块儿了,这样能够避免 FlutterView
单例化形成的 context
泄漏。
并且相比于方案二,要实现此方案只须要知足一条规则便可:
FlutterNativeView
从 FlutterActivity1 detach 而后 attach 到 FlutterActivity2 的以前,必须保证新的 flutter page 已经 push 到 flutter 的栈中,不然 FlutterActivity2 显示的仍是 FlutterActivity1 中显示的界面你会发现,这里不须要 FlutterNativeView
在 detach 的时候构造一份当前页面的快照而后占位显示.
由于在页面切换的时候 FlutterView
并无从 FlutterActivity
中移除,当 FlutterNativeView
从 FlutterView
detach 的时候,FlutterView
显示的内容就不会再更新了,至关于 Android 上的 onPreDraw
函数返回 false
, 因此这里不必截图保存快照。
通过上述方案的探索,决定在 android 上使用第三套方案
iOS 由于有侧滑返回,没法避免截图,由于在侧滑的时候,页面不必定结束。因此我这里抛弃了 android 上侧滑返回(原本 android 的侧滑返回就很奇怪,不支持合理)
实现关键点:
FlutterView
单 FlutterNativeView
实例FlutterNativeView
和 FlutterView
脱离,才能够在 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 才会有返回,顺着这个思路去实现很简单。
只要 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 的时候,须要注意 FlutterActivity
的生命周期和 FlutterView
中 surface 的建立状态,保证 FlutterActivity
和 FlutterView
的生命周期同步到 FlutterNativeView
总的来讲,咱们微店基于上述理论实现了一个混合栈插件,没有反射 flutter sdk,没有内存泄漏,不须要截图,支持页面间的数据传递(源码后续会开放),看似简单实际实现过程当中仍是遇到过不少小问题的,好比页面白屏,返回键无效之类的,这些都是 native 栈和 flutter 栈不一样步致使的。
后续咱们微店混合栈的问题继续跟进的问题以下:
Navigator.of(context).pop()
结合其中1.
的话目前没有什么好的思路,可是2.
、3.
、4.
点 已经有了想法,待实现验证,敬请期待。
qigengxin,@WeiDian,2016年加入微店,目前主要负责微店App的基础支撑开发工做。
微店App技术团队
官方公众号