第121篇 Activity 生命周期全解:七个回调的成对关系

简介: 本文深入解析Activity生命周期的两大维度:视图生命周期(6个回调)与进程生命周期(状态保存/恢复),直击面试高频考点——“为何如此设计”及“进程被杀后如何恢复”。厘清`onSaveInstanceState`触发条件、Bundle大小限制、ViewModel与状态保存的边界,并给出工程最佳实践与避坑指南。

Kotlin 语言线到此收尾,从这一篇开始进入 Android 框架层。第一个切入口就是 Activity 生命周期——它看起来是最基础的八道题,但面试里真正能区分人的是"为什么这样设计"与"进程被回收后怎么恢复"这两层。

先把结论放在前面:Activity 生命周期有两组。视图生命周期(onCreate/onStart/onResume/onPause/onStop/onDestroy)描述界面从创建到销毁;进程生命周期(onSaveInstanceState/onRestoreInstanceState/onNewIntent)描述"Activity 对象可能还在但系统随时会杀掉进程"这件事带来的状态保存问题。面试里绝大多数人只会背第一组,第二组才是分水岭。

机制背后的执行路径

先把八个回调的语义与"该做什么/不该做什么"钉清楚:

| 回调 | 触发时机 | 该做 | 不该做 | |---|---|---|---| | onCreate | 实例首次创建 | 找控件、初始化成员、绑定数据(一次性) | 注册监听、开始动画 | | onStart | 变为可见 | 恢复"可见期才需要"的资源(注册观察者) | 重网络请求 | | onResume | 变为前台可交互 | 连接相机、注册传感器 | 大量初始化 | | onPause | 失去前台 | 暂停动画、释放独占资源(相机) | 提交未保存数据 | | onStop | 完全不可见 | 取消网络请求、注销观察者 | 释放 View 引用 | | onSaveInstanceState | 即将被销毁前 | 保存"下次恢复需要"的状态 | 保存 View 层级(系统自动做) | | onRestoreInstanceState | 由非空 Bundle 恢复 | 恢复已保存状态 | 在此做网络请求 | | onDestroy | 实例销毁 | 释放自持资源(viewLifecycleOwner 协程等) | 释放绑定到 Activity 生命周期的资源(已在 onStop 释放) |

有三个必须能说清的机制点:

第一,onSaveInstanceState 只在"该 Activity 真的可能被杀掉"时才调用。 具体条件是它在 onStop 之后被调用,且仅当 Activity 处于"已停止且不再返回前台"(正常返回上一页、屏幕关闭)或者在 onPause 之后进程被系统杀掉。用户主动按返回键销毁时不会调用——因为没必要恢复。

第二,进程被回收后的恢复流程。 系统杀进程时会保存 View 层级与 onSaveInstanceState 的 Bundle;用户从最近任务回来时,系统重建 Activity → 走完 onCreate(此时 savedInstanceState 非 null)→ onStart → onRestoreInstanceState → onResume。关键:onRestoreInstanceState 排在 onStart 之后,而 onCreate 里也能读到同一个 Bundle。两者都能恢复,只是时机不同。

第三,Fragment 与 Activity 的生命周期耦合。 Fragment 的 onAttach 早于 Activity 的 onCreate;onDetach 晚于 Activity 的 onDestroy。这解释了"为什么不能在 Fragment 构造参数里拿 Activity 的 View"。

真实工程场景的推演

场景一:相机/传感器的注册与释放。正确做法是成对注册释放,且释放点要早:在 onResume 注册、onPause 释放。放在 onStart/onStop 也可行,但如果应用在后台仍需定位(如导航),放 onStart/onStop 更合适。判据是"这个资源在不可见时是否还有价值"。

场景二:网络请求的位置。放在 onCreate 意味着每次旋转屏幕都重新请求(除非用 ViewModel)。规范是请求放 ViewModel + repeatOnLifecycle(STARTED) 收集,这样旋转不重复请求、不可见时停止收集。这是第 096 篇的落地。

场景三:状态保存的取舍。什么该存、什么不该存:

override fun onSaveInstanceState(outState: Bundle) {
   
    super.onSaveInstanceState(outState)
    outState.putString(KEY_QUERY, currentQuery)     // 小的标量、用户可见状态
    outState.putInt(KEY_SCROLL, list.computeVerticalScrollOffset())
    // 不存:列表数据(重新加载即可)、View 引用(大、可能已失效)、Bitmap
}

关键点:Bundle 有大小限制(TransactionTooLargeException 典型阈值约 1MB,且走 Binder 传输),所以只存"恢复界面必需且体积小"的东西。这条是高频追问。

场景四:进程被杀与 ViewModel 的关系。ViewModel 活不过进程被杀。所以"屏幕旋转不丢数据"靠 ViewModel,"应用被系统回收后回来还有数据"靠 onSaveInstanceState + 数据层(Room/网络)。把两者混淆是常见错误。

最常见的坑是

把重逻辑放在 onResume,每次弹窗返回都触发,性能被反复消耗。 表现是打开一个权限弹窗、返回后界面重新加载数据、动画重播。修法:onResume 只做"回到前台"的轻量处理;数据加载交给 ViewModel + onViewCreated 触发一次;必要的事后刷新用 registerForActivityResult 在结果回调里精确触发。

其次是在 onCreate 里注册监听、在 onDestroy 里才释放。表现是 Activity 不可见时监听仍回调,持有 View 导致泄漏。修法:注册与释放成对且放在相邻的回调(onStart/onStop 或 onResume/onPause)。

还有一个更隐蔽的坑:把大对象(含列表数据、Bitmap)塞进 onSaveInstanceState。表现是低内存设备上后台被杀时恢复崩溃(TransactionTooLargeException),或保存耗时明显。修法:只存标量与必要的 id,列表重新从数据层加载。

现场手写这一段就够了

class DetailActivity : AppCompatActivity() {
   

    private lateinit var binding: ActivityDetailBinding
    private val vm: DetailViewModel by viewModels()
    private var sensorManager: SensorManager? = null

    // 1) onCreate:只做一次性初始化
    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)
        binding = ActivityDetailBinding.inflate(layoutInflater)
        setContentView(binding.root)
        sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
        setupListeners()
        // 恢复状态:注意 onCreate 也能读到同一个 Bundle
        savedInstanceState?.getString(KEY_QUERY)?.let {
    vm.setQuery(it) }
    }

    // 2) onStart / onStop:资源成对,"不可见时还有价值"的资源放这里
    override fun onStart() {
   
        super.onStart()
        lifecycleScope.launch {
                    // 收集只在可见时进行
            repeatOnLifecycle(Lifecycle.State.STARTED) {
   
                vm.ui.collect {
    render(it) }
            }
        }
    }

    // 3) onResume / onPause:独占资源与动画
    override fun onResume() {
   
        super.onResume()
        sensorManager?.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI)
    }

    override fun onPause() {
   
        sensorManager?.unregisterListener(listener)   // 必须成对
        super.onPause()
    }

    // 4) 状态保存:只存小而必要的东西
    override fun onSaveInstanceState(outState: Bundle) {
   
        super.onSaveInstanceState(outState)
        outState.putString(KEY_QUERY, vm.currentQuery)
        outState.putInt(KEY_TAB, binding.tab.selectedTab)
        // ❌ outState.putSerializable(KEY_LIST, hugeList)   → TransactionTooLargeException
    }

    // 5) 恢复:onRestoreInstanceState 在 onStart 之后
    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
   
        super.onRestoreInstanceState(savedInstanceState)
        binding.tab.selectedTab = savedInstanceState.getInt(KEY_TAB)
    }

    override fun onDestroy() {
   
        binding.recycler.adapter = null            // 断开 Adapter 持有 View
        super.onDestroy()
    }

    // 6) 用 Activity Result API 代替 onActivityResult,拿结果更精确
    private val pickCity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    r ->
        if (r.resultCode == RESULT_OK) {
   
            vm.selectCity(r.data?.getStringExtra("city"))   // 只在这里处理,不污染 onResume
        }
    }
    private fun launchPicker() = pickCity.launch(Intent(this, CityPickerActivity::class.java))

    companion object {
   
        private const val KEY_QUERY = "query"
        private const val KEY_TAB = "tab"
    }
}

关键行解读:onCreate 只做一次性初始化、避免旋转重复请求;onStart 里用 repeatOnLifecycle 收集(回到前台重启、不重复请求上游);onResume/onPause 成对管理独占资源;onSaveInstanceState 只存标量、避免 TransactionTooLargeException;registerForActivityResult 把"结果处理"从 onResume 里摘出来。

面试追问四连

"onSaveInstanceState 什么时候会被调用?" 答:在 onStop 之后调用,通常只在"该 Activity 可能被系统回收"时——即被其它 Activity 覆盖(之后可能回来)或进程被系统杀死。用户主动按返回键销毁时不调用,因为无需恢复。

"进程被杀后回来,Activity 怎么恢复的?" 答:系统保存了 View 层级与 Bundle;重建时走 onCreate(savedInstanceState 非 null)→ onStart → onRestoreInstanceState → onResume。View 层级由系统自动恢复,自定义状态从 Bundle 取。

"ViewModel 能跨进程被杀吗?" 答:不能。ViewModel 只活在进程内,进程死即消失。旋转屏幕不丢数据靠 ViewModel;应用被系统回收后恢复靠 onSaveInstanceState + 数据层。

"Bundle 大小限制是多少?" 答:通过 Binder 传递,有 TransactionTooLargeException 风险,实践上按 1MB 以内控制。原则是只存"恢复界面必需的标量与 id",列表、Bitmap 一律重新加载。

落地建议

1. 团队规范:注册与释放成对且放在相邻回调(资源放 onStart/onStop 或 onResume/onPause,看不可见时是否仍有价值);onResume 保持轻量,不做数据加载。 2. 网络与耗时任务统一放 ViewModel + repeatOnLifecycle(STARTED);一次性结果统一用 ActivityResult API,不用 onActivityResult 旧写法。 3. onSaveInstanceState 建立"白名单"(标量、id、选中项),其余一律重新加载;把"不往 Bundle 放大对象"写进 code review checklist。 4. 自动化预防:用 SavedStateRegistry 或 SavedStateHandle 让状态保存可测(ViewModel + SavedStateHandle 自动处理);给关键页面写"进程被杀后恢复"的测试(ActivityScenario + recreate())验证状态不丢。

给正在准备面试的你 把这题画成"两条并行的状态轴"。上面一条横轴画视图生命周期(六个回调,标出"这里该做什么"的一行小字);下面一条横轴画进程生命周期(onSaveInstanceState → 进程被系统杀掉 → 重建 → onRestoreInstanceState),在中间画一个断裂符号标"进程死亡"。两条轴之间用虚线连接对应位置,并标一句:"视图生命周期由代码控制,进程生命周期由系统决定——这是所有困惑的来源"。

再补一个高分追问答案:"为什么旋转屏幕会导致 Activity 重建?如何避免?" 原因有两个:①configChanges 未声明时系统默认重建(旋转属于配置变更);②进程被回收后回来。避免重建的方式是声明 android:configChanges="orientation|screenSize|keyboardHidden",但现代做法不推荐——因为手动处理配置变更需要自己重建状态、重置布局,反而更容易出错。推荐方案是允许重建 + ViewModel 保存数据(ViewModel 的设计本意就是为此),必要时用 onSaveInstanceState 补小状态。能讲出"重建不可怕,可怕的是没有正确保存状态",通常就是这题的高分点。

复习时别孤立刷题:tailrec 与尾递归——上一节是 Kotlin 语言线的收尾,本节进入 Android 框架层,两条线的共同点是"都要知道机制而不只是结论"。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。你的支持就是这个系列持续更新的动力。

「Android软件开发面试·从入门到精通」连载系列

上一篇:tailrec-与尾递归:递归优化的编译期支持

下一篇预告:Activity-启动模式:standard-到-singleInstance

有任何问题欢迎在评论区留言交流。

相关文章
|
18天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
8618 25
|
16天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
3040 14
|
16天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2110 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
5天前
|
人工智能 JSON Linux
【全网最详细】ComfyUI使用教程:下载+本地部署+配置+工作流搭建一篇搞定(2026最新版)
ComfyUI是一款免费开源的本地AI绘图工具,采用节点式工作流设计,支持文生图、图生图、局部重绘、放大、换脸等多种功能。可离线运行,依赖显卡加速,无需联网。支持自定义流程保存与分享,插件生态丰富,适合进阶用户。(239字)
|
16天前
|
云安全 人工智能 安全
|
11天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
11天前
|
人工智能 JSON 编解码
【2026最新版】ComfyUI本地部署教程,新手也能看懂!
ComfyUI是本地运行的AI绘画工具,采用节点式工作流设计:通过拖拽连接“加载模型”“提示词编码”“采样”“解码”等模块,实现高度可控的文生图。新手推荐使用秋叶整合包,一键启动、内置模型管理与插件安装器,轻松上手。(239字)

热门文章

最新文章