告别卡顿和耗电!view_model 的 Pause 机制如何拯救你的 Flutter 应用

一、背景:为什么需要 Pause 机制?

在开发 Flutter 应用时,我们经常会遇到一些场景,页面虽然存在于内存中,但对用户并不可见。例如:

  1. 被新页面覆盖:用户从页面 A 跳转到页面 B,此时页面 A 仍然在路由栈中,但被 B 覆盖。
  2. 在 TabBarView 等场景中 :当使用 TabBarViewPageView 时,只有当前页面是可见的。这是一个需要开发者手动管理,以暂停后台页面活动的典型场景。
  3. 应用切换到后台:用户按下 Home 键或切换到其他 App,整个 Flutter 应用进入后台。

在这些"不可见"的状态下,如果页面关联的 ViewModel 仍在执行耗时操作(如网络轮询、数据计算)并通过 notifyListeners() 频繁触发 setState(),就会导致一系列问题:

  • 性能浪费:UI 重建是昂贵的,对一个看不见的 Widget 进行重建,纯属浪费 CPU 和 GPU 资源。
  • 电量消耗:不必要的计算和渲染会加速电量消耗,影响用户体验。
  • 潜在的 Bug:在页面不可见时更新 UI,可能会引发意想不到的状态错误。

核心痛点 :我们需要一种机制,能够智能地识别页面是否可见,并在页面不可见时"暂停" ViewModel 的刷新,在页面恢复可见时再"恢复"并同步最新的状态。这就是 view_model 包中 Pause 机制的设计初衷。


二、如何监控页面的"不可见"状态?

为了实现智能暂停,首先要能准确地监控到页面的可见性变化。Flutter 提供了多种机制来捕捉这些变化,view_model 将它们封装成了不同的 PauseProvider

1. 路由事件 (PageRoutePauseProvider)

通过 RouteObserverRouteAware,我们可以监听路由的推入(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(),
);
  • enabledtrue 时,子树中的 Ticker 可以正常"心跳",动画会播放。
  • enabledfalse 时,子树中的 Ticker 会被"静音"(muted),心跳停止,所有动画都会暂停。

TickerMode 与可见性的关系

TabBarViewPageView 这类场景中,为了优化性能,开发者需要手动使用 TickerMode 来包裹非当前显示的页面,以暂停它们的动画行为。

当你滑动 TabBarView 时,它内部会做这样的事情:

  • 对于当前可见的 Tab ,它会用 TickerMode(enabled: true, ...) 包裹。
  • 对于其他不可见的 Tab ,它会用 TickerMode(enabled: false, ...) 包裹。

这样一来,不可见 Tab 中的所有动画(如 AnimationControllerListView 的滚动动画等)都会自动暂停,从而节省了大量的性能开销。

结论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() 的执行。

工作流程

  1. 进入暂停

    • 某个 PauseProvider(如 PageRoutePauseProvider)发出 true(暂停)信号。
    • PauseAwareController 检测到后,将 ViewModel 标记为 isPaused = true
    • 此后,即使 ViewModel 调用 notifyListeners()ViewModelAttacher 也会拦截 setState(),并标记 hasMissedUpdates = true。UI 不会重建。
  2. 恢复运行

    • 所有 PauseProvider 都发出了 false(恢复)信号。
    • PauseAwareController 检测到后,将 ViewModel 标记为 isPaused = false,并触发 onResume 回调。
    • ViewModelAttacheronResume 回调中检查到 hasMissedUpdatestrue,于是执行一次 setState(),将所有被"暂停"期间的更新一次性应用到 UI 上。

自定义 PauseProvider

PauseAwareController 的设计是完全开放的。你可以实现自己的 ViewModelPauseProvider 来应对任何特殊的暂停逻辑。

例如,你可以创建一个 ConnectionPauseProvider,在网络断开时暂停所有依赖网络的 ViewModel

步骤

  1. 创建自定义 Provider

    dart 复制代码
    class 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();
    }
  2. 添加到 ViewModel : 你可以通过 ViewModelStateMixinaddPauseProvider 方法,在 initState 中动态添加你的自定义 Provider。

    dart 复制代码
    class _MyPageState extends State<MyPage> with ViewModelStateMixin<MyPage> {
      @override
      void initState() {
        super.initState();
        // 添加自定义的暂停逻辑
        addPauseProvider(ConnectionPauseProvider());
      }
      // ...
    }

通过这种方式,你可以将任何业务逻辑转化为 ViewModel 的暂停/恢复条件,实现高度灵活和精-细化的性能管理。


总结与推荐

view_modelPause 机制提供了一套强大而灵活的解决方案 ,如果你正在寻找一个能够智能管理 Widget 生命周期、优化性能并保持代码整洁的状态管理框架:

立即体验pub.dev/packages/vi... 🚀

相关推荐
metaRTC3 小时前
webRTC IPC客户端Flutter版编程指南
flutter·webrtc·ipc
liuxf12343 小时前
鸿蒙Flutter,No Hmos SDK found.
flutter·华为·harmonyos
西西学代码9 小时前
Flutter---Listview横向滚动列表(1)
flutter
XI锐真的烦12 小时前
Flutter Windows 下“Running Gradle task ‘assembleDebug‘...” 卡住一整天的终极解决办法
windows·flutter
苦逼的搬砖工21 小时前
基于 easy_rxdart 的轻量响应式与状态管理架构实践
android·flutter
SoaringHeart1 天前
Flutter组件封装:标签拖拽排序 NDragSortWrap
前端·flutter
天天开发1 天前
Flutter每日库: local_auth本地设备验证插件
flutter
天天开发1 天前
Flutter每日库: logger自定义日志格式并输出到文件
flutter