Skip to content

内存、生命周期、系统约束 ​

一句话定位 ​

这一模块不是把“内存 / 生命周期 / 后台限制”并排罗列,而是按知识导航树收口为一条判断主线:

内存层 → Android 页面层 → Flutter 页面层 → iOS 约束层

读完后,应该能先判断问题属于哪一层,再决定继续看对象引用、宿主页面、Flutter 页面状态,还是系统授权边界。

本篇覆盖声明 ​

  • 主线深写:JVM / Android / Kotlin / Dart / Flutter 的内存、生命周期、前后台限制
  • 必要对照:iOS 只保留帮助跨端判断所必需的生命周期与后台限制边界
  • 暂不展开:服务端与 Web 不在本模块主线,只在对照表里保留最低必要映射

复习目标不是背 API,而是建立四级导航判断:

  1. 内存层:对象在哪、为什么没释放、GC 为什么抖动
  2. Android 页面层:宿主页面什么时候创建、覆盖、恢复、销毁
  3. Flutter 页面层:Widget / Element / State 为什么重建、缓存、dispose
  4. iOS 约束层:跨端方案落到 iOS 时,系统为什么不保证同等后台窗口

为什么需要这棵知识树 ​

这部分如果不先分层,Android / Flutter / iOS 的很多线上问题会被误判:

  • 把 对象没释放 误判成 生命周期没走
  • 把 页面没销毁 误判成 系统一定还允许继续执行
  • 把 Flutter 页面没 dispose 和 Android Activity 没 onDestroy 混成一件事
  • 把 Android 能做的后台方案直接套到 iOS,导致设计先天不可落地
  • 把“异步”“线程”“isolate”“后台任务”当成同一类能力,结果选型失真

对 Android 背景转 Flutter / 跨端的工程师来说,这一模块的价值是:

  • 把 Java/Kotlin/JVM 经验 映射到 Dart/Flutter
  • 把 对象生命周期、页面生命周期、系统授权边界 拆开理解
  • 把 宿主页面行为 和 Flutter 页面行为 分开看
  • 为后续 03-background-tasks、04-state-routing-architecture 建立前置判断

知识导航树:从对象到系统约束 ​

第一层:内存层(先判断对象为什么还活着) ​

先读:JVM / ART / Dart / Flutter 内存模型总览

这一层解决:

  • 对象在堆、栈、引用链里处于什么状态
  • GC 为什么没有立刻回收
  • Dart isolate 为什么不是 JVM 线程共享堆模型
  • Android / Flutter 泄漏问题为什么经常不是“页面没关掉”这么简单

内存层深入专题:

适合带着这些问题进入:

  • 页面退出了,内存为什么没降?
    • 一句话答:页面结束只走完 UI 生命周期,只要还有强引用链(单例 / 缓存 / 回调持有页面对象),GC 就不会回收,内存自然不降。
  • 明明已经 dispose / onDestroy,对象为什么还在?
    • 一句话答:dispose / onDestroy 只是通知回调,并不会切断其它地方对对象的引用链;对象仍可达,回收就不会发生。
  • 短命对象很多时,为什么主线程 / 主 isolate 会抖?
    • 一句话答:大量临时对象会持续触发分配与 GC,GC 暂停 / 回收本身会占用主线程或主 isolate 时间,表现为卡顿抖动。

内存专题推荐顺序 ​

总览页只负责建立共同坐标,深入内容按问题分成两条路径:

text
01 JVM / ART / Dart / Flutter 内存模型总览
├── Android / JVM:05 GC → 08 引用 → 06 泄漏 → 02 Android 生命周期
└── Dart / Flutter:07 Dart GC → 03 Flutter 生命周期 → 图片/FFI/Engine 内存
  • Android/JVM 路径:先理解 GC Roots 和可达性,再理解引用强度,最后使用 LeakCanary、Hprof 和 Profiler 定位真实持有链。
  • Dart/Flutter 路径:先理解 Dart 对象分配和 isolate heap,再结合 Widget/Element/State 生命周期,最后处理图片缓存、FFI、Native 和 GPU 资源。
  • JMM 旁路:如果问题是“线程 A 的写入何时对线程 B 可见”,转到 JMM:happens-before / 安全发布;它不是 GC 或对象布局专题。

第二层:Android 页面层(先建立宿主页面判断) ​

再读:Android 生命周期总览

这一层解决:

  • onCreate → onStart → onResume → onPause → onStop → onDestroy 的执行顺序
  • 页面是被覆盖、返回、重建,还是因为配置变更重走一遍
  • Fragment 生命周期与 View 生命周期为什么要拆开看
  • ViewModel / SavedStateHandle 在页面销毁与恢复之间扮演什么角色

对应实验入口:

适合带着这些问题进入:

  • 页面只是不可见,还是已经进入销毁路径?
    • 一句话答:不可见(onStop)≠ 销毁(onDestroy);是否进入销毁路径才决定状态如何存活、资源是否释放。
  • 为什么旋转屏幕后页面像“重建”但业务状态不一定全丢?
    • 一句话答:配置变更走“销毁 + 重建”,但 ViewModel 跨配置变更存活、SavedStateHandle 保存可持久化状态,所以业务数据不一定丢。
  • 为什么 onStop 并不等于拿到了后台长期执行许可?
    • 一句话答:onStop 只代表页面不可见,进程随时可能被 LMK / 系统回收;后台执行窗口由系统政策决定,不由回调决定。

第三层:Flutter 页面层(再把宿主经验迁移到 Flutter) ​

再读:Flutter 生命周期总览

这一层解决:

  • Widget / Element / RenderObject 三层模型分别负责什么
  • initState / didChangeDependencies / build / deactivate / dispose 各自意味着什么
  • AppLifecycleState 与宿主前后台是怎样映射的
  • 页面返回、缓存、keepAlive、路由恢复为什么不能直接套 Android Activity 心智

对应实验入口:

适合带着这些问题进入:

  • 为什么返回上一页后又 build 了一次?
    • 一句话答:路由变化会改动 Element 树,触发重新 build;build 是渲染流水线的一环,不等于重建 State。
  • 为什么 build 不能当 onCreate 用?
    • 一句话答:build 会被频繁调用(依赖变化、父级 rebuild、尺寸变化),不是一次性初始化时机;一次性初始化应放 initState。
  • Flutter 页面状态丢失,到底是 Widget 重建、Element 替换,还是宿主生命周期变化?
    • 一句话答:三者表现不同——Widget 重建不丢 State;Element 子树销毁才丢 State;宿主前后台变化触发 AppLifecycleState,需先定位是哪一种。

第四层:iOS 约束层(最后收口跨端设计边界) ​

最后读:iOS 生命周期与后台限制

这一层解决:

  • active / inactive / background / suspended / terminated 各是什么现实状态
  • 为什么 iOS 不保证 Android 式通用后台能力
  • BGTask / Background Fetch 为什么只能当机会窗而不是准点执行
  • 跨端方案到了 iOS,哪些能力必须收缩成保守设计

适合带着这些问题进入:

  • Android / Flutter 方案迁到 iOS 时,哪里会失效?
    • 一句话答:依赖 Android 通用后台能力(WorkManager 等价物、开机自启、无限制后台执行)的部分会失效;iOS 只有白名单例外和系统给的机会窗。
  • 哪些后台能力属于白名单例外,而不是通用能力?
    • 一句话答:后台音频、定位、VoIP、BGTask 等需声明能力且受系统条件约束,属于例外而非任何 App 都有的通用执行窗口。
  • 为什么“技术上能写”不等于“系统一定会给执行窗口”?
    • 一句话答:能否写代码与系统是否授权并调度执行是两个层面;iOS 由系统决定何时、给多少后台执行机会。

推荐阅读路径:按问题而不是按文件名走 ​

路径 A:先查“对象为什么没释放” ​

  1. JVM / ART / Dart / Flutter 内存模型总览
  2. Android 生命周期总览(若问题发生在 Android 宿主)
  3. Flutter 生命周期总览(若问题发生在 Flutter 页面)
  4. iOS 生命周期与后台限制(若最终要做跨端收口)

路径 B:先查“页面为什么重建 / 恢复路径不对” ​

  1. Android 生命周期总览
  2. Flutter 生命周期总览
  3. JVM / ART / Dart / Flutter 内存模型总览(回头补对象与泄漏判断)
  4. iOS 生命周期与后台限制

路径 C:先查“离开页面后还能不能继续做事” ​

  1. Android 生命周期总览
  2. Flutter 生命周期总览
  3. iOS 生命周期与后台限制
  4. 再继续看 ../03-background-tasks/README.md

路径 D:先看渲染与显示链路(与内存/生命周期相邻的显示层) ​

  1. Android View 绘制流程、Choreographer 与 VSync 帧同步(Choreographer / VSync 驱动绘制)
  2. SurfaceFlinger、BufferQueue 与三重缓冲:帧如何合成上屏(帧如何合成上屏、三重缓冲为何抗抖动)
  3. iOS Core Animation:CALayer、渲染服务与显示链路(iOS 侧 CALayer / 渲染服务 / 离屏渲染,与 Android 对位)

导航总表:每一层读什么 ​

导航层先回答什么对应文档关键入口
内存层对象为什么还活着 / 为什么 OOM / 为什么 GC 卡顿JVM / ART / Dart / Flutter 内存模型总览内存区域、GC、isolate 独立堆、泄漏判断
Android 页面层宿主页面为什么创建、覆盖、恢复、销毁Android 生命周期总览android-activity-lifecycle-basic · android-activity-lifecycle-compose · android-activity-lifecycle-java-xml
Flutter 页面层Widget / Element / State 为什么重建、缓存、disposeFlutter 生命周期总览flutter-lifecycle-observer
iOS 约束层跨端方案落到 iOS 时系统为什么不保证同等后台窗口iOS 生命周期与后台限制前后台、挂起、后台模式、BGTask 边界

分层概念地图:不要把四层问题混掉 ​

层级关注对象核心问题常见误判
内存层对象、引用、堆、栈、GC、isolate heap为什么对象还活着 / 为什么 OOM / 为什么 GC 卡顿以为页面退出对象就会自动释放
Android 页面层Activity、Fragment、ViewModel、SavedStateHandle为什么页面重建 / 为什么返回会再执行 / 状态如何保存以为 onResume / onDestroy 足以解释所有状态问题
Flutter 页面层Widget、Element、State、路由栈、AppLifecycleState为什么 build 重跑 / 为什么状态丢失 / 为什么 dispose 没按想象触发以为 Flutter 页面等同于 Android Activity
iOS 约束层进程、前后台、挂起、系统调度窗口退后台后还能不能跑 / 哪些能力受 OS 白名单控制以为异步任务或 isolate 就等于后台能力

底层机制:这一模块真正要建立什么判断 ​

1. 内存回收不是页面回调 ​

  • Activity onDestroy、Flutter dispose、iOS 进入 background,都不是 GC 回收动作本身
  • 真正决定对象能否回收的,是引用链是否断开、GC 何时扫描、宿主/框架是否还保留对象
  • 所以“页面结束了对象为什么还活着”通常要回到 引用关系 查,不是先怪生命周期回调

2. Android 页面层与 Flutter 页面层要分开看 ​

  • Android 宿主页面回答的是 Activity / Fragment 何时进入、覆盖、恢复、销毁
  • Flutter 页面层回答的是 Widget / Element / State 何时重建、缓存、dispose
  • 同一个“返回上一页”动作,在两层里触发的机制并不相同

3. 页面生命周期不是系统后台授权 ​

  • onStop 不等于“你还能放心继续跑长任务”
  • Flutter AppLifecycleState.paused 不等于“Dart 代码还能无限执行”
  • iOS background 更不等于“拿到了持续后台窗口”

也就是说:

  • 页面层 回答“用户界面现在处于什么阶段”
  • 系统层 回答“OS 现在还允许你做什么”

4. Android / Flutter / iOS 的共同问题,落点却不同 ​

同一个业务需求“页面离开后继续做事”,在不同层里可能对应:

  • 释放页面绑定资源,避免泄漏
  • 把 UI 任务迁移到 ViewModel / repository
  • 把短任务交给前台内存中的异步执行
  • 把可靠任务交给 WorkManager / 系统托管下载
  • 在 iOS 上改产品策略,接受机会型刷新而不是准点执行

跨平台对照表 ​

维度Android / JVMFlutter / DartiOS复习时该抓什么
对象默认承载线程共享堆 + 线程栈isolate 独立堆 + 事件循环ARC + 系统生命周期约束对象活多久和页面回调不是一回事
页面前台可交互onResume页面在前台 + 可交互;App 级看 resumedactive不要把“可见”与“可交互”混掉
页面离开前台onPause / onStop路由覆盖 + AppLifecycleState 变化inactive / background页面退下去不等于进程还能一直跑
页面最终销毁onDestroydispose可能被挂起或终止销毁时机不等于对象立即释放
长任务承载协程 / 线程 + WorkManager / FGS 等Future / isolate,但受宿主限制白名单后台模式 / BGTask先问系统是否授权,再谈实现
高频事故泄漏、重复请求、状态丢失、后台限制误判build 重复、副作用乱放、setState after dispose误把 iOS 当 Android 用先分清内存层、Android 页面层、Flutter 页面层、iOS 约束层

常见场景:按导航树落位 ​

1. Android 页面关了,内存没降 ​

典型判断顺序:

  1. 先回到内存层,看引用链是否真的断开
  2. 再看 Android 页面层 是否真的走到 onDestroy
  3. 再看有没有单例、静态引用、observer、handler、协程作用域持有页面
  4. 最后区分只是 GC 还没发生,还是引用链真的没断

2. Flutter 返回上一页后又执行了一遍 ​

典型判断顺序:

  1. 先定位到 Flutter 页面层,确认是 build 再跑,还是 initState / dispose 真发生了
  2. 再看副作用是不是误写在 build
  3. 再看状态放在页面 State 里还是更上层作用域里
  4. 若页面退后台,还要同时看宿主层与 AppLifecycleState

3. 跨端要做“后台继续同步” ​

典型判断顺序:

  1. 先问问题还停留在页面层,还是已经升级到系统约束层
  2. 如果只是页面切换,可能只要脱离页面作用域即可
  3. 如果是 Android 真后台可靠任务,要继续看 ../03-background-tasks/README.md
  4. 如果要兼容 iOS,先假设没有通用准点保活,再反推产品方案

常见坑 ​

坑典型现象正确收口
把对象生命周期和页面生命周期混为一谈页面退出后还怀疑“为什么对象不死”先查引用链、GC 时机、缓存持有者
把 Flutter build 当 onCreate返回页面或父组件刷新就重复请求一次性初始化放 initState / 状态层
把 Android onStop 当后台长期执行许可退后台后任务被系统限制或杀掉页面回调只说明 UI 阶段,不说明 OS 授权
以为 Flutter isolate 能绕过 iOS/Android 后台限制代码写了但系统不给执行机会isolate 解决 CPU 与隔离,不解决后台授权
把 iOS BGTask 当 WorkManager/AlarmManager执行时机漂移大、不触发只把它当机会窗,不当准点调度

相近概念对比 ​

概念它回答的问题不回答什么
GC对象何时可能被回收页面什么时候回调
Activity / Fragment 生命周期Android 宿主页面何时进入、退出、重建系统是否允许长期后台运行
Widget / State 生命周期Flutter 页面何时重建、缓存、dispose某个对象一定是否释放
AppLifecycleState / iOS app stateApp 是否在前台、后台、挂起边缘某个任务一定是否继续执行
isolate / 线程 / 协程任务如何并发执行OS 是否授予后台执行窗口

对应实验 ​

当前本模块已有实验入口:

Lab说明关键源码
android-activity-lifecycle-basicandroid-activity-lifecycle-basicLifecycleLogActivity.kt · ActivityDetailActivity.kt
android-activity-lifecycle-composeandroid-activity-lifecycle-composeComposeLifecycleActivity.kt
android-activity-lifecycle-java-xmlandroid-activity-lifecycle-java-xmlJavaLifecycleActivity.java
viewmodel-savedstate-demoviewmodel-savedstate-demoViewModelSavedStateDemoActivity.kt · CounterViewModel.kt
flutter-lifecycle-observerflutter-lifecycle-observerlifecycle_observer_page.dart

计划补齐但当前仍缺的实验:

  • labs/flutter/host/topics/memory-lifecycle/flutter-page-state-restoration/README.md — 验证路由返回、状态保留、恢复边界
  • labs/dart/host/topics/memory-lifecycle/dart-gc-allocation-patterns/README.md — 验证短命对象分配、主 isolate 卡顿与 isolate 拆分边界

复习检查题 ​

  1. 为什么这一模块要改成“内存层 → Android 页面层 → Flutter 页面层 → iOS 约束层”的导航树?

    答:因为对象是否释放、Android 宿主页面是否重建、Flutter 页面状态为何变化、系统是否允许后台执行分别由不同机制决定。按这条树来读,才能先定位问题层级,再进入对应专题,不会把 GC、页面回调和系统授权混成一团。

  2. 为什么 Android / Flutter 页面结束,不等于对象立刻释放?

    答:因为页面回调只说明 UI 实例进入某个阶段,真正决定对象能否回收的是引用链是否断开、GC 何时发生,以及框架/缓存/单例是否仍在持有对象。

  3. 为什么 Flutter 页面层要单独从 Android 页面层拆出来?

    答:因为 Flutter 的 Widget / Element / State 重建模型,与 Android Activity / Fragment 的宿主生命周期不是一一对应关系。如果不单列出来,最容易把 build 误当 onCreate,或者把宿主前后台变化和页面 State 变化混看。

  4. 如果需求是“用户离开页面后上传继续,且要跨端可落地”,第一步该问什么?

    答:第一步不是选线程、协程还是 isolate,而是先问这是页面离开后的短任务,还是 App 退后台后的可靠任务;只有先澄清系统层要求,后续才知道该放页面外作用域、Android 后台能力,还是干脆改成服务端/机会型同步方案。

后续导航建议 ​

结论:还可以继续补,但优先补“映射页”,不是盲目加主题。

建议后续补两类内容:

  1. 模块内跨端映射页(优先级高)

    • 建议文件:docs/02-memory-lifecycle/05-cross-platform-memory-lifecycle-mapping.md
    • 作用:把 Android Activity / Fragment、Flutter Widget / AppLifecycleState、iOS app state 放在同一张执行顺序表里
    • 重点:只做“映射与误判边界”,不重复展开各端完整机制
  2. 模块间导航页或 README 小节(优先级中)

    • 从本模块明确跳到:
      • 03-background-tasks:当问题已经从页面层升级到系统后台可靠执行
      • 04-state-routing-architecture:当问题核心变成状态保存、作用域与路由恢复
    • 作用:告诉读者什么时候该继续看后台任务,什么时候该继续看状态管理

速记 ​

  • 对象活着:先查引用链,不先怪生命周期
  • Android 页面问题:先分 Activity / Fragment / ViewModel 是哪条线
  • Flutter 页面问题:先分 Widget / Element / State / AppLifecycleState 是哪条线
  • 后台能否跑:先问 OS 是否授权,不先谈线程/协程/isolate
  • 跨端收口到 iOS:默认保守设计,接受机会窗,不假设通用保活

站点构建时间:2026/8/24 23:43:17