前言

WidgetsBindingObserver 是 Flutter 中一个非常重要的接口,它允许你的 Flutter 应用监听和响应底层平台(操作系统)和 Flutter 引擎的各种生命周期事件和系统变化。

作用

WidgetsBindingObserver 的核心作用是作为连接 Flutter 应用逻辑和底层系统事件的桥梁。通过实现这个接口,你可以:

  • 监听应用生命周期状态变化: 这是最常用、最重要的功能。你可以知道你的应用何时进入前台、后台,何时被暂停或终止。
  • 监听系统配置变化: 例如,屏幕尺寸变化、文字缩放因子变化、系统亮度模式(深色/浅色模式)变化、地区设置(语言)变化等。
  • 响应内存压力警告: 当系统内存不足时,可以通知你的应用采取措施释放资源。
  • 在 Hot Reload 时执行特定操作: 允许你在开发过程中热重载后执行一些初始化或清理工作。

总的来说,它的作用是让你的应用能够感知并适应外部环境的变化,从而实现更健壮、更智能的用户体验和资源管理。

使用场景

应用生命周期管理 (didChangeAppLifecycleState)

这是最主要、最频繁使用的场景。你需要根据 AppLifecycleState 的不同值来执行相应操作:

  • AppLifecycleState.resumed (活跃/前台):

    • 恢复媒体播放: 当用户从后台切回应用时,恢复音乐、视频播放。
    • 重新连接网络: 重新建立 WebSocket 连接或刷新实时数据。
    • 刷新数据: 例如,刷新聊天列表、社交媒体动态,检查是否有新消息。
    • 启动动画或相机预览: 只有在前台时才进行耗时操作。
    • 重新认证: 如果应用长时间处于后台,切回时可能需要重新验证用户身份。
  • AppLifecycleState.inactive (不活跃/暂停 - 仅 iOS 和 Android 部分情况):

    • 在 iOS 上,当有电话呼入、短信弹出、应用切换器激活时,应用会进入 inactive 状态。通常在此状态下应暂停不必要的动画或耗时操作,但UI通常仍然可见。
    • 在 Android 上,inactive 状态不常用,通常直接从 resumed 跳到 paused
  • AppLifecycleState.paused (暂停/后台):

    • 暂停媒体播放: 当用户切换到其他应用时,停止音乐、视频播放。
    • 断开网络连接: 断开实时连接(如游戏、聊天),减少耗电。
    • 保存用户数据: 在应用进入后台前保存草稿、进度、用户设置等,防止数据丢失。
    • 停止相机预览: 关闭相机硬件,释放资源。
    • 清理敏感数据: 将密码、银行卡号等敏感信息从内存中清除。
  • AppLifecycleState.detached (分离/终止 - 仅 Android):

    • 仅在 Android 上出现,表示应用进程可能仍在运行,但 Flutter 引擎的视图已被移除。通常在 Flutter Activity 被销毁但进程未被杀死时发生。
    • 可以进行最后的资源清理工作,但通常在 paused 状态下完成大部分清理。

系统配置变化 (didChange...)

  • didChangePlatformBrightness (深色/浅色模式切换):

    • 更新主题或UI: 如果你的应用有自定义的 UI 元素,而不仅仅依赖 ThemeData,你可能需要手动更新它们的颜色或样式以适应新的亮度模式。
    • 切换图片资源: 根据亮暗模式加载不同的图片或图标。
  • didChangeLocales (系统语言/地区设置变化):

    • 重新加载本地化资源: 如果你的应用使用了自定义的国际化方案,或者需要根据语言加载不同的动态内容,可以在这里触发重新加载。
    • 更新日期/时间格式: 根据新的地区设置调整显示格式。
  • didChangeTextScaleFactor (文字缩放因子变化):

    • 调整自定义文本布局: 如果你的应用中有非常复杂的文本布局,可能需要根据用户设置的文字大小偏好进行微调。通常 Flutter 默认的 MediaQuery 会处理大部分情况。
  • didChangeMetrics (屏幕尺寸/像素比变化):

    • 响应屏幕旋转或分屏模式: 某些情况下,你可能需要根据新的屏幕尺寸重新绘制或调整布局,尤其是在使用自定义渲染或 Canvas 绘图时。

内存管理 (didHaveMemoryPressure)

  • 清理缓存: 当系统发出内存压力警告时,主动清除图片缓存、网络请求缓存或其他不必要的内存占用,以防止应用被系统强制关闭。
  • 释放非关键资源: 释放一些可以随时重新加载的、但当前不急需的大型对象。

用法

使用 WidgetsBindingObserver 的典型做法就是将其混入到一个 StatefulWidgetState 类中:

  1. 混入 WidgetsBindingObserver
    State 类定义上使用 with WidgetsBindingObserver

  2. 注册观察者:
    initState() 方法中,调用 WidgetsBinding.instance.addObserver(this) 将当前 State 对象注册为观察者。

  3. 实现回调方法:
    覆盖对应的回调方法(如 didChangeAppLifecycleState)。

  4. 移除观察者:
    dispose() 方法中,务必调用 WidgetsBinding.instance.removeObserver(this) 来移除观察者,防止内存泄漏。

示例代码:

import 'package:flutter/material.dart';

class LifecycleAwareWidget extends StatefulWidget {
  @override
  _LifecycleAwareWidgetState createState() => _LifecycleAwareWidgetState();
}

class _LifecycleAwareWidgetState extends State<LifecycleAwareWidget>
    with WidgetsBindingObserver { // 1. 混入 WidgetsBindingObserver

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this); // 2. 注册观察者
    print('Widget initState - Observer added');
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this); // 4. 移除观察者,防止内存泄漏
    print('Widget dispose - Observer removed');
    super.dispose();
  }

  // 3. 实现回调方法
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print('AppLifecycleState changed: $state');
    switch (state) {
      case AppLifecycleState.resumed:
        // 应用回到前台
        print('App is back in foreground!');
        // 示例:重新加载数据,恢复动画,重新连接Socket等
        break;
      case AppLifecycleState.inactive:
        // 应用处于不活跃状态(如iOS来电、多任务切换器)
        print('App is inactive.');
        // 示例:暂停非关键的动画
        break;
      case AppLifecycleState.paused:
        // 应用进入后台
        print('App is in background.');
        // 示例:保存数据,暂停媒体播放,断开Socket,停止相机等
        break;
      case AppLifecycleState.detached:
        // 应用被分离(Android,Flutter引擎视图被移除)
        print('App is detached.');
        // 示例:执行最后的清理
        break;
      case AppLifecycleState.hidden:
        // 应用被隐藏 (Web 或特定场景,不常用)
        print('App is hidden.');
        break;
    }
  }

  @override
  void didChangeMetrics() {
    print('Metrics changed (e.g., screen size, orientation)');
    // 示例:响应屏幕旋转
  }

  @override
  void didChangePlatformBrightness() {
    print('Platform brightness changed (dark/light mode)');
    // 示例:更新自定义UI的主题
  }

  @override
  void didChangeLocales() {
    print('Locales changed (language settings)');
    // 示例:重新加载国际化资源
  }

  @override
  void didHaveMemoryPressure() {
    print('Received memory pressure warning!');
    // 示例:清理缓存
  }

  // 其他回调方法...

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('WidgetsBindingObserver Demo'),
      ),
      body: Center(
        child: Text('Check console for lifecycle and system events!'),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: LifecycleAwareWidget(),
  ));
}

注意事项:

  • 不阻塞UI: 在回调方法中不要执行耗时的操作,否则的话,会阻塞主线程,影响用户体验。耗时操作应当其放到后台线程中去执行。
  • 处理平台差异: AppLifecycleState 的某些状态在不同平台上的行为可能略有不同(例如 inactive 主要用于 iOS)。在编写逻辑时需要考虑这些差异。

V.S. AppLifecycleListener

如果仅需要检测生命周期的变化的话,建议选择 AppLifecycleListener,因为相较于 WidgetsBindingObserver,后者提供了更细粒度的、独立的生命周期回调方法。

AppLifecycleListener 是 Flutter 3.10 引入的新 API,专门用于更清晰、更现代地管理应用生命周期。专门用来解决 didChangeAppLifecycleState 中某些状态的歧义,并提供更符合现代操作系统(尤其是桌面和 Web)行为的生命周期管理。

Diagram of the application lifecycle defined by the AppLifecycleState enum

示例

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';


void main() {
  runApp(const AppLifecycleListenerExample());
}

class AppLifecycleListenerExample extends StatelessWidget {
  const AppLifecycleListenerExample({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: Scaffold(body: AppLifecycleDisplay()));
  }
}

class AppLifecycleDisplay extends StatefulWidget {
  const AppLifecycleDisplay({super.key});

  @override
  State<AppLifecycleDisplay> createState() => _AppLifecycleDisplayState();
}

class _AppLifecycleDisplayState extends State<AppLifecycleDisplay> {
  late final AppLifecycleListener _listener;
  final ScrollController _scrollController = ScrollController();
  final List<String> _states = <String>[];
  late AppLifecycleState? _state;

  @override
  void initState() {
    super.initState();
    _state = SchedulerBinding.instance.lifecycleState;
    _listener = AppLifecycleListener(
      onShow: () => _handleTransition('show'),
      onResume: () => _handleTransition('resume'),
      onHide: () => _handleTransition('hide'),
      onInactive: () => _handleTransition('inactive'),
      onPause: () => _handleTransition('pause'),
      onDetach: () => _handleTransition('detach'),
      onRestart: () => _handleTransition('restart'),
      onStateChange: _handleStateChange,
    );
    if (_state != null) {
      _states.add(_state!.name);
    }
  }

  @override
  void dispose() {
    _listener.dispose();
    super.dispose();
  }

  void _handleTransition(String name) {
    setState(() {
      _states.add(name);
    });
    _scrollController.animateTo(
      _scrollController.position.maxScrollExtent,
      duration: const Duration(milliseconds: 200),
      curve: Curves.easeOut,
    );
  }

  void _handleStateChange(AppLifecycleState state) {
    setState(() {
      _state = state;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizedBox(
        width: 300,
        child: SingleChildScrollView(
          controller: _scrollController,
          child: Column(
            children: <Widget>[
              Text('Current State: ${_state ?? 'Not initialized yet'}'),
              const SizedBox(height: 30),
              Text('State History:\n  ${_states.join('\n  ')}'),
            ],
          ),
        ),
      ),
    );
  }
}

参考文档

  1. AppLifecycleListener
  2. AppLifecycleState
  3. WidgetsBindingObserver