Appearance
Flutter 生命周期总览
Flutter 生命周期的核心不是“一个页面从创建到销毁”这么简单,而是 宿主 App 前后台状态 + Route 栈变化 + Widget / Element / RenderObject 三棵树的重建与释放 共同决定页面行为。
一句话定义
Flutter 生命周期关注的是:配置对象何时重建、挂载实例何时复用、渲染对象何时参与布局绘制、页面状态何时保留,以及宿主 App 切前后台时 Flutter 层会收到什么信号。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
Widget 生命周期、AppLifecycleState、路由 push/pop 观察 | flutter-lifecycle-observer | lifecycle_observer_page.dart |
本篇覆盖声明
本文聚焦 Flutter 生命周期主线,重点回答 5 个高频问题:
Widget/Element/RenderObject分别是什么,为什么 Android 背景工程师容易把它们混成“一个 View”- 一句话答:Widget 是不可变配置,Element 是挂载与复用节点,RenderObject 才是渲染对象;Android 倾向将这三者统合在单个 View 实例中。(详见下文 §1及复习题 1)
initState/didChangeDependencies/build/deactivate/dispose的执行顺序、适合放什么、不适合放什么- 一句话答:按一次性初始化(initState) -> 依赖计算(didChangeDependencies) -> UI描述(build) -> 暂离树(deactivate) -> 销毁(dispose) 顺序执行;一次性副作用切忌塞入 build。(详见下文 §2及复习题 2)
AppLifecycleState与页面 push/pop 不是一回事,分别该解决什么问题- 一句话答:AppLifecycleState 解决应用级切前后台的资源暂停与恢复,push/pop 解决路由栈导航与页面曝光刷新。(详见下文 §3及复习题 3)
- 页面重建时哪些状态会保留,哪些状态会丢,怎么做状态保存与恢复
- 一句话答:同位置同 Key 的 Element/State 在 rebuild 时保留,节点移除或 Key 变化则丢掉;跨配置变更与回收需借助 PageStorage 或 RestorationMixin。(详见下文 §4及复习题 4)
- Flutter 生命周期与 Android / iOS 宿主生命周期怎么映射,什么地方只能类比、不能硬等价
- 一句话答:宿主 Activity/ViewController 管原生存活与 AppLifecycleState,Flutter Route/State 管内部页面与组件,宿主后台不等于内部 Route 立即 dispose。(详见下文 §5及复习题 5)
Android / Flutter / Dart 是主线;Web / Backend 仅保留帮助理解边界的必要对照。
推荐学习顺序
- 先看
Widget/Element/RenderObject三层模型,建立“Flutter 不等于单层 View 树”的认知 - 再看
StatefulWidget生命周期顺序,理解initState/build/dispose的职责边界 - 再看
AppLifecycleState,区分“页面切换”和“App 退后台” - 再看页面重建与状态保存,回答“为什么页面回来没销毁但又重建了”
- 最后看 Flutter ↔ 原生宿主生命周期映射表,建立跨端对照
为什么需要
Flutter 页面“看起来像一个页面”,但底层并不是 Android Activity 那种单层对象模型。真实工程里高频踩坑基本都来自这个误判:
- 为什么
build会被频繁重跑,而不是只执行一次?- 一句话答:
build只是生成不可变 Widget 配置快照,父节点 setState、InheritedWidget 依赖更新或路由恢复均会触发 UI 重述。(详见下文 §1、§2)
- 一句话答:
- 为什么页面从栈顶离开不一定马上触发
dispose?- 一句话答:若页面被压入栈下层或使用缓存(如 KeepAlive),其 State 节点依然存活在 Element 树中。(详见下文 §2、§3)
- 为什么 Flutter 页面生命周期不能直接映射成 Android
Activity生命周期?- 一句话答:Activity 是系统级组件与进程载体,而 Flutter 页面只是单一宿主 Activity 内部组件树上的 Route 快照。(详见下文 §5及复习题 5)
- 为什么 App 退后台时只等
dispose无法暂停资源?- 一句话答:切后台时栈中 Route 依然存活不会触发
dispose,必须监听AppLifecycleState暂停前台任务。(详见下文 §3及复习题 3)
- 一句话答:切后台时栈中 Route 依然存活不会触发
- 为什么状态放在
State字段里在热重建或宿主回收后会丢失?- 一句话答:
State只是内存变量,当 Element 被销毁重建或进程被杀死恢复时内存重建,需配合状态恢复机制。(详见下文 §4及复习题 4)
- 一句话答:
对 Android 背景工程师来说,最大的迁移点是:
- Android 常把
Activity/Fragment当“页面实例”主载体 - Flutter 则把“配置描述”“挂载实例”“渲染对象”拆成三层
- 所以你必须先判断自己面对的是 重建配置、复用挂载节点,还是 真正释放页面状态
底层机制
1. Widget / Element / RenderObject:Flutter 为什么不是单层 View 树
Widget、Element、RenderObject 是 Flutter UI 体系里三个职责完全不同的对象:
| 层 | 一句话职责 | 更像 Android 里的什么 | 关键特点 |
|---|---|---|---|
Widget | 不可变配置,描述“我想长什么样” | View 的 XML / Compose 参数快照 | 轻量、可频繁重建 |
Element | Widget 在树中的挂载实例,负责把新旧 Widget 对应起来 | 介于 View 实例与组合树节点之间 | 持有 BuildContext,决定是否复用 State |
RenderObject | 真正负责 layout / paint / hit test | Android View 的测量绘制能力 | 只在需要布局绘制的节点上存在 |
关键理解:
- Widget 可以频繁 new:Flutter 鼓励你把
build当纯函数式描述,不要害怕 Widget 重建 - Element 才决定复用关系:同一个位置、同一个 runtimeType、同一个 key,Flutter 会尝试复用旧 Element / State
- RenderObject 关注布局和绘制:不是每个 Widget 都直接对应一个新的 RenderObject
可以把一次更新粗略理解成:
text
父级 setState
→ 重新执行 build
→ 产生新的 Widget 配置树
→ Element 对比新旧 Widget
→ 能复用则更新引用,不能复用则卸载旧节点、挂载新节点
→ 需要时更新或新建 RenderObject
→ layout / paint这就是为什么:
build很常见,但dispose不常见- 页面返回时你看到的可能只是重新
build - 改
Key可能让本来可复用的 State 直接丢失
观察点:做列表重排、条件渲染、切换
Key时,看日志里是单纯build,还是出现deactivate/dispose。对应 Lab:flutter-lifecycle-observer · lifecycle_observer_page.dart
2. StatefulWidget 常见生命周期顺序与职责边界
最常见的首次进入顺序:
text
createState
→ initState
→ didChangeDependencies
→ build交互或依赖变化后,常见会继续出现:
text
setState / inherited dependency changed / parent rebuild
→ build离开树时常见顺序:
text
deactivate
→ (若重新插回树,可能再次 build)
→ (若最终不再复用) dispose一个更贴近真实工程的顺序可以记成:
text
首次挂载:initState → didChangeDependencies → build
页面交互:setState → build
父级重建:build
依赖变化:didChangeDependencies → build
路由切换/树调整:deactivate → (可能重新 build,也可能 dispose)
最终销毁:disposeinitState
适合放:
- 一次性 controller 创建:
AnimationController、ScrollController、TextEditingController - 首次 listener 绑定
- 首次发起“只想做一次”的初始化逻辑
不适合放:
- 依赖
context.dependOnInheritedWidgetOfExactType的逻辑 - 需要每次回到前台都刷新的逻辑
- 直接依赖路由返回结果的逻辑
didChangeDependencies
适合放:
- 首次依赖
InheritedWidget/Provider/Localizations/MediaQuery的初始化 - 依赖对象变化后需要重算的逻辑
build
适合放:
- 纯 UI 描述
- 基于当前 state 组装 widget tree
- 轻量、可重复执行的派生计算
不适合放:
- 网络请求
- 打点去重逻辑
- controller 创建
- 任何“只能执行一次”的副作用
deactivate
deactivate 表示当前 Element 正在离开树,但 不代表一定销毁。常见于:
- 路由切换时节点暂时移出
- 带
GlobalKey的子树移动位置 - 树重排时旧节点先卸下再尝试复用
所以:
- 不要把核心释放逻辑写在
deactivate - 看到
deactivate不要立刻下结论“页面死了”
dispose
真正适合放资源释放:
controller.dispose()streamSubscription.cancel()focusNode.dispose()WidgetsBinding.instance.removeObserver(this)
要点:
dispose后State.mounted == false- 异步回调回来前要先判断
mounted dispose通常意味着这个State不再复用
3. 为什么 build 会频繁执行,但 initState / dispose 不会一一对应
Android 背景工程师最容易误会的是:把 build 想成“View 创建”。实际上 build 更像“重新描述当前 UI 应该长什么样”。
高频触发 build 的原因包括:
- 当前页面
setState - 父组件重建
InheritedWidget依赖变化- 屏幕尺寸、主题、文本缩放、语言等环境变化
- 路由返回后上层页面重新变为活跃并触发重绘
因此:
build多次执行是正常行为,不是性能 bug 本身- 真正的问题是你是否把“昂贵副作用”塞进了
build - 性能调优时应先分清 rebuild 次数多 与 rebuild 代价高 是两件事
4. AppLifecycleState:处理的是宿主 App 前后台,不是页面 push/pop
Flutter 提供:
dart
WidgetsBindingObserver.didChangeAppLifecycleState(AppLifecycleState state)常见状态可按“观察意义”理解:
| 状态 | 典型含义 | 你应关注什么 |
|---|---|---|
resumed | App 回到前台、可交互 | 恢复轮询、动画、前台埋点、前台资源 |
inactive | 前台但临时失焦 | 中间态,系统弹窗/切换过程里常见 |
hidden | 对用户已不可见(部分平台更明显) | 可视性变化,通常作为过渡态参考 |
paused | App 进入后台,不再处理前台交互 | 暂停轮询、动画、摄像头、定位等高成本前台工作 |
detached | Engine 与宿主视图分离 | 进程/引擎层收尾边界,业务里较少主动依赖 |
不同平台和系统版本可观察到的状态顺序不完全一致;工程上更重要的是理解这些状态表达的“资源与可见性语义”。
一个常见切后台序列可近似理解为:
text
resumed
→ inactive
→ hidden(部分平台)
→ paused最小可用示例:注册 Observer 并暂停/恢复前台资源
dart
class AppLifecycleWatcher extends StatefulWidget {
const AppLifecycleWatcher({super.key, required this.child});
final Widget child;
@override
State<AppLifecycleWatcher> createState() => _AppLifecycleWatcherState();
}
class _AppLifecycleWatcherState extends State<AppLifecycleWatcher>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // 注册:开始接收前后台事件
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); // 必须成对移除,否则泄漏
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
_resumePolling(); // 回前台:恢复轮询 / 动画 / 埋点
case AppLifecycleState.paused:
_pausePolling(); // 退后台:暂停高成本前台工作
default:
break;
}
}
}- 可能执行顺序:App 切后台 → 系统回调
paused→ 暂停轮询;回到前台 → 回调resumed→ 恢复。 - 观察重点:
addObserver与removeObserver必须成对;同时这段逻辑与页面dispose无关——退后台时 route 还在、页面没销毁,所以暂停/恢复只能靠AppLifecycleState驱动。
回前台则可能反向出现:
text
paused
→ hidden / inactive
→ resumed什么时候该用 AppLifecycleState
- App 级轮询、心跳、WebSocket 前台降频
- 页面还在栈里,但用户已经把 App 切后台
- 摄像头、定位、音视频、动画需要跟随前后台状态切换
- 前后台埋点与曝光统计
什么时候不该只靠 AppLifecycleState
- 单纯页面 A → 页面 B 的 push/pop
- 只关心当前 route 是否在栈顶
- 只关心某个 tab 是否可见
这类情况更应结合:
Navigator/ route observer- 页面自身 state
- 上层状态管理作用域
5. 页面重建与状态保存:为什么“没销毁”也可能看起来像重新进了一次页面
Flutter 页面重建不等于页面销毁。常见要分 4 类状态:
| 状态类型 | 典型内容 | 页面 rebuild 后是否保留 | route pop 后是否保留 | App 被系统杀掉后是否保留 |
|---|---|---|---|---|
| 局部临时 UI 状态 | bool isExpanded、输入框焦点 | 若同一 State 复用则保留 | 页面被销毁则不保留 | 不保留 |
| 控制器/订阅 | TextEditingController、AnimationController | 同一 State 复用时保留 | dispose 后不保留 | 不保留 |
| 路由栈内页面状态 | 列表滚动位置、tab 内部页 | 取决于是否仍在栈中 / 是否 keep alive | 被 pop 后通常不保留 | 不保留 |
| 可恢复业务状态 | 草稿、过滤条件、表单进度 | 可通过状态层或恢复机制恢复 | 可恢复 | 需要显式持久化/恢复 |
影响状态是否保留的关键因素
- Element / State 是否被复用
- Widget 的位置、类型、Key 是否保持稳定
- 页面是否只是被其他 route 覆盖,还是已经被 pop / replace
- 状态是存在当前页面
State、更上层状态容器,还是持久化层
常见保存策略
| 目标 | 常见做法 | 适用场景 |
|---|---|---|
| 页面被盖住再回来仍保留内存状态 | 保持 route 不被 pop;必要时配合 AutomaticKeepAliveClientMixin | tab 页、分页容器 |
| rebuild 不丢局部 state | 保持稳定树位置与 Key;避免无意更换 runtimeType | 条件渲染、列表重排 |
| 页面 pop 再进仍想恢复业务状态 | 提升到上层状态管理(如 Riverpod / Provider / BLoC) | 列表筛选、草稿 |
| 宿主回收或冷启动后恢复 | 持久化 + 恢复机制(如本地存储、Restoration) | 草稿、未提交表单、用户偏好 |
Android 背景常见误判
- Android 里常用
ViewModel/SavedStateHandle兜配置变更与进程恢复 - Flutter 里如果只把状态塞在当前
State里,它只能覆盖“当前 Element 还活着”的情况 - 一旦 route 被 pop、key 变化、宿主进程被杀,就必须靠更上层状态或持久化恢复
观察点:
- 触发
setState后,看输入、计数、滚动位置是否保留- push 到详情页再 pop,看列表页是否只是
build还是整页重新创建- 人为改
Key,观察原有State是否被强制丢弃
6. Flutter 与原生宿主生命周期映射:可以类比,但不能硬等价
Flutter 页面通常运行在一个宿主 Activity / Fragment / FlutterViewController 内,因此存在两条并行生命周期线:
- 宿主生命周期:Android
Activity/ iOSUIApplication/UIViewController - Flutter 树生命周期:
Widget/Element/State/RenderObject
一个实用对照表如下:
| 关注点 | Flutter 层信号 | Android 宿主更接近谁 | iOS 宿主更接近谁 | 关键差异 |
|---|---|---|---|---|
| 首次创建页面状态 | initState | onCreate | viewDidLoad | Flutter 是页面 state 创建,不等于宿主窗口创建 |
| 依赖环境首次就绪 / 变化 | didChangeDependencies | onCreate 后读取依赖 / config 变化 | trait / environment 更新 | Flutter 更强调 inherited 依赖传播 |
| 重新描述 UI | build | invalidate / requestLayout / Compose recomposition | setNeedsLayout / body 重算 | build 远比宿主创建更频繁 |
| 页面暂时离树 | deactivate | View detach / Fragment view 变动 | view hierarchy 临时调整 | 不代表最终销毁 |
| 页面最终释放 | dispose | onDestroy / view 销毁 | deinit / view controller release | 只针对当前 Flutter state |
| App 回前台 | AppLifecycleState.resumed | onStart / onResume | applicationDidBecomeActive | 属于宿主 app 前后台,而非单 route |
| App 退后台 | AppLifecycleState.paused | onPause / onStop | applicationDidEnterBackground | 页面可能还在 route 栈中 |
| 宿主不可见但未销毁 | route 仍在栈 + app paused | Activity stopped | app backgrounded | Flutter 页面内存可继续存在 |
一个典型场景:Flutter 页在 Android 宿主中切后台
常见观察顺序:
text
Flutter 页面已在栈顶
→ 宿主 Activity onPause
→ Flutter 收到 AppLifecycleState.inactive / paused
→ 宿主 Activity onStop此时:
- 当前 Flutter route 往往没有
dispose - 页面 state 还在内存里
- 但它已经不该继续做前台资源工作
所以:
- 页面级资源释放:看
dispose - App 前后台暂停/恢复:看
AppLifecycleState - Route 栈切换:看
Navigator/ route observer
Android / Flutter / Web / Backend 对照
| 维度 | Flutter | Android | Web | Backend |
|---|---|---|---|---|
| 页面创建 | initState 创建 State | Activity/Fragment onCreate | 组件 mount / 首次 render | 请求进入 handler |
| 页面更新 | build 反复执行 | View 刷新 / Compose recomposition | rerender | 无 UI 对应 |
| 页面暂离前台 | route 被覆盖但仍在栈 / App 变 inactive | onPause | tab 失焦 / route 被遮挡 | 无直接对应 |
| 页面不可见 | route 可能仍在栈,App 可能 paused | onStop | 页面 hidden / 缓存 | 无 UI 栈 |
| 页面最终销毁 | dispose | onDestroy | unmount | 请求结束 / 对象释放 |
| App 前后台 | AppLifecycleState | Activity + Process 生命周期 | Page Visibility / lifecycle | 进程/worker 级管理 |
| 状态恢复 | 上层状态、Restoration、持久化 | ViewModel / SavedStateHandle / 持久化 | store + storage | cache / DB / session |
常见场景
1. 列表页进入详情页再返回
这个场景要解决的不是“页面有没有销毁”这么单一的问题,而是同时观察:
- 列表页原
State是否还活着 - 返回时触发的是重新
build,还是重新initState - 详情页销毁后,父页是否只做轻量恢复,还是把昂贵副作用又跑了一遍
对应最小观察代码可以看:
对应 Lab:flutter-lifecycle-observer · lifecycle_observer_page.dart
这个 lab 里父页有计数器、输入框和 push 到详情页的按钮;详情页返回时会把结果带回父页,便于观察“返回后状态是否保留、哪些日志会再次打印”。
这个场景的概念重点是:返回上一页时,很多时候发生的是“原列表页 State 继续存活,但因为重新成为栈顶而再次 build”,而不是“列表页被销毁后重新创建”。所以下面这组顺序,观察的是 rebuild,不是 recreate。
text
列表页首次:initState → didChangeDependencies → build
打开详情:列表页可能再次 build;详情页 initState → build
返回列表:详情页 dispose;列表页再次 build- 预期现象:列表页通常不会立刻销毁;返回时更常见的是再次
build,而不是重新initState。 - 观察重点:如果把昂贵初始化写在
build,返回时就会重复执行;判断是否真的重建,要看有没有出现新的initState/ 旧页dispose。
2. App 退后台时暂停轮询、动画、摄像头
这个场景的概念重点是:App 退后台 和 route 被 push/pop 不是同一类事件。
- route 变化回答“页面栈怎么变了”
AppLifecycleState回答“宿主 App 现在是否还在前台”
因此像轮询、动画、摄像头、播放器这类资源,不能只等 dispose,而要结合 AppLifecycleState 做暂停与恢复。
这里先把概念钉住:App 退后台时,Flutter 页面常常还留在 route 栈里,没有走到 dispose;真正变化的是宿主 App 的前后台状态。因此下面这些现象对应的是 AppLifecycleState,不是 route 销毁顺序。
对应 Lab:flutter-lifecycle-observer · lifecycle_observer_page.dart
- 预期现象:当前页面未必
dispose,但会先后收到inactive/paused等状态;回前台后再收到resumed。 - 观察重点:如果你只在
dispose停任务,退后台时任务可能继续跑;若回前台需要补刷新,应该在resumed做受控恢复,而不是在每次build都刷新。
3. 页面重建但状态保留 / 状态丢失
这个场景要先分清两个词:
- rebuild:重新执行
build,但原State可能仍在 - recreate:原
State不再复用,需要重新initState
Flutter 页面里很多“怎么又执行了一遍”的困惑,本质上都是没先分清这两个层级。
常见对照:
- 同一路由仍在栈里,且
State未被替换:计数器、controller 往往保留 - 改了
Key/ 换了 widget type:原State可能直接丢失 - route 被
pop后再重新打开:通常会重新initState
对应 Lab:flutter-lifecycle-observer · lifecycle_observer_page.dart
- 预期现象:同一路由仍在栈里且
State未被替换时,局部状态往往保留;一旦Key、类型或 route 生命周期变化导致旧State不再复用,就会转成真正的 recreate。 - 观察重点:先区分“rebuild”与“recreate”,再用日志看是否真的走到
dispose;不要看到页面内容刷新了,就直接判定状态被重建。
4. 异步回调晚于页面销毁
这个场景的核心概念是:异步任务的完成时机 和 页面 State 的存活时机 是两条独立时间线。
- 页面可以先
dispose - 异步结果可以稍后才回来
- 如果回调里仍直接
setState,就会命中经典的setState() called after dispose()
因此这里真正要建立的是“页面销毁后,回调必须先判断 mounted,且 subscription / timer / stream 要成对清理”的工程习惯。
这里的核心概念是:异步任务和页面生命周期不是一条时间线。任务可能仍在后台继续,页面却已经先被销毁;所以下面的顺序要拿来观察“结果晚到”问题,而不是误以为只要发起任务时页面还活着,回调回来时就一定还能更新 UI。
常见顺序:
text
initState 发起异步任务
→ 用户迅速返回
→ dispose
→ 异步结果回来
→ setState after dispose 报错- 预期现象:页面先
dispose后,晚到结果不应再直接驱动旧 UI;安全实现应在回调前检查mounted,并让订阅类资源在dispose成对清理。 - 观察重点:异步回调前检查
mounted;subscription / timer / stream 必须在dispose取消,否则最容易出现setState after dispose与隐性泄漏。
常见坑
| 坑 | 现象 | 修法 |
|---|---|---|
在 build 中做网络请求 / 埋点 / controller 创建 | 返回页面、父组件刷新时重复执行 | 一次性初始化放 initState;持续状态交给状态层 |
| 把 App 前后台逻辑写成 route push/pop | 切后台不生效,前台恢复混乱 | 用 WidgetsBindingObserver 监听 AppLifecycleState |
误把 deactivate 当最终销毁 | 过早释放资源,或错误理解日志 | 真正释放放 dispose |
忘记在 dispose 解绑 observer / controller / stream | 内存泄漏、重复回调、setState after dispose | 在 dispose 成对清理 |
改动 Key 却没意识到会换掉 State | 输入框内容、滚动位置、局部状态突然丢失 | 只在确有需要时使用 / 变更 Key |
以为 Flutter 页面等同宿主 Activity | 资源释放时机判断错误 | 分清 route 生命周期、widget 生命周期、app 生命周期 |
与相近概念对比
| 概念 | 关注点 | 更适合处理的问题 | 不该替代什么 |
|---|---|---|---|
initState | 当前 State 首次创建 | 一次性初始化、controller 创建 | 不能替代每次前台恢复 |
didChangeDependencies | inherited 依赖变化 | locale / theme / provider 依赖重算 | 不能替代纯 UI 渲染 |
build | UI 描述重算 | 组装 widget tree | 不能承载一次性副作用 |
deactivate | 暂离元素树 | 观察节点迁移 | 不能替代资源最终释放 |
dispose | 当前 State 最终释放 | 解绑、取消订阅、销毁 controller | 不能替代 App 退后台 |
AppLifecycleState | 宿主 App 前后台 | 暂停轮询、动画、摄像头、前后台埋点 | 不能替代 route push/pop |
route observer / Navigator | 页面栈进出 | 页面曝光、返回刷新、路由协作 | 不能替代 App 生命周期 |
| 上层状态管理 / 持久化 | 跨页面或跨重建保留状态 | 草稿、过滤条件、会话状态 | 不能替代局部 widget 生命周期 |
对应实验
各章节内已嵌入跳转链接;此处汇总全部 Lab:
| Lab | 说明 | 源码 |
|---|---|---|
| flutter-lifecycle-observer | flutter-lifecycle-observer | lifecycle_observer_page.dart |
复习检查题
为什么说 Flutter 生命周期至少要分成 Widget / Element / RenderObject 三层来理解?
答:因为 Widget 只是不可变配置,Element 才是挂载实例与复用关系的核心,RenderObject 才负责布局与绘制。只有把三层拆开,才能解释为什么
build会频繁执行、为什么同一个页面看似“重建了”却没有dispose。initState、build、dispose最核心的职责边界分别是什么?答:
initState负责当前State的一次性初始化与资源创建;build负责可重复执行的 UI 描述;dispose负责最终解绑与释放。把一次性副作用塞进build,或把 App 前后台逻辑塞进dispose,都会导致时机错误。为什么 App 退后台时不能只依赖页面
dispose?答:因为 App 切后台时当前 route 往往还留在栈里,页面 state 仍然存活,不一定触发
dispose。这时应通过AppLifecycleState暂停轮询、动画、摄像头等前台资源。Flutter 页面返回时“重新 build”和“重新创建 State”有什么本质区别?
答:重新
build表示当前 Element / State 仍被复用,只是 UI 重新描述;重新创建State则意味着旧状态已不再复用,通常会重新走initState,局部内存状态也可能随之丢失。判断依据是是否真的走到了dispose,以及 Widget 的位置、类型、Key 是否保持稳定。Flutter 与 Android 宿主生命周期映射时,最容易误判的点是什么?
答:最容易误判的是把 Flutter route 或 widget 生命周期直接等同 Android
Activity生命周期。实际上 Flutter 至少并行存在 route 生命周期、widget/state 生命周期、app 生命周期三条线;宿主onPause/onStop时 Flutter 页面可能还没dispose。
速记
- Flutter 不是单层 View 树,而是
Widget配置 +Element挂载 +RenderObject渲染三层模型 initState做一次性初始化,build做可重复 UI 描述,dispose做最终释放deactivate只是暂离树,不等于销毁- App 前后台看
AppLifecycleState,页面进出栈看Navigator/ route observer - rebuild ≠ recreate;没看到
dispose,就别轻易下结论说“页面被销毁了”