一、背景:为什么需要 Pause 机制?
在开发 Flutter 应用时,我们经常会遇到一些场景,页面虽然存在于内存中,但对用户并不可见。例如:
- 被新页面覆盖:用户从页面 A 跳转到页面 B,此时页面 A 仍然在路由栈中,但被 B 覆盖。
- 在 TabBarView 等场景中 :当使用
TabBarView或PageView时,只有当前页面是可见的。这是一个需要开发者手动管理,以暂停后台页面活动的典型场景。 - 应用切换到后台:用户按下 Home 键或切换到其他 App,整个 Flutter 应用进入后台。
在这些"不可见"的状态下,如果页面关联的 ViewModel 仍在执行耗时操作(如网络轮询、数据计算)并通过 notifyListeners() 频繁触发 setState(),就会导致一系列问题:
- 性能浪费:UI 重建是昂贵的,对一个看不见的 Widget 进行重建,纯属浪费 CPU 和 GPU 资源。
- 电量消耗:不必要的计算和渲染会加速电量消耗,影响用户体验。
- 潜在的 Bug:在页面不可见时更新 UI,可能会引发意想不到的状态错误。
核心痛点 :我们需要一种机制,能够智能地识别页面是否可见,并在页面不可见时"暂停" ViewModel 的刷新,在页面恢复可见时再"恢复"并同步最新的状态。这就是 view_model 包中 Pause 机制的设计初衷。
二、如何监控页面的"不可见"状态?
为了实现智能暂停,首先要能准确地监控到页面的可见性变化。Flutter 提供了多种机制来捕捉这些变化,view_model 将它们封装成了不同的 PauseProvider。
1. 路由事件 (PageRoutePauseProvider)
通过 RouteObserver 和 RouteAware,我们可以监听路由的推入(push)和弹出(pop)事件。
didPushNext():当一个新路由被推到当前路由之上时触发。这意味着当前页面被覆盖了,应该暂停。didPopNext():当顶层路由被弹出,使当前路由重新可见时触发。这意味着当前页面恢复可见,应该恢复。
PageRoutePauseProvider 就是基于这个原理,在页面被覆盖时发出暂停信号。
2. 应用生命周期 (AppPauseProvider)
通过 WidgetsBindingObserver,我们可以监听整个应用的生命周期状态。
AppLifecycleState.hidden(或paused,inactive):当应用切换到后台或被电话等系统事件中断时触发。此时整个应用都不可见,应该暂停。AppLifecycleState.resumed:当应用重新回到前台时触发。此时应用恢复可见,应该恢复。
AppPauseProvider 负责在应用进入后台时发出暂停信号。
3. TickerMode (TickerModePauseProvider)
这是本文的重点。TickerMode 是 Flutter 动画系统中的一个重要概念,它同样可以用来判断 Widget 的可见性。
三、重点:为什么 TickerMode 可以判断可见性?
什么是 Ticker?
Ticker 是 Flutter 动画的"心跳",它以屏幕的刷新率(通常是 60fps)为节奏,周期性地触发回调。AnimationController 等动画控制器就是依赖 Ticker 来驱动动画值的变化。
什么是 TickerMode?
TickerMode 是一个 InheritedWidget,它允许你显式地启用或禁用 其子树中所有 Ticker 的活动。
dart
TickerMode(
enabled: true, // or false
child: MyAnimatedWidget(),
);
- 当
enabled为true时,子树中的Ticker可以正常"心跳",动画会播放。 - 当
enabled为false时,子树中的Ticker会被"静音"(muted),心跳停止,所有动画都会暂停。
TickerMode 与可见性的关系
在 TabBarView 或 PageView 这类场景中,为了优化性能,开发者需要手动使用 TickerMode 来包裹非当前显示的页面,以暂停它们的动画行为。
当你滑动 TabBarView 时,它内部会做这样的事情:
- 对于当前可见的 Tab ,它会用
TickerMode(enabled: true, ...)包裹。 - 对于其他不可见的 Tab ,它会用
TickerMode(enabled: false, ...)包裹。
这样一来,不可见 Tab 中的所有动画(如 AnimationController、ListView 的滚动动画等)都会自动暂停,从而节省了大量的性能开销。
结论 :TickerMode.of(context) 的 enabled 状态,成为了一个判断当前 Widget 是否在 TabBarView 这类组件中"可见"的绝佳指标。
TickerModePauseProvider 正是利用了这一点:它会监听 TickerMode.getNotifier(context),当 TickerMode 被禁用时,它就会发出暂停信号。
四、ViewModel 的混合判断机制与自定义 Pause
混合判断机制 (PauseAwareController)
view_model 的强大之处在于,它不依赖于任何单一的可见性判断,而是将上述所有机制组合在了一起。
ViewModelStateMixin 会自动创建一个 PauseAwareController,并为其注入多个 PauseProvider:
dart
late final _pauseAwareController = PauseAwareController(
providers: [
_appPauseProvider, // 应用切后台时暂停
_routePauseProvider, // 路由被覆盖时暂停
_TickerModePauseProvider, // TickerMode = false 时暂停
],
// ...
);
PauseAwareController 的核心逻辑非常清晰:
只要有任意一个
provider要求暂停,ViewModel就会进入暂停状态。
dart
// PauseAwareController 内部逻辑
final newPauseState = _providerPauseStates.values.any((isPaused) => isPaused);
这意味着,无论是因为页面被覆盖、应用切到后台,还是因为所在的 Tab 变成了非当前项,ViewModel 都能准确地暂停 setState() 的执行。
工作流程
-
进入暂停:
- 某个
PauseProvider(如PageRoutePauseProvider)发出true(暂停)信号。 PauseAwareController检测到后,将ViewModel标记为isPaused = true。- 此后,即使
ViewModel调用notifyListeners(),ViewModelAttacher也会拦截setState(),并标记hasMissedUpdates = true。UI 不会重建。
- 某个
-
恢复运行:
- 所有
PauseProvider都发出了false(恢复)信号。 PauseAwareController检测到后,将ViewModel标记为isPaused = false,并触发onResume回调。ViewModelAttacher在onResume回调中检查到hasMissedUpdates为true,于是执行一次setState(),将所有被"暂停"期间的更新一次性应用到 UI 上。
- 所有
自定义 PauseProvider
PauseAwareController 的设计是完全开放的。你可以实现自己的 ViewModelPauseProvider 来应对任何特殊的暂停逻辑。
例如,你可以创建一个 ConnectionPauseProvider,在网络断开时暂停所有依赖网络的 ViewModel。
步骤:
-
创建自定义 Provider:
dartclass ConnectionPauseProvider extends ViewModelPauseProvider { final _controller = StreamController<bool>.broadcast(); ConnectionPauseProvider() { Connectivity().onConnectivityChanged.listen((status) { if (status == ConnectivityResult.none) { _controller.add(true); // 网络断开,请求暂停 } else { _controller.add(false); // 网络恢复,请求恢复 } }); } @override Stream<bool> get onPauseStateChanged => _controller.stream; @override void dispose() => _controller.close(); } -
添加到 ViewModel : 你可以通过
ViewModelStateMixin的addPauseProvider方法,在initState中动态添加你的自定义 Provider。dartclass _MyPageState extends State<MyPage> with ViewModelStateMixin<MyPage> { @override void initState() { super.initState(); // 添加自定义的暂停逻辑 addPauseProvider(ConnectionPauseProvider()); } // ... }
通过这种方式,你可以将任何业务逻辑转化为 ViewModel 的暂停/恢复条件,实现高度灵活和精-细化的性能管理。
总结与推荐
view_model 的 Pause 机制提供了一套强大而灵活的解决方案 ,如果你正在寻找一个能够智能管理 Widget 生命周期、优化性能并保持代码整洁的状态管理框架:
立即体验 :pub.dev/packages/vi... 🚀