Flutter路由栈和生命周期解析

原文:《Flutter路由栈和生命周期解析》 作者:Harry_Chen

什么是路由

路由(Routes)是什么?路由是屏幕或应用程序页面的抽象。

Flutter 使我们能够优雅地管理路由主要依赖的是 Navigator(导航器)类。这是一个用于管理一组具有某种进出规则的页面的 Widget,也就是说用它我们能够实现各个页面间有规律的切换。而这里的规则便是在其内部维护的一个“ 路由栈”。

命名路由

在一般应用中,我们用的最多的还是命名路由,它是将应用中需要访问的每个页面命名为不重复的字符串,我们便可以通过这个字符串来将该页面实例推进路由。

例如:

new MaterialApp(
  home: new Screen1(),
  routes: <String, WidgetBuilder> {
    '/screen1': (BuildContext context) => new Screen1(),
    '/screen2' : (BuildContext context) => new Screen2(),
    '/screen3' : (BuildContext context) => new Screen3(),
    '/screen4' : (BuildContext context) => new Screen4()
  },
)

路由栈

Navigator维护了一个堆栈,来保存路由跳转的状态。

screen1跳转到screen2

Navigator.pushNamed(context, '/screen2')

退出路由pop

Navigator.pop()

切勿用pushNamed替代pop,不然就会出现下面的情况了。

生命周期

简单地聊完路由栈,接下来介绍 StatefulWidget一系列的生命周期。

上图就是 State 的生命周期图。

StatefulWidget.createState()

Framework 通过调用 StatefulWidget.createState() 来创建一个 State。

initState()

新创建的 State 会和一个 BuildContext 产生关联,此时认为 State 已经被安装好了,initState() 函数将会被调用。通常,我们可以重写这个函数,进行初始化操作。

didChangeDependencies()

在 initState() 调用结束后,这个函数会被调用。事实上,当 State 对象的依赖关系发生变化时,这个函数总会被 Framework 调用。

build()

经过以上步骤,系统认为一个 State 已经准备好了,就会调用 build() 来构建视图。我们需要在这个函数中,返回一个 Widget。

deactivate()

当 State 被暂时从视图树中移除时,会调用这个函数。页面切换时,也会调用它,因为此时 State 在视图树中的位置发生了变化,需要先暂时移除后添加。⚠️注意,重写的时候必须要调用 super.deactivate()。

dispose()

当 State 被永久的从视图树中移除,Framework 会调用该函数。在销毁前触发,我们可以在这里进行最终的资源释放。在调用这个函数之前,总会先调用 deactivate()。⚠️注意,重写的时候必须要调用 super.dispose()

didUpdateWidget(covariant T oldWidget)

当 widget 的配置发生变化时,会调用这个函数。比如,Hot-reload 的时候就会调用这个函数。这个函数调用后,会调用 build()。

setState()

当我需要更新 State 的视图时,需要手动调用这个函数,它会触发 build()

一般情况下我们用的最多的是initState,build,deactivate,dispose,setState。接下来也主要讲initState以及dispose在路由跳转过程中的触发条件。

路由跳转时的initState,deactivate,dispose

一开始我们是这么写的

@override
void initState() {
  super.initState();
  print('------initstate--------');
}

@override
void deactivate() {
  super.deactivate();
  print('-----deactivate----');
}

@override
void dispose() {
  super.dispose();
  print('----dispose--------');
}

当我们通过pushNamed跳转到其他路由,发现deactivate触发了,但是dispose并没有触发,并且再次通过pushNamed回到本页的时候,initstate也并没有再次执行。

写多了Vue单页面应用的同学,应该知道这其实很让人困扰。我们很多时候需要像Vue一样,在created的时候初始化数据,在beforeDestroy的时候移除页面一些监听事件或者销毁定时器,防止内存泄露。

让我们回到生命周期说明那里:

deactivate() 当 State 被暂时从视图树中移除时,会调用这个函数。

dispose()当 State 被永久的从视图树中移除,Framework 会调用该函数。

此时是不是恍然大悟!pushNamed只是暂时把视图移除了,并没有彻底移除,所以导致了一些奇怪的问题(前提是你跟我一样,业务上需要控制页面的初始化和销毁)

那怎么办。此时popAndPushNamed闪亮登场!用它替代pushNamed,你就会发现该触发的事件都触发了~

popAndPushNamed将当前路由弹出并跳转到新的路由,彻底移除旧路由。

还有类似的方法pushReplacementNamed,pushNamedAndRemoveUntil等,就不赘述了。

取消导航Header的返回键

AppBar(automaticallyImplyLeading: false)

WillPopScope取消Android物理返回键

这个方法也可以取消iOS默认左右滑动切换路由。

Widget build(BuildContext context) {
  return WillPopScope(
      onWillPop: () async {
        return false;
      },
      child: Scaffold(
        // Your Code
      )
  );
}

留下评论