ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Android Lifecycle实战:从状态机到自定义LifecycleOwner防泄漏

2026/10/1 3:45:17 拓冰建站 浏览量
Android Lifecycle实战:从状态机到自定义LifecycleOwner防泄漏 做过几年 Android 开发的人或多或少都遇到过这么一种场景你在 Activity 里注册了一个广播接收器或者一个定位回调结果忘了在 onDestroy 里注销测试的时候一切正常一到线上就各种奇怪问题——内存泄漏、重复回调、甚至崩溃。我接手过一个维护中的项目线上反馈里有一种崩溃特别诡异用户进入某个地图页面切换到后台再切回来界面上的位置回调居然还在疯狂更新最后因为权限被拒导致崩溃。查了半天发现是定位 SDK 的监听器在 Activity 销毁后没有移除而 SDK 内部又强依赖 Activity 的上下文。原因不复杂但暴露了一个核心问题——让普通对象感知 Activity/Fragment 的生命周期并且在合适的时机自动执行对应的清理动作这件事远比表面看起来复杂。Android 官方很早就给出了标准答案Lifecycle 组件。它是 Jetpack 架构组件的基石之一ViewModel、LiveData、Room 全部建立在这套机制之上。Lifecycle 想要解决的问题很单纯让任何对象都能感知宿主Activity/Fragment的生命周期变化并在合适的时机完成初始化、更新和清理。这篇文章不打算讲源码解析只聊使用。我会结合真实项目里踩过的坑来拆解 Lifecycle 的用法——从最基础的状态机模型到自定义 LifecycleOwner 的高级玩法再到各种为什么看起来没生效的排查思路。适合所有 Android 开发者尤其是维护中大型项目、做组件封装或者写 SDK 的同学。1. Lifecycle 是什么为什么我们离不开它1.1 从一次线上崩溃说起先把前面那个故事讲完整。当时那个页面是一个地图轨迹追踪页业务方要求用户进入页面就自动开始定位退出页面就停止。最初的实现很直接在 Activity 的 onResume 里 startLocationonPause 里 stopLocation。后来需求变了定位逻辑被抽到了一个独立的 Tracker 类里由这个类自己管理回调的注册和反注册。问题随之而来Tracker 根本不知道 Activity 什么时候销毁。你可以在 Tracker 里暴露一个 release() 方法然后让 Activity 在 onDestroy 里手动调用。这种做法在页面少的时候勉强能用页面一多十几个 Activity 都要手动调用 release()总有那么几个忘了写。于是泄漏就产生了而且这种泄漏很难查因为崩溃往往发生在很久以后跟泄漏点隔着十万八千里。Lifecycle 组件就是为了干掉这种手动通知的模式。它把宿主现在处于什么状态变成了一个可以被观察的、标准化的信号源。任何一个普通类只要实现了 LifecycleObserver 接口并被注册到某个 LifecycleOwner 上就能自动收到生命周期事件回调完全不需要宿主逐个通知。对维护大型项目的团队来说这意味着生命周期相关的职责终于可以被封装、被复用、被测试了。1.2 核心角色LifecycleOwner 与 LifecycleObserverLifecycle 这套机制涉及三个关键角色我把它们的关系整理成了下面这张表角色说明典型实现LifecycleOwner拥有生命周期的对象对外暴露getLifecycle()AppCompatActivity、Fragment、自定义 ViewLifecycleObserver观察者能接收生命周期事件回调自定义的任意类Lifecycle两者之间的桥梁负责管理状态与派发事件LifecycleRegistry标准实现LifecycleOwner 只有一个方法getLifecycle()返回一个 Lifecycle 对象。Lifecycle 内部维护了一个状态机并能在状态变化时通知所有已注册的 Observer。对开发者来说最常见的用法就是写一个实现了 LifecycleObserver 的类然后把它注册到 Activity/Fragment 的lifecycle.addObserver(...)上。看一个最小例子class MyObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // 宿主进入 STARTED 状态 } override fun onDestroy(owner: LifecycleOwner) { // 宿主被销毁做清理 } } // 使用 class MainActivity : AppCompatActivity() { private val observer MyObserver() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycle.addObserver(observer) } }注意老项目里可能见过OnLifecycleEvent(Lifecycle.Event.ON_START)之类的注解写法这个注解在 lifecycle 2.6.0 中已被标记为 deprecated。官方推荐使用DefaultLifecycleObserver或直接实现LifecycleEventObserver。后面我会详细讲新老写法的差异和迁移成本。1.3 官方组件对 Lifecycle 的深度依赖很多人用了好几年 ViewModel 和 LiveData却没意识到它们全部建立在 Lifecycle 之上。ViewModel 的onCleared()之所以能在 Activity 销毁时被调用是因为ViewModelStore内部注册了生命周期回调LiveData 之所以能在页面不可见时不派发数据是因为observe(LifecycleOwner, Observer)方法内部会把 LifecycleOwner 的状态纳入判断。如果你理解了 Lifecycle 本身再回头看这些组件会非常通透。它们本质上都是 LifecycleObserver 的某种封装ViewModelStore 在 ON_DESTROY 时清空数据LiveData 在状态变成 STARTED 时才开始派发数据Room 的协程作用域会在 ON_DESTROY 时自动取消。所以什么时候我建议你直接使用 Lifecycle 而不是 LiveData当你有一段资源需要跟随生命周期开启和关闭但它又不是数据的场景。比如传感器监听、蓝牙连接、网络轮询、音视频采集这些用 LiveData 表达很别扭用 LifecycleObserver 却刚刚好。2. 核心机制状态机与事件派发2.1 状态与事件两张表看懂 LifecycleLifecycle 里最重要的两个概念是 State状态和 Event事件。新手最容易把这两个东西搞混我用最直白的方式解释State是此时宿主处于什么状态是一个结果。Event是宿主刚刚发生了什么是一次通知。Lifecycle 官方定义了五个状态State含义对应的典型场景INITIALIZED已创建但还没走到 onCreate对象构造完成后CREATEDonCreate 已执行onStart 未执行Activity 创建完成STARTEDonStart 已执行onResume 未执行页面可见但不可交互RESUMEDonResume 已执行页面可见且可交互DESTROYED已销毁onDestroy 之后Event 则对应我们熟悉的生命周期回调Event触发时机ON_CREATEonCreate 之后ON_STARTonStart 之后ON_RESUMEonResume 之后ON_PAUSEonPause 之后ON_STOPonStop 之后ON_DESTROYonDestroy 之后2.2 事件上升与状态下降这里有个非常关键的点State 是单调的Event 是双向的。Activity 从创建到销毁状态沿着 INITIALIZED → CREATED → STARTED → RESUMED → DESTROYED 这条线上升而从用户切后台到最终销毁状态沿着 RESUMED → STARTED → CREATED → DESTROYED 这条线下降。为什么叫状态机因为 Lifecycle 内部就是按照状态转移来派发事件的。每次 Activity 回调执行后LifecycleRegistry 会重新计算当前应该处于什么状态然后向上或向下调整并在状态改变时通知 Observer。举个具体例子Activity 执行完 onCreate 之后Lifecycle 会把状态从 INITIALIZED 提升到 CREATED同时向所有 Observer 派发 ON_CREATE执行完 onStart 后状态从 CREATED 提升到 STARTED派发 ON_START。反过来执行完 onStop 后状态从 STARTED 降回 CREATED派发 ON_STOP。理解这个机制对排查问题特别有帮助。你就能明白Observer 收到的 ON_STOP 和 ON_DESTROY 顺序永远不会乱因为状态机的状态转移是受控的。官方文档里那句话非常准确Lifecycle 保证了事件派发的一致性无论宿主是正常销毁还是异常重建。2.3 新老 API 对比DefaultLifecycleObserver 到底好在哪里lifecycle 2.6.0 之后官方推荐用DefaultLifecycleObserver替代注解方式。它本质上是一个接口提供了一组带默认实现空实现的回调方法class LocationTracker : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // 开启定位 } override fun onStop(owner: LifecycleOwner) { // 停止定位 } override fun onDestroy(owner: LifecycleOwner) { // 释放资源 } }对比注解写法新写法有四个明显优势不需要字符串拼方法名不怕写错IDE 自动补全友好类型安全不会被混淆器搞坏参数里直接带上了 LifecycleOwner需要的时候可以访问宿主状态。我强烈建议新项目直接用 DefaultLifecycleObserver老项目如果还在用注解在升级 lifecycle 版本时顺手迁移掉。迁移成本极低就是把OnLifecycleEvent标注的方法改成 override 方法方法体原样搬过去就行。3. 实操在真实项目里用好 Lifecycle3.1 场景一定位监听器的生命周期绑定定位是 Lifecycle 最典型的应用场景。先看一个错误的写法class LocationTracker(val context: Context) { fun start() { // 注册定位监听 } fun stop() { // 移除定位监听 } } // Activity 里 override fun onStart() { super.onStart() tracker.start() } override fun onStop() { super.onStop() tracker.stop() }这种写法功能上没错但存在两个隐患一是如果 Tracker 被多个页面复用每个页面都要写两行 start/stop代码重复二是如果某个页面忘记调用 stop()定位就会一直跑耗电又泄漏。而且这种忘记在 code review 中很难发现因为每个页面都长得很像。用 Lifecycle 改造后class LocationTracker(private val callback: (Location) - Unit) : DefaultLifecycleObserver { private var isActive false override fun onStart(owner: LifecycleOwner) { start() } override fun onStop(owner: LifecycleOwner) { stop() } private fun start() { if (isActive) return isActive true // 注册定位监听 } private fun stop() { if (!isActive) return isActive false // 移除定位监听 } }在 Activity 里只需要一行lifecycle.addObserver(LocationTracker { location - // 更新 UI })调用方不再需要关心什么时机启动、什么时机停止Observer 自己在合适的时机完成。这就是 Lifecycle 最大的价值把什么时候该干活这个责任从调用方转移到了组件自身。说句实话用过一段时间之后你再看那些手写 start/stop 的代码会浑身难受。3.2 场景二网络轮询任务的自动启停再来看一个轮询场景。很多 App 有首页自动刷新、消息中心轮询的需求。最常见的错误写法是override fun onResume() { super.onResume() startPolling() } override fun onPause() { super.onPause() stopPolling() }看着没问题但如果你把轮询逻辑封装成一个 PollingHelper并且希望它在后台自动暂停、回到前台自动恢复用 Lifecycle 是这样写的class PollingHelper( private val intervalMillis: Long, private val action: () - Unit ) : DefaultLifecycleObserver { private val scope CoroutineScope(SupervisorJob() Dispatchers.Main) private var job: Job? null override fun onStart(owner: LifecycleOwner) { start() } override fun onStop(owner: LifecycleOwner) { stop() } override fun onDestroy(owner: LifecycleOwner) { scope.cancel() stop() } private fun start() { if (job?.isActive true) return job scope.launch { while (isActive) { action() delay(intervalMillis) } } } private fun stop() { job?.cancel() job null } }这里注意一个细节我在 onDestroy 里调用了scope.cancel()目的是在协程作用域层面兜底。虽然 onStop 已经把 job cancel 了但万一 onDestroy 时还有挂起的任务在飞cancel scope 能确保不留下任何后台协程泄漏。这套逻辑配合 Room 的协程支持、ViewModel 的 viewModelScope基本能覆盖绝大多数跟着生命周期走的异步场景。3.3 场景三自定义 LifecycleOwner 的高级玩法上面两个场景都是把 Observer 绑定到 Activity/Fragment 上属于搭便车。如果有一天你需要在自定义 View、Service 或者一个普通的业务管理器里拥有生命周期怎么办答案是自定义 LifecycleOwner。我在做视频播放器组件时遇到了这个需求播放器 View 内部要管理一堆资源包括 SurfaceHolder、音频焦点、网络缓冲这些都需要跟随 View 从创建到销毁的完整过程。但我不能确定外部使用者会在哪个时机调用释放方法。于是我给播放器 View 实现了 LifecycleOwnerclass PlayerView JvmOverloads constructor( context: Context, attrs: AttributeSet? null ) : FrameLayout(context, attrs), LifecycleOwner { private val registry LifecycleRegistry(this) private var isAttached false init { // 初始状态设为 CREATED因为 View 已经构造完成 registry.currentState Lifecycle.State.CREATED } override fun getLifecycle(): Lifecycle registry override fun onAttachedToWindow() { super.onAttachedToWindow() if (!isAttached) { isAttached true registry.currentState Lifecycle.State.RESUMED } } override fun onDetachedFromWindow() { super.onDetachedFromWindow() if (isAttached) { isAttached false registry.currentState Lifecycle.State.DESTROYED } } }玩法核心是LifecycleRegistry。它有一个currentState属性你在合适的时机把状态往上调或往下调LifecycleRegistry 就会自动派发对应的事件给所有 Observer。上面这个例子里我没有模拟完整的状态转移链而是在 View attach 时直接把状态设置到 RESUMEDdetach 时设置为 DESTROYED。这在语义上是允许的——LifecycleRegistry 会按照状态机规则从当前状态一步一步向上或向下派发事件确保 Observer 收到的回调顺序正确。3.4 手动派发事件的补充场景除了设置currentStateLifecycleRegistry 还有一个更细粒度的handleLifecycleEvent()方法可以直接派发一个 Eventregistry.handleLifecycleEvent(Lifecycle.Event.ON_START)但这里要特别注意直接派发事件和设置状态只能选一种不要混用。我在项目里见过有人先handleLifecycleEvent(ON_START)再设currentState RESUMED结果 Observer 收到了两次 ON_START 回调。因为 handleLifecycleEvent 会改变内部状态你如果又手动设 currentState等于让状态机跳了两下事件自然就重了。正确的做法是场景简单就只设 currentState场景复杂就只调 handleLifecycleEvent把宿主原有的 onCreate/onStart/onStop 回调逐一对传。千万别两套混着来否则排查问题时会非常痛苦。4. 常见问题与排查技巧实录4.1 问题Observer 的 onDestroy 一直不回调这是我被问过最多的问题之一。排查思路分成三步第一步检查 Observer 是否真的被注册了。Lifecycle 有个特性同一个 Observer 实例被 addObserver 多次后面的调用会被忽略。如果你在 onCreate 和 onResume 里都写了lifecycle.addObserver(observer)那不是重复注册而是第二次被静默忽略。但如果你每次都是lifecycle.addObserver(SomeObserver())创建新实例那就有两套 Observer 在跑行为和预期完全不一样。第二步检查宿主是否真的走到了 onDestroy。常见场景是Activity 被系统回收时走了 onSaveInstanceState 但没有立刻走 onDestroy这并不算异常。如果你在开发者选项里开启了不保留活动那 onDestroy 一定会触发。第三步也是最隐蔽的状态机已经处于 DESTROYED 后再添加 Observer可能收不到任何事件。因为 DESTROYED 是终态状态不会再变化事件也不会再派发。如果业务上有销毁后还想回调一次的需求需要在销毁前手动触发一次或者不要依赖生命周期回调改成在宿主销毁时显式调用一个方法。4.2 问题同一个 Observer 被添加多次会怎样前面提过LifecycleRegistry 内部用FastSafeIterableMap存储 Observerkey 是 Observer 实例本身。所以同一实例重复 addObserver 是幂等的不会重复回调。但如果你每次新建实例就会导致多个实例同时收到回调某些操作被执行多次如果 Observer 内部持有 Activity 的引用removeObserver 时只移除了一个剩下的还在直接造成内存泄漏排查技巧在 Observer 的构造方法里打一行日志看同一个生命周期过程中到底 new 了多少个实例。如果数量大于 1说明 addObserver 的时机有问题多半是放在了 onResume 或者某个会被多次调用的方法里。4.3 问题Fragment 中 Lifecycle 的几个大坑Fragment 的生命周期比 Activity 复杂得多最容易踩的坑有三个第一个Fragment 的 lifecycle 不等于 getView() 的 lifecycle。Fragment 从 detached 到 re-attached它自己的 Lifecycle 可能会跨过多个状态而 View 的生命周期是独立的。如果你在 Fragment 的 Observer 里直接操作 View很可能在 View 还不可用时就被回调了。正确处理方式能绑viewLifecycleOwner就不要绑 Fragment 本身。第二个viewLifecycleOwner 只在 onCreateView 之后才有值。在 onCreateView 之前调用viewLifecycleOwner.lifecycle.addObserver(...)会直接崩必须在 onViewCreated 之后用。很多新手在这里翻车报错信息往往指向空指针不太好查。第三个Fragment 销毁 View 时viewLifecycleOwner 会走到 DESTROYED但 Fragment 本身可能还活着。如果你把定位监听绑定在 viewLifecycleOwner 上Fragment 的 View 被回收时监听就会停但 Fragment 还在后台业务上可能不对。这种情况需要你根据业务判断到底绑哪个 LifecycleOwner。提示我个人的习惯是——跟 UI 相关的比如更新 TextView、操控 RecyclerView绑 viewLifecycleOwner跟页面业务状态相关的比如上报埋点、保存草稿绑 Fragment 自己的 lifecycle。4.4 最佳实践清单踩坑多年总结出来的经验最后整理一份可以直接抄的清单每条都是我实际踩过坑换来的优先使用 DefaultLifecycleObserver放弃注解方式。没有什么理由继续用旧写法除非你的项目还停留在 lifecycle 2.4 以下且无法升级。Observer 内部不要持有 Activity/Fragment 的强引用。如果确实需要用 WeakReference或者至少在 onDestroy 里置空。不要在 onResume 里 addObserver。这会打乱状态机的初始事件派发导致 Observer 漏掉 ON_CREATE、ON_START 等事件。addObserver 的最佳时机是 onCreate。至于 removeObserver只要 Observer 不是在静态区缓存不 remove 通常也不会泄漏因为 onDestroy 之后 Lifecycle 本身也不可用了。自定义 LifecycleOwner 时用 LifecycleRegistry 要注意线程。默认的 LifecycleRegistry 不是线程安全的setCurrentState 和 handleLifecycleEvent 必须在主线程调用。如果你的组件可能在其他线程触发状态变化需要自己加锁或切换到主线程。如果做 SDK 封装对外暴露 LifecycleOwner 而不是注册方法。例如播放器 SDK可以让调用方传入一个 LifecycleOwner内部自动 addObserver。这样你的 SDK 使用成本极低还天然避免了误用。我个人在把项目里所有手写生命周期调用改成 Lifecycle 之后最强烈的感受是代码里少了很多 if 判断少了很多 onResume/onPause 重写少了很多忘记调用 release()带来的线上崩溃。刚开始会觉得不就是回调吗我自己写也行但用久了你就会发现Lifecycle 的真正价值不在于省那几行代码而在于它把生命周期从一个隐式约定变成了一种可复用、可组合、可测试的工程能力。它让每一个组件都清楚地知道我该在什么时候起来干活又该在什么时候收工回家。如果你正在维护一个老项目建议从最痛的地方开始改定位、轮询、传感器、蓝牙这些是最容易泄漏又最容易用 Lifecycle 解决的场景。改完一两个模块你自然就知道这套东西到底值不值得全面推广了。最后再分享一个小技巧调试 Lifecycle 回调顺序时可以在 DefaultLifecycleObserver 每个方法里打一行 Log看它们跟随宿主状态变化的先后顺序对着状态机图对比排查问题会快非常多。