Flutter 应用生命周期:使用 AppLifecycleListener 阻止应用崩溃

当我刚开始进行 Flutter 开发时,应用生命周期(App Life Cycle)感觉就像一个神秘的黑盒子。为什么我点击 Home 键时我的计时器会停止?为什么我恢复应用时相机有时会崩溃?朋友们,答案就在于应用生命周期

欢迎关注我的微信公众号:OpenFlutter,感谢。

作为一名 Flutter 开发者,掌握应用生命周期不仅仅是一个好主意------它是构建专业应用的必不可少的条件。它能让你防止应用崩溃、节省用户的电量,并保护他们宝贵的数据。

本指南将详细解析这些关键状态,并使用 AppLifecycleListener 给你一个完整、现代的示例。


为什么生命周期很重要:"Paused" 状态陷阱

忘记"hello world"吧------你需要管理的最重要的单个状态是 paused(暂停)。

1. show (启动/变得可见)

  • 类比: 你正走进一个房间。🚶
  • 简单解释: 当你的应用首次启动,或者从后台返回并实际在屏幕上绘制其像素时发生。这是用户可以再次看到你的 UI 的那一刻。

2. resume (活动且就绪)

  • 类比: 你完全清醒并正在说话。🗣️
  • 简单解释: 应用完全处于前台 ,可见,并主动接收触摸输入和运行代码。这是正常的、完全运行的状态。你希望你的繁重工作(如动画或网络轮询)在这里运行。

3. hide (被遮盖)

  • 类比: 有人把一块大宣传板放在你面前。🖼️
  • 简单解释: 应用正在运行,但它被其他东西完全遮挡 了,比如系统警报、隐私屏幕或另一个应用叠加层。它还没有进入后台,但用户看不到它。你应该停止绘制,但保持核心功能就绪。

4. inactive (短暂暂停)

  • 类比: 电话铃响了,你冻结在句子中间。📞
  • 简单解释: 这是一个临时的、短暂的暂停 。应用是可见的,但它失去了焦点,无法接收触摸。它通常发生在应用即将进入后台之前(例如,当你点击 Home 按钮时),或者当一个系统级事件(比如来电或系统权限对话框)弹出时。

5. pause (在后台)

  • 类比: 你离开了房间并关上了门。🚪
  • 简单解释: 应用完全处于后台 且不再可见。它仍存在于手机内存中,但处于休眠状态。这是你的警告! 当你看到这个状态时,你必须保存用户数据停止任何耗费资源的活动(如相机或 GPS),因为手机可能随时会终止你的应用以释放资源。

6. detach (正在关闭)

  • 类比: 房子正在被推土机夷平。💥
  • 简单解释: 这是最终状态 。Flutter 框架正在被销毁并从操作系统的视图中移除。应用即将被终止并从内存中清除。这是你进行任何少量日志记录或清理的最后机会。

7. restart (进程重新启动)

  • 类比: 你瞬间被传送回起点。🔄
  • 简单解释: 这个状态通常意味着整个应用程序进程被操作系统终止 (要么是用户手动操作,要么是由于内存不足自动终止),而现在它正在从头开始重新启动 。一切都从头开始(就像 showresume)。如果应用之前处于 pause 状态被终止,但用户随后试图将其重新唤醒,你就会看到这个状态。

典型的流程(进入主屏幕并返回)

  1. resume(活动)
  2. inactive(短暂中断)
  3. pause(在后台 --- 关键的清理时间!
  4. (... 应用保持在这里,直到操作系统需要内存 ...)
  5. show(返回前台)
  6. resume(再次活动)

陷阱(The Trap)

如果当应用进入 paused 状态时,你仍然让相机或大规模、活跃的网络同步运行,操作系统 (OS) 会将你的应用标记为资源占用者 。然后,操作系统可以在没有警告的情况下终止你的应用 以释放资源给其他应用。你将丢失用户数据,用户也会遇到崩溃。管理生命周期就是你防止这种情况发生的方式。


分步实现:AppLifecycleListener

第 1 步:设置根 Widget

此步骤定义了应用程序的入口点,并建立了基本的 Widget 树。

dart 复制代码
void main() {
  runApp(const MyApp());
}

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

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

第 2 步:定义 AppLifecycleDisplay Widget

此步骤建立一个 StatefulWidget 来管理和跟踪生命周期事件,因为日志记录和 UI 更新都需要改变状态。

dart 复制代码
class AppLifecycleDisplay extends StatefulWidget {
  const AppLifecycleDisplay({super.key});

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

第 3 步:在 initState 中设置监听器

initState 方法是实例化 AppLifecycleListener 并配置它来监听所有可能事件的地方。

dart 复制代码
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);
    }
  }

  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;
    });
  }

第 4 步:在 dispose 中清理资源

监听器会在 dispose 方法中被停止和释放,以防止内存泄漏。对于 Flutter widget 中任何可释放的资源来说,这是一个关键的最佳实践。

dart 复制代码
@override
  void dispose() {
    _listener.dispose();
    super.dispose();
  }

第 5 步:构建用户界面

此步骤定义了视觉布局,用于显示当前状态和历史日志。

dart 复制代码
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('App LifeCycle State'),
          elevation: 0,
        ),
        body: 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  ')}'),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

完整代码

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({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);
    }
  }

  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
  void dispose() {
    _listener.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('App LifeCycle State'),
          elevation: 0,
        ),
        body: 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. 运行你的应用。状态应该是 resumed(恢复)。
  2. 按下 Home 键:观察日志快速转换 ,经历 inactive (非活动)、paused (暂停),并可能经历 hidden(隐藏)。
  3. 返回应用:观察它转换回来 ,经历 inactive (非活动)回到 resumed(恢复)。

理解这些转换,并将你的清理代码放置在 paused 代码块内,是一个优秀的 Flutter 开发者的标志。正是这一点,将一个业余项目与一个能经受住真实用户手机严格考验的应用区分开来。

生命周期管理愉快!如果你有任何问题,请在下方留言。我很乐意随时提供帮助!

相关推荐
excel2 小时前
一文读懂 Vue 组件间通信机制(含 Vue2 / Vue3 区别)
前端·javascript·vue.js
我的xiaodoujiao3 小时前
从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 9--基础知识 5--常用函数 3
前端·python·测试工具·ui
李鸿耀5 小时前
Flex 布局下文字省略不生效?原因其实很简单
前端
皮蛋瘦肉粥_1216 小时前
pink老师html5+css3day06
前端·css3·html5
华仔啊10 小时前
前端必看!12个JS神级简写技巧,代码效率直接飙升80%,告别加班!
前端·javascript
excel10 小时前
dep.ts 逐行解读
前端·javascript·vue.js
爱上妖精的尾巴10 小时前
5-20 WPS JS宏 every与some数组的[与或]迭代(数组的逻辑判断)
开发语言·前端·javascript·wps·js宏·jsa
excel11 小时前
Vue3 响应式核心源码全解析:Dep、Link 与 track/trigger 完整执行机制详解
前端
前端大卫11 小时前
一个关于时区的线上问题
前端·javascript·vue.js