本文涵盖了 Flutter 中 StatefulWidget 的生命周期、App 生命周期以及特殊的 addPostFrameCallback 回调,帮你彻底搞懂页面从创建到销毁的整个过程。
一、概述
Flutter 中的生命周期主要分为三类:
- Widget 自身生命周期 - StatefulWidget 的状态变化
- App 生命周期 - 整个应用的前后台切换
- 帧回调生命周期 - 渲染完成后的特殊回调
二、StatefulWidget 生命周期详解
2.1 生命周期流程图
创建页面
↓
createState()
↓
initState() ←─────┐
↓ │
didChangeDependencies()
↓ │
build() ←────┐ │
↓ │ │
didUpdateWidget() │
↓ │ │
setState() ───┘ │
↓ │
deactivate() │
↓ │
dispose() ←────────┘
2.2 各阶段详细说明
创建阶段
dart
class MyPage extends StatefulWidget {
@override
_MyPageState createState() => _MyPageState(); // 1. 创建State
}
class _MyPageState extends State<MyPage> {
@override
void initState() { // 2. 初始化,只调用一次
super.initState();
print('页面初始化');
// 初始化数据、添加监听器等
}
@override
void didChangeDependencies() { // 3. 依赖变化时调用
super.didChangeDependencies();
print('依赖变化');
// 依赖的InheritedWidget变化时触发
}
@override
Widget build(BuildContext context) { // 4. 构建UI
print('构建页面');
return Container();
}
}
运行阶段
dart
@override
void didUpdateWidget(MyPage oldWidget) { // 父组件重建时调用
super.didUpdateWidget(oldWidget);
print('组件更新');
}
void _refreshData() {
setState(() { // 手动触发UI更新
print('数据更新');
});
}
销毁阶段
dart
@override
void deactivate() { // 从组件树移除时调用
super.deactivate();
print('页面即将移除');
}
@override
void dispose() { // 永久销毁,只调用一次
print('页面销毁');
// 释放资源、移除监听
super.dispose();
}
三、App 生命周期
3.1 使用方式
需要混入 WidgetsBindingObserver:
dart
class _MyPageState extends State<MyPage> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // 添加监听
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.resumed:
print('应用可见并获取焦点');
break;
case AppLifecycleState.inactive:
print('应用处于非激活状态(如来电)');
break;
case AppLifecycleState.paused:
print('应用进入后台');
break;
case AppLifecycleState.detached:
print('应用已分离');
break;
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); // 移除监听
super.dispose();
}
}
3.2 状态切换场景
操作 状态变化
打开App detached → resumed
切换到后台 resumed → paused
从后台切回 paused → resumed
来电话 resumed → inactive → paused
挂断电话 paused → inactive → resumed
四、特殊回调:addPostFrameCallback
4.1 是什么?
在当前渲染帧完成后执行的回调,确保在页面完全渲染后执行某些操作。
4.2 执行时机
dart
@override
void initState() {
super.initState();
print('1. initState');
WidgetsBinding.instance.addPostFrameCallback((_) {
print('3. 第一帧渲染完成');
});
print('2. initState结束');
}
@override
Widget build(BuildContext context) {
print('build执行');
return Container();
}
// 输出顺序:
// initState
// initState结束
// build执行
// 第一帧渲染完成
4.3 常用场景
场景一:页面加载完成后的初始化
dart
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// 页面完全渲染后再加载数据
_loadInitialData();
_showGuideDialog();
});
}
场景二:获取组件尺寸
dart
GlobalKey _key = GlobalKey();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
RenderBox box = _key.currentContext?.findRenderObject() as RenderBox;
Size size = box.size; // 此时才能获取到正确尺寸
print('组件尺寸: $size');
});
}
场景三:避免 build 中的无限循环
dart
// ❌ 错误示范
@override
Widget build(BuildContext context) {
setState(() {}); // 会导致无限循环
return Container();
}
// ✅ 正确示范
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {}); // 只在第一帧后执行一次
});
}
场景四:页面曝光埋点
dart
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_reportPageView(); // 页面完全显示后才上报曝光
});
}
4.4 注意事项
- 不需要手动移除:回调执行后自动清理
- 可添加多个:按添加顺序执行
- 只在当前帧执行:如需每帧执行,使用 addPersistentFrameCallback
五、完整场景示例
5.1 综合应用
dart
class LifecycleDemoPage extends StatefulWidget {
@override
_LifecycleDemoPageState createState() => _LifecycleDemoPageState();
}
class _LifecycleDemoPageState extends State<LifecycleDemoPage>
with WidgetsBindingObserver {
bool _isLoading = true;
List<String> _data = [];
final GlobalKey _listKey = GlobalKey();
@override
void initState() {
super.initState();
print('📝 initState: 页面初始化');
// 添加App生命周期监听
WidgetsBinding.instance.addObserver(this);
// 页面渲染完成后执行
WidgetsBinding.instance.addPostFrameCallback((_) {
print('🎨 第一帧渲染完成');
_onFirstFrameRendered();
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('📝 didChangeDependencies: 依赖变化');
}
@override
void didUpdateWidget(LifecycleDemoPage oldWidget) {
super.didUpdateWidget(oldWidget);
print('📝 didUpdateWidget: 组件更新');
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('📱 App状态: $state');
if (state == AppLifecycleState.resumed) {
// 从后台切回时刷新数据
_refreshData();
}
}
void _onFirstFrameRendered() {
// 获取列表位置信息
final box = _listKey.currentContext?.findRenderObject() as RenderBox;
print('📏 列表位置: ${box.localToGlobal(Offset.zero)}');
// 加载数据
_loadData();
}
Future<void> _loadData() async {
setState(() => _isLoading = true);
// 模拟网络请求
await Future.delayed(Duration(seconds: 1));
if (mounted) {
setState(() {
_data = List.generate(20, (i) => 'Item $i');
_isLoading = false;
});
// 数据加载完成后的曝光上报
WidgetsBinding.instance.addPostFrameCallback((_) {
print('📊 数据加载完成,可以上报曝光');
});
}
}
void _refreshData() {
WidgetsBinding.instance.addPostFrameCallback((_) {
print('🔄 刷新数据');
_loadData();
});
}
@override
void deactivate() {
super.deactivate();
print('📝 deactivate: 页面即将移除');
}
@override
void dispose() {
print('📝 dispose: 页面销毁');
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
print('🎨 build: 构建页面');
return Scaffold(
appBar: AppBar(title: Text('生命周期示例')),
body: _isLoading
? Center(child: CircularProgressIndicator())
: ListView.builder(
key: _listKey,
itemCount: _data.length,
itemBuilder: (ctx, i) => ListTile(title: Text(_data[i])),
),
);
}
}
5.2 常见场景速查表
需求 使用的方法
页面初始化数据 initState
页面渲染后操作 addPostFrameCallback
获取组件尺寸 addPostFrameCallback
从后台切回刷新 didChangeAppLifecycleState + resumed
避免重复build addPostFrameCallback + setState
页面曝光埋点 addPostFrameCallback
释放资源 dispose
移除监听 dispose
六、总结
6.1 执行顺序总结
第一次进入页面:
initState → didChangeDependencies → build → addPostFrameCallback → resumed
页面切换(A→B):
A: deactivate
B: initState → build → resumed
从B返回A:
A: build → resumed
App退到后台:
resumed → paused
App从后台返回:
paused → resumed
6.2 最佳实践建议
- initState:只做轻量初始化,不要调用 setState
- build:只做UI构建,不要做耗时操作
- addPostFrameCallback:适合做渲染后的操作、数据加载、埋点
- dispose:必须释放所有资源,移除监听
- didChangeAppLifecycleState:处理前后台切换逻辑
💡 记住要点:
· initState:出生
· build:成长
· addPostFrameCallback:成年(可以做事了)
· dispose:消亡
· resumed:睡醒(回到前台)
希望这篇总结对你有帮助!如有遗漏或错误,欢迎指正交流。