Flutter Widget生命周期—StatelessWidget vs StatefulWidget完全解析|新宇宙博客返回列表StatelessWidget vs StatefulWidget 的生命周期
全面讲解Flutter两种Widget的生命周期、State对象创建与销毁、build方法触发条件及常见生命周期陷阱

核心摘要:Flutter Widget 的生命周期是理解 Flutter 状态管理的基础。本文系统梳理 StatelessWidget 的单次构建语义,以及 StatefulWidget 的完整生命周期(createState → initState → didChangeDependencies → build → didUpdateWidget → deactivate → dispose),深入分析每个阶段的触发时机、应该做什么、不应该做什么,并与 React 生命周期、Android Activity/Fragment、iOS UIViewController 进行横向对比。
目录
- StatelessWidget 的生命周期
- StatefulWidget 完整生命周期
- State 的各阶段详解
- 生命周期的触发场景
- App 级别的生命周期
- 与 React / Android / iOS 的对比
- 实战案例:资源管理最佳实践
- 深度追问
- 总结表格
// StatelessWidget:没有状态,没有生命周期钩子
// 唯一的"生命周期"就是 build()
class UserCard extends StatelessWidget {
final User user;
const UserCard({super.key, required this.user});
@override
Widget build(BuildContext context) {
// 这是一个纯函数:相同 user → 相同输出
// 每次父 Widget 重建时,可能会调用此方法
return Card(
child: ListTile(
title: Text(user.name),
subtitle: Text(user.email),
),
);
}
}
// ⚡ StatelessWidget 的重建优化
class OptimizedCard extends StatelessWidget {
const OptimizedCard({super.key, required this.user});
final User user;
@override
Widget build(BuildContext context) {
return Card(
child: Column(children: [
// ✅ 常量子 Widget 不会随父重建而重建
const CardHeader(),
UserInfo(user: user),
// ✅ 静态内容使用 const
const SizedBox(height: 8),
]),
);
}
}
// StatelessWidget 何时"重建"?
// 1. 父 Widget 调用 setState()
// 2. 父 Widget 重建时,如果这个 Widget 在父的 build() 中被创建
// 注意:重建并不意味着对应的 Element 被销毁,Element 会被复用
// 如何避免不必要的重建?
// 1. 使用 const 构造函数(最有效)
// 2. 将变化的部分提升到单独的 StatefulWidget
// 3. 使用状态管理工具(Provider、Riverpod、Bloc)的 select
┌─────────────────────────────────────────────┐
│ StatefulWidget 生命周期 │
└─────────────────────────────────────────────┘
│
Widget 首次插入树 │
▼
┌─────────────────┐
│ createState() │ ← Widget 调用,创建 State 对象
└────────┬────────┘
│
▼
┌─────────────────┐
│ initState() │ ← State 初始化(一次性)
└────────┬────────┘
│
▼
┌─────────────────────────┐
│ didChangeDependencies() │ ← InheritedWidget 依赖建立
└────────────┬────────────┘
│
┌─────────────────▼─────────────────┐
│ build() │ ◀──────────────┐
└─────────────────┬─────────────────┘ │
│ │
┌───────────┴───────────┐ │
│ │ │
setState() 或 父 Widget 重建且 │
依赖变化 配置不同 │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ didUpdateWidget() │ │
│ └──────────┬──────────┘ │
│ │ │
└───────────┬───────────┘ │
│ │
└───────────────────────────────────┘
Widget 从树中移除 │
▼
┌─────────────────┐
│ deactivate() │ ← 暂时或永久离树
└────────┬────────┘
│
┌────────────┴────────────┐
│ │
重新插入 永久移除
│ │
▼ ▼
build() ┌─────────────────┐
(重新激活) │ dispose() │ ← 清理资源
└─────────────────┘
3. State 的各阶段详解
3.1 createState() — 创建状态
class MyWidget extends StatefulWidget {
final String title;
const MyWidget({super.key, required this.title});
@override
State<MyWidget> createState() => _MyWidgetState();
// 规则:
// 1. 只创建 State 对象,不做初始化工作
// 2. 每次 Widget 被插入树时调用(通常只有一次)
// 3. 若使用 GlobalKey,State 可以在树的不同位置被复用(不重新 createState)
}
3.2 initState() — 初始化
class _MyWidgetState extends State<MyWidget> {
late AnimationController _controller;
late StreamSubscription _subscription;
Timer? _timer;
@override
void initState() {
super.initState(); // ⚠️ 必须调用,而且第一个调用
// ✅ 可以做的事:
// 1. 初始化一次性资源
_controller = AnimationController(
vsync: this as TickerProvider, // 需要 mixin
duration: const Duration(seconds: 1),
);
// 2. 订阅 Stream
_subscription = myStream.listen(_onStreamData);
// 3. 读取 widget 属性(注意:不要读 context 上的 InheritedWidget)
print('初始化,title = ${widget.title}');
// 4. 注册回调
WidgetsBinding.instance.addPostFrameCallback((_) {
// ✅ 帧回调中可以安全使用 context
_doSomethingAfterFirstBuild(context);
});
// ❌ 不应该做的事:
// Theme.of(context) ← 此时 Element 还未建立依赖关系(应在 didChangeDependencies)
// Navigator.push() ← 此时树还未完全构建
}
void _onStreamData(dynamic data) {
if (mounted) { // ⚠️ 检查 mounted,防止 dispose 后调用 setState
setState(() { /* 更新状态 */ });
}
}
3.3 didChangeDependencies() — 依赖变化
@override
void didChangeDependencies() {
super.didChangeDependencies();
// 触发时机:
// 1. initState() 之后(必然调用一次)
// 2. 任何 context.dependOnInheritedWidgetOfExactType 的数据变化时
// ✅ 适合做的事:
// 1. 第一次获取 InheritedWidget 数据
final theme = Theme.of(context);
final locale = Localizations.localeOf(context);
// 2. 依赖 Locale 的数据初始化
// 注意:可能多次调用,需要避免重复操作
if (_initializedLocale != locale) {
_initializedLocale = locale;
_loadLocalizedData(locale);
}
}
Locale? _initializedLocale;
3.4 build() — 构建 UI
@override
Widget build(BuildContext context) {
// 规则:
// 1. 必须是纯函数(同样的状态 → 同样的 Widget)
// 2. 不要在 build 中执行副作用(网络请求、写文件)
// 3. 可以任意频繁调用,要足够高效
// ✅ 只描述 UI,从状态派生视图
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: _isLoading
? const CircularProgressIndicator()
: _buildContent(),
);
}
bool _isLoading = false;
Widget _buildContent() {
// 可以提取子 build 方法,但本质还是 build 的一部分
return Text('内容');
}
@override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// 触发时机:父 Widget 重建,且新旧 Widget 类型相同(Element 被复用)
// 注意:widget 已经更新为新的 Widget,oldWidget 是旧的
// ✅ 适合做的事:
// 1. 响应 Widget 属性变化
if (oldWidget.title != widget.title) {
_onTitleChanged(widget.title);
}
// 2. 重新初始化依赖 Widget 属性的资源
if (oldWidget.duration != widget.duration) {
_controller.duration = widget.duration;
}
// 典型例子:VideoPlayer 换了 URL,需要重新加载
// if (oldWidget.videoUrl != widget.videoUrl) {
// _loadVideo(widget.videoUrl);
// }
}
3.6 deactivate() — 暂时离树
@override
void deactivate() {
// 触发时机:Element 从树中移除,但可能还会被重新插入
// 例如:GlobalKey 导致的树重新父化(reparenting)
// 例如:TabBarView 切换 Tab 时(PageStorageKey)
// 通常很少需要覆盖此方法
// 如果覆盖,务必调用 super.deactivate()
super.deactivate();
}
3.7 dispose() — 释放资源
@override
void dispose() {
// 触发时机:Element 从树中永久移除
// 规则:必须是最后的清理动作
// ✅ 必须清理的资源:
_controller.dispose(); // AnimationController
_subscription.cancel(); // StreamSubscription
_timer?.cancel(); // Timer
_focusNode.dispose(); // FocusNode
_textController.dispose(); // TextEditingController
_scrollController.dispose(); // ScrollController
_pageController.dispose(); // PageController
// ⚠️ 取消 HTTP 请求(若使用支持取消的库)
_cancelToken.cancel();
// ❌ dispose 后不要调用 setState
// ❌ dispose 后不要访问 context
super.dispose(); // ⚠️ 必须调用,而且最后调用
}
}
4. 生命周期的触发场景
// 场景1:页面导航
void navigationScenario() {
// push: initState → didChangeDependencies → build
Navigator.push(context, MaterialPageRoute(builder: (c) => NewPage()));
// pop: deactivate → dispose
Navigator.pop(context);
}
// 场景2:条件渲染
class ConditionalParent extends StatefulWidget {
@override
State<ConditionalParent> createState() => _ConditionalParentState();
}
class _ConditionalParentState extends State<ConditionalParent> {
bool _showChild = true;
@override
Widget build(BuildContext context) {
return Column(children: [
if (_showChild) const ChildWidget(),
// true → false: ChildWidget 的 deactivate() → dispose()
// false → true: 新 ChildWidget 的 createState() → initState() → build()
]);
}
}
// 场景3:父 Widget 重建(配置更新)
class ParentWidget extends StatefulWidget {
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
String _title = '旧标题';
@override
Widget build(BuildContext context) {
return ChildWithConfig(title: _title); // 每次 setState 传入新 title
}
}
class ChildWithConfig extends StatefulWidget {
final String title;
const ChildWithConfig({super.key, required this.title});
@override
State<ChildWithConfig> createState() => _ChildWithConfigState();
}
class _ChildWithConfigState extends State<ChildWithConfig> {
@override
void didUpdateWidget(ChildWithConfig oldWidget) {
super.didUpdateWidget(oldWidget);
// ✅ 父改变了 title,这里捕获变化
if (oldWidget.title != widget.title) {
print('title 从 ${oldWidget.title} 变为 ${widget.title}');
}
}
@override
Widget build(BuildContext context) => Text(widget.title);
}
5. App 级别的生命周期
// AppLifecycleState:监听整个 App 的前后台状态
class AppLifecycleWidget extends StatefulWidget {
@override
State<AppLifecycleWidget> createState() => _AppLifecycleState();
}
class _AppLifecycleState extends State<AppLifecycleWidget>
with WidgetsBindingObserver {
AppLifecycleState _appState = AppLifecycleState.resumed;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // 注册观察者
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); // 注销观察者(必须!)
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() => _appState = state);
switch (state) {
case AppLifecycleState.resumed:
// App 回到前台,恢复操作(恢复音频、刷新数据)
_resumeWork();
case AppLifecycleState.inactive:
// App 即将失去焦点(弹出系统弹窗、切换 App 中)
// iOS:来电、控制中心
// 保存临时数据
_saveTemporaryData();
case AppLifecycleState.paused:
// App 在后台(Android),或不可见(iOS)
// 停止后台工作,保存状态
_pauseWork();
case AppLifecycleState.detached:
// App 即将销毁(Android Activity destroy)
// 最后的清理机会
_cleanup();
case AppLifecycleState.hidden:
// Dart 3.1+:App 被隐藏(多窗口)
break;
}
}
// 监听系统字体大小变化
@override
void didChangeTextScaleFactor() {
// 字体大小变化,可能需要重新布局
setState(() {});
}
// 监听系统亮度变化(深色/浅色模式)
@override
void didChangePlatformBrightness() {
setState(() {});
}
}
// Dart 3.2+:AppLifecycleListener(更现代的 API)
class ModernLifecycleWidget extends StatefulWidget {
@override
State<ModernLifecycleWidget> createState() => _ModernLifecycleState();
}
class _ModernLifecycleState extends State<ModernLifecycleWidget> {
late final AppLifecycleListener _lifecycleListener;
@override
void initState() {
super.initState();
_lifecycleListener = AppLifecycleListener(
onResume: () => print('应用恢复'),
onPause: () => print('应用暂停'),
onDetach: () => print('应用分离'),
onShow: () => print('应用显示'),
onHide: () => print('应用隐藏'),
);
}
@override
void dispose() {
_lifecycleListener.dispose(); // 必须释放!
super.dispose();
}
}
6. 与 React / Android / iOS 的对比
React 生命周期对比
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
this.timer = setInterval(() => this.setState(...), 1000);
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.url !== this.props.url) {
this.fetchData(this.props.url);
}
}
componentWillUnmount() {
clearInterval(this.timer);
}
}
function MyHookComponent({ url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetchData(url).then(setData);
return () => cleanup();
}, []);
useEffect(() => {
fetchData(url).then(setData);
}, [url]);
}
Android 生命周期对比
| Android Activity | Flutter State |
|---|
onCreate() | initState() |
onStart() | N/A |
onResume() | AppLifecycleState.resumed |
onPause() | AppLifecycleState.paused |
onStop() | N/A |
onDestroy() | dispose() |
onSaveInstanceState() | PageStorage / 状态管理 |
onRestoreInstanceState() | 状态持久化恢复 |
iOS UIViewController 对比
| iOS UIViewController | Flutter State |
|---|
viewDidLoad() | initState() |
viewWillAppear() | N/A |
viewDidAppear() | didChangeDependencies() + build() |
viewWillDisappear() | deactivate() |
viewDidDisappear() | deactivate() (持久) |
deinit | dispose() |
traitCollectionDidChange() | didChangeDependencies() |
7. 实战案例:资源管理最佳实践
// 综合示例:视频播放器 Widget 的完整生命周期管理
import 'package:video_player/video_player.dart';
class VideoPlayerWidget extends StatefulWidget {
final String url;
final bool autoPlay;
const VideoPlayerWidget({
super.key,
required this.url,
this.autoPlay = false,
});
@override
State<VideoPlayerWidget> createState() => _VideoPlayerState();
}
class _VideoPlayerState extends State<VideoPlayerWidget>
with WidgetsBindingObserver {
VideoPlayerController? _controller;
bool _isInitialized = false;
bool _isError = false;
String? _errorMessage;
// ─── 初始化 ───────────────────────────────────────
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // 监听 App 生命周期
_initVideo(widget.url);
}
Future<void> _initVideo(String url) async {
try {
final controller = VideoPlayerController.networkUrl(Uri.parse(url));
await controller.initialize();
if (!mounted) {
// ⚠️ 异步操作完成时检查 mounted,防止 Widget 已被销毁
controller.dispose();
return;
}
setState(() {
_controller = controller;
_isInitialized = true;
});
if (widget.autoPlay) {
controller.play();
}
} catch (e) {
if (!mounted) return;
setState(() {
_isError = true;
_errorMessage = '视频加载失败: $e';
});
}
}
// ─── 响应 Widget 配置变化 ──────────────────────────
@override
void didUpdateWidget(VideoPlayerWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.url != widget.url) {
// URL 改变,需要重新加载
_disposeController();
setState(() {
_isInitialized = false;
_isError = false;
});
_initVideo(widget.url);
}
}
// ─── App 前后台切换 ─────────────────────────────────
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (_controller == null) return;
switch (state) {
case AppLifecycleState.paused:
case AppLifecycleState.inactive:
// 进入后台:暂停视频
if (_controller!.value.isPlaying) {
_controller!.pause();
_wasPlayingBeforePause = true;
}
case AppLifecycleState.resumed:
// 回到前台:恢复播放
if (_wasPlayingBeforePause) {
_controller!.play();
_wasPlayingBeforePause = false;
}
default:
break;
}
}
bool _wasPlayingBeforePause = false;
// ─── 构建 UI ────────────────────────────────────────
@override
Widget build(BuildContext context) {
if (_isError) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error, color: Colors.red, size: 48),
Text(_errorMessage ?? '未知错误'),
TextButton(
onPressed: () => _initVideo(widget.url),
child: const Text('重试'),
),
],
),
);
}
if (!_isInitialized) {
return const Center(child: CircularProgressIndicator());
}
return AspectRatio(
aspectRatio: _controller!.value.aspectRatio,
child: VideoPlayer(_controller!),
);
}
// ─── 清理资源 ────────────────────────────────────────
void _disposeController() {
_controller?.dispose();
_controller = null;
_isInitialized = false;
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); // 注销观察者
_disposeController(); // 释放视频控制器
super.dispose();
}
}
// 生命周期日志工具(调试用)
mixin LifecycleLogMixin<T extends StatefulWidget> on State<T> {
@override
void initState() {
print('[$runtimeType] initState');
super.initState();
}
@override
void didChangeDependencies() {
print('[$runtimeType] didChangeDependencies');
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
print('[$runtimeType] build');
return super.build(context);
}
@override
void didUpdateWidget(covariant T oldWidget) {
print('[$runtimeType] didUpdateWidget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('[$runtimeType] deactivate');
super.deactivate();
}
@override
void dispose() {
print('[$runtimeType] dispose');
super.dispose();
}
}
8. 深度追问
Q1:initState 和 didChangeDependencies 都在首次构建时调用,有什么区别?
initState 在 Element 挂载时立即调用,此时 Element 还未建立与 InheritedWidget 的依赖关系,因此不能调用 Theme.of(context) 等依赖 InheritedWidget 的方法。didChangeDependencies 在 initState 之后调用,此时依赖关系已建立,可以安全访问 InheritedWidget。实践原则:纯初始化(不依赖 context 数据)放 initState;依赖 InheritedWidget 的初始化放 didChangeDependencies(注意处理多次调用)。
Q2:setState 调用后,build 是立即执行的吗?
不是。setState 只是将当前 Element 标记为 dirty,并调度一次帧更新(scheduleFrame())。真正的 build 在下一帧开始时执行。这意味着同一帧内多次调用 setState 只会触发一次 build,Dart 会批量处理所有标记为 dirty 的 Element。这也是为什么 setState 的回调是同步的——你只是更新状态变量,不是立即触发重建。
Q3:为什么需要检查 mounted,什么情况下 mounted 为 false?
mounted 反映当前 State 对应的 Element 是否还在树中。在异步操作(Future/async)完成后,Widget 可能已经被销毁(用户导航离开、条件渲染移除等)。若此时调用 setState(),会抛出 setState() called after dispose() 异常。检查 if (mounted) 是异步回调中的保护措施。Dart 3.2+ 的 lint 规则 use_build_context_synchronously 也会提醒你在 await 后使用 context 的风险。
Q4:dispose 中一定要调用 super.dispose() 吗?为什么?
是的,而且必须在最后调用。super.dispose() 会 1)将 _debugLifecycleState 设置为 defunct 状态 2)释放 State 与 Element 的关联 3)触发框架的清理逻辑。如果不调用,Element 可能不被正确标记为废弃,在调试模式下会触发断言错误,在生产模式可能造成内存泄漏。
9. 总结表格
| 生命周期方法 | 触发时机 | 可做的事 | 禁止的事 |
|---|
createState() | Widget 插入树时 | 创建 State 对象 | 初始化操作 |
initState() | State 创建后(一次) | 初始化资源、订阅 | Theme.of(context) |
didChangeDependencies() | initState 后 + 依赖变化 | 访问 InheritedWidget | — |
build() | 每次需要构建 | 构建 Widget 树 | 副作用、耗时操作 |
didUpdateWidget() | 父 Widget 配置变化 | 响应配置变化 | — |
deactivate() | Element 离树(可能恢复) | 暂停资源 | — |
dispose() | Element 永久离树 | 释放所有资源 | 调用 setState |
| 对比 | Flutter State | React Hooks | Android Activity |
|---|
| 初始化 | initState | useEffect([], []) | onCreate |
| 配置更新 | didUpdateWidget | useEffect([dep]) | onNewIntent |
| 清理 | dispose | useEffect return | onDestroy |
| 后台 | WidgetsBindingObserver | visibilitychange event | onPause/onResume |