type
status
date
slug
summary
tags
category
icon
前景介绍
Flutter 是谷歌开发的一款可以跨平台开发的 UI 框架,它的原理接近于游戏引擎,目的在于统一 Android/iOS 两端开发,Flutter 页面有自己的栈,正常情况下,如果一个 app 完全由 Flutter 构成,那么只需要一个
FlutterView
即可。上述方案只适用于一些新构建的 app,对于一些已有的 app,是不可能用 flutter 来重构的,成本太大,周期太长,所以这里需要实现一套 Native 页面栈和 Flutter 页面栈的管理方案,混合栈。
<!-- more -->
关于混合栈的管理,闲鱼出过一篇文章,但是对于它的兼容性问题和截图问题,没有采用,不过作者对闲鱼的混合栈源码做了参考,这里感谢闲鱼的源码分享。本文是在 android 的基础上讲解实现方式的,iOS 目前使用的还是截图方案,其余的原理差不多
方案探索
方案一
不进行任何处理,直接使用
FlutterActivity
来打开页面:此方法最接近原生,交替打开几个页面后会呈现出以下页面结构
每个
FlutterActivity
都有自己的 flutter 栈,此时如果用户点击了返回按钮的时候页面退出的呈现形式是正常的,但是如果 app 用了侧滑返回的话工作就会不正常侧滑结束 FlutterActivit2 会一下子结束三个 flutter widget 页面
除了上述问题外,还存在一个严重的问题:
FlutterView1
和 FlutterView2
属于两个 isolate,两者相当于两个 flutter engine 实例,在内存上隔离的,不共享总结: 该方案有以下缺点
- 不兼容现有的侧滑返回
- 页面的生命周期埋点需要在 dart 层重新实现一套
- 不同
FlutterView
之间无法共享内存(图片缓存,全局单例都不可公用)
- 资源占用大:每次启动一个
FlutterActivity
都会启动一个新的 Flutter 实例
- 界面切换体验有差别:Native 页面之间的切换动画和 flutter 页面之间的切换动画有差别
方案二
全局共用一个
FlutterView
,每个 flutter 页面都有一个对应的 native 页面:此方案可以解决方案一中的内存共享浪费问题此方案的大致原理如下:
关键步骤是 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
实例
- 每一个 flutter 页面对应一个 native 的 activity,并通过一个 id 关联,做到栈同步
- Flutter 和 Native 基于 url 的方式开管理页面
- 禁用 flutter 自带的页面切换动画,使用 native 自带的动画来实现
- 使用一个空白的 widget 作为 flutter 页面的栈底
- 当打开新页面或者退出页面的时候,必须先让
FlutterNativeView
和FlutterView
脱离,才可以在 flutter 栈操作页面的进退
整个页面启动跳转打大致流程图如下:
左侧是 flutter 的执行流程,右侧是 android native activity 的执行流程
页面的传参和数据返回
上述代码的设计还没有考虑页面之间的数据传递,原生 flutter 的页面数据传递是这样的:
所以在设计页面数据传递的时候向原生的看齐,如下所示:
当 native 端的
MethodCallHandler
被调用时,有个参数是 result
,只有给这个 result
设置了结果 (result.success(xxx)
),上面的 await 才会有返回,顺着这个思路去实现很简单。只要
FlutterActivity
把由当前 flutter 发起打开页面请求的 result
对象保存起来,然后调用 startActivityForResult
来启动页面,等页面结束后会回调到 onActivityResult
中,此时再通过保存的 result
对象,把结果返回给 flutter 端。传参直接使用 intent 传参即可
沉浸式同步的问题
每次启动一个新的
FlutterActivity
都需要和 flutter 端同步下当前状态栏的沉浸式状态,这里通过 native 主动调用 channel 来同步FlutterNativeView 的 detach 和 attach
FlutterNativeView
的 detach 和 attach 的时候,需要注意 FlutterActivity
的生命周期和 FlutterView
中 surface 的创建状态,保证 FlutterActivity
和 FlutterView
的生命周期同步到 FlutterNativeView
总结
总的来说,我基于上述理论实现了一个混合栈插件,没有反射 flutter sdk,没有内存泄漏,不需要截图,支持页面间的数据传递(源码后续会开放),看似简单实际实现过程中还是遇到过很多小问题的,比如页面白屏,返回键无效之类的,这些都是 native 栈和 flutter 栈不同步导致的
现在我这里混合栈的问题还剩:
- 首次打开白屏时间长
- 不支持 Hero 动画
- iOS 无法避免截图方案
- 无法和
Navigator.of(context).pop()
结合
1 的话目前没有什么好的思路,但是2, 3, 4本人已经有了想法,待实现验证
- Author:NotionNext
- URL:https://tangly1024.com/article/example-2
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts