资讯动态

Android Lifecycle 原理深度解析:从源码到自定义 LifecycleOwner 实践

发布时间:2026/10/13 17:12:35 来源:尧图企业网站定制
1. 从一次内存泄漏说起为什么需要 Lifecycle做过 Android 开发的人大概率都写过这样的代码在 Activity 的onCreate里注册广播接收器、绑定传感器监听、启动网络请求回调然后在onDestroy里一个个手动反注册、解绑、取消。写的时候觉得没什么等到项目里 Activity 和 Fragment 多起来某天用内存分析工具一跑发现某个已经销毁的页面还挂在某个监听器的引用链上——内存泄漏了。问题出在哪出在生命周期管理是散落的手工活。Activity 和 Fragment 有生命周期但那些被它们持有的组件Presenter、自定义 View、各种 Listener并不知道宿主什么时候会死。于是每个组件都得自己想办法感知宿主的生死要么靠宿主主动调用要么靠一堆if (isDestroyed())判断。这种模式写起来啰嗦维护起来更痛苦稍不留神就漏掉一个反注册。Lifecycle 组件要解决的就是这件事把生命周期从 Activity/Fragment 的私有状态抽象成一个可以被任意对象观察的公共能力。任何需要感知生命周期的组件只要实现一个观察者接口就能在宿主状态变化时收到通知不用再依赖宿主主动调用。这听起来简单但它背后牵扯到一套相当精巧的设计——注解处理器、适配器模式、状态机、观察者模式全用上了。这篇内容适合已经用过 Lifecycle 但没深究过原理的 Android 开发者也适合想理解 Jetpack 组件设计思路的人。我会从为什么这么设计讲到源码里到底怎么实现的再补充一些实际使用中容易踩的坑。读完之后你不仅能看懂 Lifecycle 的源码还能理解它为什么能同时兼容 Activity、Fragment 和自定义组件。2. Lifecycle 的三层抽象事件、状态与观察者要理解 Lifecycle 的原理先得搞清楚它的三个核心概念事件Event、状态State和观察者Observer。这三者构成了整个组件的基础模型后面所有的源码分析都围绕它们展开。2.1 事件与状态生命周期被拆成了两个维度很多人第一次看 Lifecycle 源码时会困惑为什么既有Event又有State直接用一个枚举表示当前在哪个阶段不行吗不行。因为生命周期是有方向的。ON_CREATE和ON_DESTROY都对应页面不在前台这个模糊描述但一个是往上走、一个是往下走。如果只用一个状态枚举观察者收到通知时无法判断自己是刚被创建还是即将被销毁也就没法做出正确的响应。Lifecycle 的做法是把生命周期拆成两个维度Event事件表示发生了什么是一个瞬时动作比如ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP、ON_DESTROY。State状态表示当前处于哪个阶段是一个持续状态比如CREATED、STARTED、RESUMED、DESTROYED。两者之间有明确的映射关系。ON_CREATE事件发生后状态变为CREATEDON_START之后变为STARTEDON_RESUME之后变为RESUMED而ON_PAUSE、ON_STOP、ON_DESTROY则分别把状态拉回STARTED、CREATED、DESTROYED。这里有个细节值得注意状态是至少达到的语义。也就是说当状态是RESUMED时意味着CREATED和STARTED也都已经发生过了。这个语义在LifecycleRegistry判断观察者是否需要接收某个事件时非常关键。2.2 观察者接口一个方法搞定所有回调Lifecycle 的观察者接口定义得非常克制public interface LifecycleObserver { }对就是一个空接口。真正的方法调用靠注解public class MyObserver implements LifecycleObserver { OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { // 处理逻辑 } }这种设计的好处是解耦观察者不需要实现一堆方法只声明自己关心的事件即可。但代价是注解处理器必须在编译期扫描这些方法生成一个适配器类把注解方法映射到统一的回调接口上。这个适配器就是LifecycleEventObserver的实现运行时由LifecycleRegistry调用。后来官方推出了DefaultLifecycleObserver接口用默认方法替代注解避免了反射和注解处理器的开销。两种方式现在都支持但新项目建议直接用DefaultLifecycleObserver。2.3 状态机的边界为什么 DESTROYED 是终点Lifecycle 的状态机有一个硬性约束DESTROYED是终态一旦进入就不能再回到其他状态。这个约束在LifecycleRegistry的setCurrentState里有明确检查如果试图从DESTROYED转换到其他状态会直接抛异常。为什么这么设计因为一个已经销毁的页面不应该再被复活。如果允许状态回退观察者可能会在页面销毁后再次收到ON_START之类的通知导致资源被重新申请、监听器被重新注册最终引发难以排查的 bug。把DESTROYED设为终态等于在框架层面堵死了这条错误路径。理解了这三个概念再看LifecycleRegistry的源码就不会迷路了。它本质上就是一个带状态校验的事件分发器维护当前状态接收新事件计算状态变化然后按顺序通知所有观察者。3. LifecycleRegistry 源码拆解事件是怎么被分发的LifecycleRegistry是 Lifecycle 的核心实现类所有生命周期事件的接收和分发都在这里完成。它的源码不算长但每一行都有讲究。我按添加观察者 → 接收事件 → 分发事件的顺序来拆。3.1 addObserver观察者的状态对齐当你调用lifecycle.addObserver(observer)时LifecycleRegistry做的第一件事不是简单地把观察者塞进列表而是先把观察者的状态对齐到宿主的当前状态。Override public void addObserver(NonNull LifecycleObserver observer) { State initialState mState DESTROYED ? DESTROYED : INITIALIZED; ObserverWithState statefulObserver new ObserverWithState(observer, initialState); // ... 省略部分逻辑 State targetState calculateTargetState(observer); while (statefulObserver.mState.compareTo(targetState) 0 mObserverMap.contains(observer)) { // 逐个事件推进观察者状态 Event event upEvent(statefulObserver.mState); statefulObserver.dispatchEvent(mOwner, event); } }这段逻辑的意思是如果宿主已经处于RESUMED状态而新加入的观察者初始状态是INITIALIZED那么框架会补发ON_CREATE、ON_START、ON_RESUME三个事件让观察者的状态追上宿主。这个设计非常关键。想象一下在onResume里动态添加一个观察者如果框架不补发事件这个观察者就永远收不到ON_RESUME因为它加入的时候事件已经过去了。补发机制保证了无论何时添加观察者它都能收到从创建到当前状态的所有事件行为一致。3.2 handleLifecycleEvent状态推进的入口宿主比如 Activity在生命周期变化时会调用LifecycleRegistry.handleLifecycleEvent(event)。这个方法做两件事计算事件对应的目标状态然后调用moveToState。public void handleLifecycleEvent(NonNull Lifecycle.Event event) { State next getStateAfter(event); moveToState(next); }getStateAfter是一个纯函数把事件映射到状态。比如ON_CREATE映射到CREATEDON_RESUME映射到RESUMED。moveToState则负责实际的推进逻辑。3.3 moveToState正向与反向的分发差异moveToState是分发逻辑的核心它要处理两种情况状态前进比如从CREATED到RESUMED和状态后退比如从RESUMED到CREATED。private void moveToState(State next) { if (mState next) { return; } mState next; // ... 省略同步逻辑 if (mHandlingEvent || mAddingObserverCounter ! 0) { mNewEventOccurred true; return; } mHandlingEvent true; sync(); mHandlingEvent false; }注意mHandlingEvent和mNewEventOccurred这两个标志位。它们解决的是重入问题如果在分发事件的过程中某个观察者又触发了新的生命周期事件比如在onResume里调用了finish()框架不能立即递归分发否则会导致状态错乱。正确的做法是标记有新事件发生了等当前分发结束后再统一处理。sync()方法负责遍历所有观察者把每个观察者的状态推进到与宿主一致。正向推进时按ON_CREATE → ON_START → ON_RESUME的顺序补发事件反向推进时按ON_PAUSE → ON_STOP → ON_DESTROY的顺序补发。这个顺序不能乱否则观察者会收到不符合预期的回调。3.4 状态比较compareTo 的妙用Lifecycle.State是一个枚举但它的声明顺序是精心安排的public enum State { DESTROYED, INITIALIZED, CREATED, STARTED, RESUMED; }这个顺序让compareTo可以直接用来判断状态的前后关系。RESUMED的 ordinal 最大DESTROYED最小。所以statefulObserver.mState.compareTo(targetState) 0就表示观察者状态落后于目标状态需要推进。用枚举的声明顺序来表达状态高低是一个很聪明的做法。它避免了手写比较逻辑也让代码更简洁。但要注意这个顺序是有语义的不能随意调整枚举顺序否则整个状态机就乱了。4. 注解处理器与适配器OnLifecycleEvent 背后的编译期魔法前面提到LifecycleObserver是个空接口真正的方法调用靠注解。那注解是怎么变成运行时回调的这就要说到lifecycle-compiler这个注解处理器了。4.1 编译期生成适配器类当你在一个类里写了OnLifecycleEvent注解的方法lifecycle-compiler会在编译期扫描到它并生成一个名为类名_LifecycleAdapter的类。这个类实现了LifecycleEventObserver接口内部持有一个对原始观察者的引用。生成的代码大致长这样public class MyObserver_LifecycleAdapter implements LifecycleEventObserver { final MyObserver mReceiver; MyObserver_LifecycleAdapter(MyObserver receiver) { this.mReceiver receiver; } Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { if (event Lifecycle.Event.ON_RESUME) { mReceiver.onResume(); } // ... 其他事件 } }运行时LifecycleRegistry通过反射找到这个适配器类实例化它然后调用onStateChanged。适配器内部再用if-else把事件分发给对应的注解方法。4.2 为什么用适配器而不是直接反射有人可能会问既然都要反射为什么不直接反射调用注解方法还要多生成一个适配器类原因有两个。第一性能。反射调用方法本身开销不小如果每次事件分发都反射查找方法在频繁的生命周期变化场景下会成为瓶颈。生成适配器后反射只发生在创建适配器实例时而且这个实例会被缓存后续的事件分发就是普通的方法调用。第二可预测性。适配器是编译期生成的方法映射关系在编译时就确定了不会因为混淆、反射权限等问题在运行时失败。这对大型项目来说很重要。4.3 DefaultLifecycleObserver更现代的替代方案注解方式虽然直观但有几个缺点需要注解处理器、生成的类会增加方法数、反射创建适配器有开销。所以官方后来推出了DefaultLifecycleObserverpublic interface DefaultLifecycleObserver extends LifecycleObserver { default void onCreate(NonNull LifecycleOwner owner) {} default void onStart(NonNull LifecycleOwner owner) {} default void onResume(NonNull LifecycleOwner owner) {} // ... }用默认方法定义所有回调观察者只需要重写自己关心的方法。这种方式不需要注解处理器也不需要反射运行时直接调用接口方法。新项目建议优先用这个接口老项目如果已经用了注解方式迁移也不复杂。5. 自定义 LifecycleOwner让任意组件拥有生命周期Lifecycle 最强大的地方在于它不只服务于 Activity 和 Fragment。任何组件都可以实现LifecycleOwner接口拥有自己的生命周期。这在自定义 View、Presenter、甚至 Service 里都很有用。5.1 实现 LifecycleOwner 的最小步骤实现一个自定义的LifecycleOwner只需要三步创建一个LifecycleRegistry实例传入this。实现getLifecycle()方法返回这个实例。在合适的时机调用handleLifecycleEvent推进状态。public class MyCustomView extends View implements LifecycleOwner { private final LifecycleRegistry lifecycleRegistry; public MyCustomView(Context context) { super(context); lifecycleRegistry new LifecycleRegistry(this); lifecycleRegistry.setCurrentState(Lifecycle.State.INITIALIZED); } Override public Lifecycle getLifecycle() { return lifecycleRegistry; } Override protected void onAttachedToWindow() { super.onAttachedToWindow(); lifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED); } Override protected void onDetachedFromWindow() { lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED); super.onDetachedFromWindow(); } }这样任何LifecycleObserver都可以观察这个 View 的生命周期在它 attach 和 detach 时收到通知。5.2 状态推进的时机选择自定义LifecycleOwner最容易出错的地方是状态推进的时机。推早了观察者可能在资源还没准备好时就收到回调推晚了观察者可能错过关键事件。一个实用的原则是把状态推进绑定到组件真实的生命周期节点上。比如自定义 View 用onAttachedToWindow和onDetachedFromWindowPresenter 用宿主 Activity 的回调Service 用onCreate和onDestroy。不要凭空造状态否则观察者的行为会变得不可预测。另外要注意setCurrentState和handleLifecycleEvent的区别前者直接设置状态后者通过事件推导状态。一般来说用handleLifecycleEvent更安全因为它会走完整的状态校验和分发流程。setCurrentState主要用于初始化或者在明确知道目标状态且不需要事件语义的场景。5.3 多观察者的顺序问题当多个观察者同时观察一个LifecycleOwner时它们的回调顺序是按添加顺序执行的。这个顺序在大多数场景下无所谓但在某些场景下很关键。比如一个观察者负责释放资源另一个负责记录日志如果日志观察者先执行可能会记录到资源已释放的错误状态。LifecycleRegistry内部用FastSafeIterableMap存储观察者遍历时按插入顺序。如果你对顺序有要求要么控制添加顺序要么在观察者内部做状态判断。不要依赖框架帮你排序它不保证。6. 实际使用中的坑与经验原理讲完了最后分享几个我在实际项目里踩过的坑。这些是文档里不会写、但真实开发中很容易遇到的问题。6.1 在 onDestroy 里做异步操作ON_DESTROY事件触发时页面已经进入销毁流程但此时做异步操作比如提交数据到数据库、发送网络请求是有风险的。因为进程可能随时被杀异步任务不一定能完成。正确的做法是在ON_STOP或更早的时机启动异步任务在ON_DESTROY里只做同步的清理工作。如果确实需要在销毁时提交数据考虑用WorkManager之类的机制把任务交给系统调度而不是依赖页面生命周期。6.2 观察者持有 Context 导致泄漏这是一个经典问题观察者内部持有了 Activity 的 Context而观察者的生命周期比 Activity 长导致 Activity 无法被回收。Lifecycle 本身不会导致泄漏但如果观察者注册后没有在合适的时机移除或者观察者内部持有长生命周期引用就会出问题。建议在ON_DESTROY里调用lifecycle.removeObserver(this)或者用LifecycleOwner的getLifecycle().addObserver配合自动移除机制。6.3 状态判断的常见误区很多人会用lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)来判断页面是否可见。这个判断本身没错但要注意isAtLeast的语义是至少达到RESUMED也满足isAtLeast(STARTED)。如果你只想判断是否处于 STARTED 且未 RESUMED需要写成currentState Lifecycle.State.STARTED。另一个误区是认为currentState是实时更新的。实际上currentState在handleLifecycleEvent被调用时更新而事件分发是同步的。所以在观察者回调内部读取currentState拿到的可能是分发前的状态而不是当前事件对应的状态。要拿当前事件对应的状态应该用事件参数推导。6.4 与协程、Flow 的配合现在很多项目用协程和 Flow 做异步。lifecycleScope和repeatOnLifecycle是很好的工具但要注意repeatOnLifecycle会在状态进入和退出时重新启动协程。如果你的 Flow 有副作用比如修改数据库重复启动可能导致重复执行。一个实用的模式是用repeatOnLifecycle(STARTED)收集 UI 相关的 Flow用lifecycleScope.launch处理一次性任务。不要把有副作用的操作放在会被重复启动的协程里。7. 从 Lifecycle 看 Jetpack 的设计哲学写完这些源码分析和踩坑经验我最大的感受是Lifecycle 的价值不在于它有多复杂而在于它把一个散落各处的痛点抽象成了一个统一的能力。在 Lifecycle 出现之前每个组件都得自己处理生命周期感知代码重复、容易出错。Lifecycle 把这部分逻辑收敛到框架层让组件只需要声明我关心哪些事件剩下的交给框架。这种关注点分离的思路后来被ViewModel、LiveData、DataStore等组件继承成了 Jetpack 的一贯风格。如果你正在维护一个老项目我建议先从DefaultLifecycleObserver开始替换手写的生命周期回调逐步把散落的注册/反注册逻辑收敛到观察者里。这个过程不需要一次性完成可以按模块推进。等观察者模式用顺了再考虑把一些业务逻辑抽到ViewModel里配合LiveData或Flow做数据驱动。最后分享一个小技巧调试生命周期问题时可以在观察者的每个回调里打日志同时打印lifecycle.currentState。这样能清楚看到状态推进的顺序和时机比单纯看代码推理高效得多。我在排查一个页面销毁后仍收到回调的问题时就是靠这个日志发现观察者被重复添加了两次导致同一个事件被处理了两遍。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑