Skip to content

Flutter 中的生命周期:总览、实战与源码导读

· 8 min

这是一篇结合“教程型 + 概念图解 + 源码导读”的文章。你将先获得一张可记忆的总览图,然后用最小示例串联各回调的真实触发时机,最后从源码视角验证直觉并掌握边界与最佳实践。

读者与目标#


生命周期一图总览#

把三棵树记住:Widget(描述)→ Element(实例,连接树形)→ RenderObject(布局/绘制)。生命周期主要发生在 State(对应 StatefulWidget 的可变部分)与 Element 上。

时序主线(StatefulWidget):

reateState
→ initState
→ didChangeDependencies // 依赖(如 InheritedWidget)建立后触发
→ build // 可多次
→ didUpdateWidget? // 父 Widget 配置更新时
→ reassemble? // 热重载
→ deactivate // 临时移出树(如路由切换)
→ dispose // 永久移出 / 销毁

辅助线:

[!tip] 口诀


该放哪里做什么(对照表)#

时机回调典型用途注意事项
创建initState一次性初始化:控制器、订阅、启动轻量异步不要在此直接 async/await 阻塞构建;需要 context 的依赖请移到 didChangeDependenciespostFrameCallback
依赖建立didChangeDependencies读取依赖(如 InheritedWidget / Provider),或 RouteObserver 订阅可能被多次调用(依赖变化);避免重复订阅(用标志位)
构建build纯函数式描述 UI避免重型同步任务与副作用
父配置更新didUpdateWidget对比 oldWidget 调整内部状态使用 widget.xxxoldWidget.xxx 比较
热重载reassemble开发期修正调试状态仅开发期有效
临时移出树deactivate临时分离时清理与迁移可能会重新插回(紧跟着又 build)
销毁dispose成对释放(Controller、Listener、Timer、Stream 等)一切异步回调需判断 mounted,避免“僵尸 setState”

最小可运行示例#

下面示例把各回调的触发时机打印到控制台,同时演示 Route 与 App 生命周期。

import 'package:flutter/material.dart';
final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
void main() {
runApp(MaterialApp(
home: const PageA(),
navigatorObservers: [routeObserver],
));
}
class PageA extends StatelessWidget {
const PageA({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page A')),
body: Center(
child: ElevatedButton(
child: const Text('Go to Page B'),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const PageB()),
),
),
),
);
}
}
class PageB extends StatefulWidget {
const PageB({super.key});
@override
State<PageB> createState() => _PageBState();
}
class _PageBState extends State<PageB>
with WidgetsBindingObserver, RouteAware {
bool _subscribed = false;
@override
void initState() {
super.initState();
debugPrint('initState');
WidgetsBinding.instance.addObserver(this);
// 轻量异步或预取,不阻塞首帧
Future.microtask(() => debugPrint('microtask after initState'));
// 若需要获取尺寸/布局信息,用 frame 末尾回调
WidgetsBinding.instance.addPostFrameCallback((_) {
debugPrint('postFrameCallback: can read size/layout');
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint('didChangeDependencies');
// 在这里订阅路由可见性(需要 context 才能拿到当前 Route)
if (!_subscribed) {
final route = ModalRoute.of(context);
if (route is PageRoute) {
routeObserver.subscribe(this, route);
_subscribed = true;
}
}
}
@override
void didUpdateWidget(covariant PageB oldWidget) {
super.didUpdateWidget(oldWidget);
debugPrint('didUpdateWidget: ${oldWidget != widget}');
}
@override
Widget build(BuildContext context) {
debugPrint('build');
return Scaffold(
appBar: AppBar(title: const Text('Page B')),
body: Center(
child: ElevatedButton(
child: const Text('setState'),
onPressed: () => setState(() {
debugPrint('setState -> schedule rebuild');
}),
),
),
);
}
// RouteAware
@override
void didPush() => debugPrint('RouteAware.didPush');
@override
void didPop() => debugPrint('RouteAware.didPop');
@override
void didPushNext() => debugPrint('RouteAware.didPushNext');
@override
void didPopNext() => debugPrint('RouteAware.didPopNext');
// App 生命周期
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
debugPrint('AppLifecycleState: $state'); // resumed/paused/inactive/detached
}
@override
void deactivate() {
super.deactivate();
debugPrint('deactivate');
}
@override
void dispose() {
debugPrint('dispose');
if (_subscribed) routeObserver.unsubscribe(this);
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}

运行步骤建议:

  1. 启动进入 PageA,点击进入 PageB:观察 didPush → build
  2. 点击按钮触发 setState:观察重建不走 initState
  3. 返回 PageA:观察 didPop → deactivate → dispose
  4. 切后台/回前台:观察 AppLifecycleState 变化。

[!warning] 常见报错

if (!mounted) return;
setState(() { /* update */ });

三个高频实战场景#


源码导读#

[!tip] 观察点


易错点与最佳实践#


记忆小抄#


参考实践片段#

final route = ModalRoute.of(context);
if (route is PageRoute) {
routeObserver.subscribe(this, route);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
final size = context.size; // or use LayoutBuilder/Measure
});
@override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.config != widget.config) {
// update internal state
}
}
- 以上内容已按你的博客 `postSchema`(如 `title/pubDate/toc/ogImage` 等)生成,可直接保存为新文章文件。
- 若需要插入配图或流程图,我可以补充 PNG/ASCII 图或提供 Mermaid 版本。