作者:闲鱼技术——皓黯

  1. 在经历了近两个月的开发以及内部测试与线上灰度,FlutterBoost3.0preview版本终于与大家见面了,与beta版本相比,这个版本在不大动主体结构的基础上,增加了以下能力:
  1. 重构生命周期,确保生命周期语义准确
  2. 双端一致性近一步对齐
  3. 增加自定义的启动参数
  4. 实现页面返回传参方案
  5. 支持页面透明能力
  6. 增加自定义事件发送机制
  7. 增加前置拦截器
  8. 提供更完善的文档与例子

    由于篇幅有限,就不对以上能力一一展开了。如果对具体实现感兴趣的话,可以通过源码和文档来了解相应的功能,我们今天先来聊一聊生命周期部分的设计与实现、文档与用例和社区建设。

1. 页面生命周期设计

  1. FlutterBoost3.0有两个和页面相关的概念,一个是BoostContainer,另一个是BoostPage。每个BoostContainerNative层都会有一个与之对应的容器,比如在Android端,这个容器可能是FlutterBoostActivity或者FlutterBoostFragment,而在iOS端,这个容器则指的是FBFlutterViewContainer。每个BoostContainer都会有一个与之对应的Navigator,因而一个BoostContainer可以包含多个BoostPage,这个设计也被我们称之为双重栈。细心的同学可能已经发现了,当我们需要打开一个Flutter页面时,有一个额外的参数`withContainer`,这个参数设置为false时,表示打开页面时不需要打开一个新容器,而这个参数如果被设置为true,则会在打开页面的同时,去打开一个新容器。

FlutterBoost3.0发布preview版本 - 图1

  1. 在开发业务的过程中,我们常常需要知道一个Flutter页面的可见性,比如我们写了一个Flutter页面,里面有一个VideoWidget,我们希望VideoWidget在页面可见的时候播放,在页面不可见的时候暂停。对页面可见性变化的监听是FlutterBoost需要提供的核心能力之一,因此我们提供了一个名为PageVisibilityObserverAPI,用于专门监听页面可见性变化。
  2. 在一开始的设计当中,我们是通过上层Dart代码来掌控页面生命周期的。我们在FlutterBoostAppState里,维护了一个List<BoostContaner>,而在每个BoostContainer中,则维护了一个List<BoostPage>,所以显示的页面实际上就是顶部BoostContainer中的顶部BoostPage,所以每次顶部的BoostContainer发生变化,或者顶部BoostContainer中的顶部BoostPage发生变化,我们就认为发生了页面可见性变化,并将相应事件分发下去。
  3. 然而这个方案在实践的过程中,发现了一些问题,由于Dart侧只对Flutter的双层栈有感知,而对Native侧的页面栈则是毫无感知的,所以哪怕一个页面是顶部BoostContainer中的顶部BoostPage,也不见得这个页面就处于显示状态。举个例子,比如Flutter页面A去打开一个Native页面B,那么在打开Native页面B之后,Flutter页面A还是在双重栈顶部,因此如果不做任何处理,那么FlutterBoost还是认为Flutter页面A是可见的。我们也尝试了一些手段去解觉这些Bad Case,比如在Native层也维护一个Flutter页面栈用来感知页面显示隐藏等。但是在后续的验证过程中,我们发现这个方案可维护性不高,最终放弃了。
  4. 之后我们决定还是采用更稳妥的方案,BoostContainer的显示和隐藏,让Native容器自己来控制,当Native容器展示的时候发出onContainerShow事件通知对应的BoostContainer展示,Native容器隐藏时再发出onContainerHide事件通知对应的的BoostContainer隐藏,这样生命周期难以维护的问题也就解决了,这个方案的可维护性相比上一个方案要稳定,但是这个方案也有一个不得不解决的问题,第一次onContainerShow可能会收不到。我们以Android为例,FlutterBoostActivity创建后的第一次onResume发出了onContainerShow事件,然而该事件并不一定会被收到,因为此时可能页面对应的Widget还没有完成创建。稳定的生命周期事件是一个强诉求,所以这个问题我们需要在FlutterBoost3.0彻底解决。那么这个问题该如何解决呢?为了解决这个问题,我们将第一次收到onContainerShow事件单独处理,具体逻辑如下
  1. void containerDidShow(BoostContainer container) {
  2. final id = container.pageInfo.uniqueId;
  3. assert(id != null);
  4. if (!hasShownPageIds.contains(id)) {
  5. hasShownPageIds.add(id);
  6. // This case indicates it is the first time that this container show
  7. // So we should dispatch event using
  8. // PageVisibilityBinding.dispatchPageShowEventOnPageShowFirstTime
  9. // to ensure the page will receive callback
  10. PageVisibilityBinding.instance
  11. .dispatchPageShowEventOnPageShowFirstTime(container.topPage.route);
  12. } else {
  13. PageVisibilityBinding.instance
  14. .dispatchPageShowEvent(container.topPage.route);
  15. }
  16. }
  1. 而在PageVisibilityBinding.dispatchPageShowEventOnPageShowFirstTime中,我们将事件分发延迟到这一帧渲染结束
  1. ///When page show first time,we should dispatch event in [FrameCallback]
  2. ///to avoid the page can't receive the show event
  3. void dispatchPageShowEventOnPageShowFirstTime(Route<dynamic> route) {
  4. WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
  5. dispatchPageShowEvent(route);
  6. });
  7. }
  1. 这样我们就能确保第一次onContainerShow事件能够被接收到。从最后的代码来看,这是一个微不足道的改动,但是从设计上看,这个改动明确定义了页面生命周期事件,确保了生命周期事件一定被调用到。

2. Flutter应用生命周期设计

  1. 对于纯Flutter应用来说,一个Flutter应用实际上是跑在一个Activity或者ViewController上的,所以Flutter应用的生命周期实际上也和ActivityViewController的生命周期绑定了,这也是Flutter目前的实现。但是对于单引擎多页面的混合栈场景来说,这无疑是不适用的。如果我们不对Flutter应用生命周期进行任何处理,那么就有可能出现Flutter应用生命周期混乱,最终导致界面无法刷帧。之前我们解决这个问题的思路,都是从Native层出发,通过控制生命周期事件的发送,来校正Flutter应用的生命周期。比如Android端,就是在每次Flutter应用被pause的时候,补发一个resume事件,而iOS端则是绕过FlutterViewController生命周期方法来自行管理生命周期事件。这样做虽然校正了Flutter应用的生命周期,但最终也导致了上层应用的生命周期非常混乱,可能会出现pauseresume连续多次被调用的情况。
  2. 为了上层使用者能有一个稳定的Flutter应用生命周期,我们决定将这部分能力接管。我们提供了一个BoostFlutterBinding,用于接管生命周期,相关代码如下
  1. mixin BoostFlutterBinding on WidgetsFlutterBinding {
  2. bool _appLifecycleStateLocked = true;
  3. @override
  4. void initInstances() {
  5. super.initInstances();
  6. _instance = this;
  7. changeAppLifecycleState(AppLifecycleState.resumed);
  8. }
  9. static BoostFlutterBinding get instance => _instance;
  10. static BoostFlutterBinding _instance;
  11. @override
  12. void handleAppLifecycleStateChanged(AppLifecycleState state) {
  13. if (_appLifecycleStateLocked) {
  14. return;
  15. }
  16. Logger.log('boost_flutter_binding: handleAppLifecycleStateChanged ${state.toString()}');
  17. super.handleAppLifecycleStateChanged(state);
  18. }
  19. void changeAppLifecycleState(AppLifecycleState state) {
  20. if (SchedulerBinding.instance.lifecycleState == state) {
  21. return;
  22. }
  23. _appLifecycleStateLocked = false;
  24. handleAppLifecycleStateChanged(state);
  25. _appLifecycleStateLocked = true;
  26. }
  27. }
  1. 代码非常简单,就是将handleAppLifecycleStateChanged方法给hook掉,这样无论Native层如何调用Flutter设定的生命周期事件,都不会影响到上层Flutter应用的生命周期。另外我们提供了一个changeAppLifecycleState方法,这个方法可以真正来改变上层Flutter应用的生命周期,目前这个方法的调用时机与Flutter容器个数相关,当容器大于等于1Flutter应用的生命周期状态为resumed,而容器个数为0时,Flutter应用的生命周期状态则为paused

3. 文档与用例完善

  1. FlutterBoost在之前只有一份接入文档,这对使用者来说并不算友好。因此我们决定提供更完善的文档。我们梳理了大家最关心的几个主题:
  • 集成详细步骤
  • 基本的路由API
  • 页面生命周期监测相关API
  • 自定义发送跨端事件API

    对于这些主题,我们这次都提供了相对应的文档,让使用者接入FlutterBoost可以更轻松,在遇到问题的时候也能更容易排查。另外我们也正在编写全新的example3.0,配合文档使用,在这个新的example3.0中,我们会把所有文档中提到的部分都以用例的方式呈现出来,希望这能帮助到大家。

4. 社区建设

  1. 之后Boost所有的开发计划工作,都会在project看板上体现出来,对应的地址为( https://github.com/alibaba/flutter_boost/projects)。对我们工作感兴趣的同学,可以在看板上看到我们的工作规划,欢迎一起交流探讨。另外如果有哪些想要我们支持的feature,也可以提issue给我们,如果我们评估合理,也会加入到看板上。

FlutterBoost3.0发布preview版本 - 图2

  1. 于此同时,我们增加了一个AUTHORS文件(https://github.com/alibaba/flutter_boost/blob/master/AUTHORS),记录所有FlutterBoost的贡献者(Contributors)。之后给我们提PR的同学,在提供PR的同时,也可以在这个文件上,加上你的名字,感谢大家为Boost做出的贡献。

5. 未来展望

  1. FlutterBoost3.0作为AliFlutter的核心基础设施,目前主要由闲鱼团队和UC Hummer团队进行开发维护,主要开发者包括noborder0xZOnechristyujColdPaleLightluckysmg,另外也要感谢seedotleeCheungSKeibktokyjk等同学为FlutterBoost3.0做出的贡献,目前集团内已有多个App接入了FlutterBoost3.0,包括夸克、淘宝联盟、吃货笔记等。
  2. 之后FlutterBoost3.0preview版本原则上不会再做Breaking Change,目前已经使用了beta版的同学,也可以陆续切到preview版本上了,下个阶段我们会将工作重心放在issue收敛与文档用例补齐上。
  3. FlutterBoost作为一个开源项目,还有很长的路要走,感谢大家一路支持和包容,我们也希望有更多同学能参与到这个项目中来。