Android Lifecycle 源码解析:状态机、事件分发与自定义组件实践

发布时间:2026/10/13 17:14:04
Android Lifecycle 源码解析:状态机、事件分发与自定义组件实践 1. 为什么 Lifecycle 值得单独拎出来讲做 Android 开发的朋友大概率都经历过这样的场景在 Activity 的onCreate里注册监听、在onDestroy里反注册在onStart里开定时器、在onStop里关掉它。代码写多了以后你会发现每个页面的生命周期回调里都塞着一堆“配对操作”漏掉一个就是内存泄漏顺序写反了就是空指针崩溃。Lifecycle 组件出现的意义就是把这套“跟着页面生死走”的逻辑从 Activity/Fragment 里抽出来交给一个独立的、可观察的对象去管理。这篇文章我打算把 Lifecycle 从使用姿势到源码原理完整拆一遍。核心关键词就是Lifecycle、LifecycleOwner、LifecycleObserver、LifecycleRegistry、状态机、Event 与 State 的映射关系。它解决的是“组件感知宿主生命周期”的问题适合已经会写 Android 页面、但被生命周期回调折磨过、想搞清楚底层到底怎么运转的中高级开发者。看完之后你应该能做到自己写一个能感知生命周期的自定义组件能看懂LifecycleRegistry里那套状态推进逻辑并且在面试或代码评审时把 Event 和 State 的关系讲明白。我个人的习惯是学一个 Jetpack 组件先问三个问题它替我省了什么事、它的核心数据结构是什么、它在边界情况下怎么保证正确。Lifecycle 这三个问题的答案分别是省去了手动配对注册反注册、核心是LifecycleRegistry里的状态机、边界靠“状态只能前进不能随意回退”的规则来兜底。下面按这个思路展开。2. Lifecycle 的整体设计与核心角色拆解2.1 四个核心角色分别负责什么Lifecycle 这套东西看着类不少其实角色分工非常清晰我习惯用“被观察者 观察者 登记处 状态记录”来理解。Lifecycle抽象类定义了一套生命周期状态的枚举和事件枚举同时提供addObserver、removeObserver、getCurrentState这些方法。它是个“契约”规定了谁能被观察、怎么观察。LifecycleOwner接口只有一个getLifecycle()方法。谁实现了它谁就“拥有”一个生命周期可以被观察。Activity、Fragment 在较新的版本里都默认实现了它。LifecycleObserver标记接口本身没有任何方法。它的子接口LifecycleEventObserver和DefaultLifecycleObserver才是真正干活的地方通过注解或方法回调接收事件。LifecycleRegistryLifecycle 的唯一官方实现类是整套机制的心脏。它持有观察者列表维护当前状态负责在宿主生命周期变化时把事件分发给所有观察者。这里有个容易混淆的点很多人以为LifecycleOwner和Lifecycle是一回事其实前者是“持有者”后者是“被持有的生命周期对象”。一个 Owner 对应一个 Lifecycle 实例通常就是LifecycleRegistry。2.2 为什么用“状态机 事件”而不是直接回调如果只是简单地转发onCreate、onStart那直接在 Activity 里调观察者的方法就行了为什么还要搞出一套 State 和 Event这是理解 Lifecycle 的关键。原因在于观察者的注册时机是不确定的。一个观察者可能在 Activity 已经onResume之后才被添加进来。如果只是简单转发事件这个后加入的观察者就永远收不到ON_CREATE、ON_START这些“过去发生过”的事件它的初始化逻辑就废了。Lifecycle 的解法是引入“当前状态”这个概念。LifecycleRegistry始终记录着宿主当前处于哪个 State当新观察者加入时它会根据观察者当前的状态和宿主当前状态之间的差距补发中间需要经历的事件把观察者“追平”到当前状态。这就是状态机的价值它让“迟到者”也能正确同步。2.3 State 与 Event 的映射关系这套映射是整个组件的骨架必须记牢。State 表示“现在处于什么阶段”Event 表示“发生了什么导致状态变化”。当前 State收到 Event目标 StateINITIALIZEDON_CREATECREATEDCREATEDON_STARTSTARTEDSTARTEDON_RESUMERESUMEDRESUMEDON_PAUSESTARTEDSTARTEDON_STOPCREATEDCREATEDON_DESTROYDESTROYED注意ON_DESTROY之后是DESTROYED这是一个终态到了这里观察者会被移除不再接收任何事件。另外ON_ANY是个特殊事件只在注解方式里用表示“任何事件都回调”。提示State 的枚举顺序是有讲究的DESTROYED的 ordinal 最小RESUMED最大。源码里判断“状态是否升级”就是靠比较 ordinal这个细节后面讲源码时会用到。3. 核心原理深挖LifecycleRegistry 是怎么运转的3.1 观察者是怎么被存起来的LifecycleRegistry内部维护了一个FastSafeIterableMapkey 是观察者value 是它的状态。这个数据结构本质是一个支持在遍历时安全增删的链表为什么不用普通的HashMap因为生命周期事件分发过程中观察者有可能在回调里把自己移除掉比如一次性任务普通集合遍历时会抛ConcurrentModificationException而这个自定义结构允许“边遍历边修改”。每个观察者被包装成一个ObserverWithState里面持有观察者本身和它当前的状态。这个包装很关键因为状态是“每个观察者各自维护”的不是全局一份。宿主状态是宿主状态观察者状态是观察者状态两者分开记录才能实现前面说的“补发”。3.2 状态同步的核心算法当宿主生命周期变化时LifecycleRegistry会调用handleLifecycleEvent(event)它先根据事件算出目标状态然后调用moveToState。这个方法里有个sync过程逻辑大致是如果当前状态小于目标状态需要前进就正向遍历观察者逐个把它们的 state 往上推。如果当前状态大于目标状态需要后退就反向遍历逐个往下推。每个观察者的状态推进是通过ObserverWithState.dispatchEvent完成的它会根据“观察者当前状态”和“目标状态”之间的差距逐个派发中间事件。举个例子宿主从RESUMED退到CREATED一个观察者当前是RESUMED那它需要依次收到ON_PAUSE、ON_STOP状态从RESUMED退到STARTED再退到CREATED。这个“逐个派发”保证了观察者收到的回调顺序和真实生命周期一致不会跳步。3.3 为什么状态只能前进不能随意回退源码里有个enforceMainThreadIfNeeded和状态校验逻辑核心约束是观察者的状态不能超过宿主的状态。也就是说宿主还在CREATED观察者不可能被推到RESUMED。这个约束防止了状态错乱。还有一个细节LifecycleRegistry在moveToState时会记录mHandlingEvent标志防止在事件分发过程中被重入调用导致状态错乱。如果你在观察者回调里又触发了宿主的状态变化虽然不常见这个标志能避免死循环。注意LifecycleRegistry默认要求在主线程操作createUnsafe可以绕过检查但除非你非常清楚自己在做什么否则不要用。跨线程操作生命周期状态是崩溃和状态错乱的常见来源。4. 实操从零写一个能感知生命周期的组件4.1 用 DefaultLifecycleObserver 写一个日志组件最推荐的写法是继承DefaultLifecycleObserver它提供了空实现的方法你只需要覆写关心的那几个不用像LifecycleEventObserver那样写一堆when判断。class LifecycleLogger : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { Log.d(LifecycleLogger, onCreate: ${owner.javaClass.simpleName}) } override fun onStart(owner: LifecycleOwner) { Log.d(LifecycleLogger, onStart) } override fun onResume(owner: LifecycleOwner) { Log.d(LifecycleLogger, onResume) } override fun onDestroy(owner: LifecycleOwner) { Log.d(LifecycleLogger, onDestroy, 清理资源) } }注册的时候直接lifecycle.addObserver(LifecycleLogger())就行。这里有个实操心得观察者对象最好在页面销毁时能被回收如果你用匿名内部类或者持有 Activity 引用的对象记得在onDestroy里移除否则即使 Lifecycle 帮你管理了回调对象本身还是可能泄漏。4.2 用注解方式实现老项目常见早期版本用的是OnLifecycleEvent注解配合LifecycleObserver标记接口class LegacyObserver : LifecycleObserver { OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { // 恢复逻辑 } OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop() { // 暂停逻辑 } }注解方式底层靠反射生成适配器性能比直接实现接口略差而且现在官方已经不推荐了。新项目一律用DefaultLifecycleObserver老项目迁移时注意注解方式的方法不能有参数除了可选的LifecycleOwner参数写错会静默不回调这个坑我踩过。4.3 自定义 LifecycleOwner如果你有一个自定义的 View 或者管理器想让它拥有生命周期能力可以实现LifecycleOwnerclass MyCustomComponent : LifecycleOwner { private val lifecycleRegistry LifecycleRegistry(this) override val lifecycle: Lifecycle get() lifecycleRegistry fun start() { lifecycleRegistry.currentState Lifecycle.State.STARTED } fun stop() { lifecycleRegistry.currentState Lifecycle.State.CREATED } fun destroy() { lifecycleRegistry.currentState Lifecycle.State.DESTROYED } }这里的关键是状态推进必须符合状态机的顺序不能从INITIALIZED直接跳到RESUMED。LifecycleRegistry内部会帮你补发中间事件但如果你跳得太离谱比如从DESTROYED往回走它会抛异常或者忽略。实测下来按CREATED → STARTED → RESUMED的顺序推进最稳。5. 常见问题与排查技巧实录5.1 观察者收不到回调怎么办这是最高频的问题我整理了一个排查顺序表现象可能原因排查方法完全不回调观察者没注册检查addObserver是否执行只回调部分事件注册时机太晚看注册时宿主处于哪个 State注解方法不触发方法签名错误检查参数和返回值回调顺序异常在回调里改了状态避免在回调里操作宿主状态偶发不回调跨线程操作确认在主线程注册和触发重点说“注册时机太晚”这个。假设你在onResume里才addObserver那这个观察者会收到ON_CREATE、ON_START、ON_RESUME三个补发事件因为宿主当前是RESUMED观察者从INITIALIZED追平到RESUMED。如果你在观察者的onCreate里做了依赖onResume之后才有的资源就会出问题。所以观察者的初始化逻辑要写成幂等的、不依赖具体时机的。5.2 内存泄漏的排查思路Lifecycle 本身设计上是为了减少泄漏但用错了照样漏。常见的有两种一种是观察者持有了 Activity 的强引用且没有在onDestroy移除。虽然DESTROYED之后LifecycleRegistry会清空观察者列表但如果你的观察者被别的长生命周期对象持有它本身还是活的。另一种是在onDestroy回调里启动了异步任务任务又持有观察者。这种要靠 LeakCanary 抓抓到之后看引用链通常能定位到是哪个观察者没释放。提示LifecycleRegistry在DESTROYED状态时会调用clear()把所有观察者清掉。所以理论上只要宿主正常销毁观察者都会被释放。真正漏的是“观察者被外部持有”这种情况。5.3 和协程、LiveData 配合时的坑lifecycleScope和repeatOnLifecycle是现在很常用的组合。repeatOnLifecycle会在STARTED时启动协程、STOPPED时取消这个行为依赖 Lifecycle 的事件分发。如果你在onCreate里调用repeatOnLifecycle它会等到ON_START才真正执行块内代码这个“延迟”如果没预期到会以为代码没跑。另一个坑是lifecycleScope.launch默认在DESTROYED时取消但如果你在onDestroy里又launch了一个它可能来不及执行就被取消了。这种场景要用NonCancellable或者干脆别在销毁时启动协程。6. 源码级细节几个容易被忽略的设计6.1 addObserver 时的状态同步addObserver不是简单地把观察者塞进 map它会先计算观察者的初始状态然后调用sync把它追平到宿主当前状态。这个过程中如果宿主已经是DESTROYED观察者会立刻收到ON_DESTROY并被移除。这个设计保证了“注册即同步”不会出现观察者状态和宿主状态长期不一致的情况。6.2 事件分发的顺序保证正向遍历状态前进和反向遍历状态后退用的是同一个 map但遍历方向不同。为什么后退要反向因为状态后退时观察者的回调顺序应该和前进时相反这样才符合“栈式”的生命周期语义。比如前进时 A 先onCreate再 BonCreate后退时应该 B 先onDestroy再 AonDestroy。这个细节在写有依赖关系的多个观察者时很重要。6.3 状态比较用的是 ordinal前面提过State枚举的声明顺序决定了 ordinal。DESTROYED最小RESUMED最大。源码里判断“是否需要前进”就是比较 ordinal。这意味着如果你自己扩展 State虽然不可能因为是枚举顺序错了逻辑就全乱了。理解这一点看源码时那些if (state.ordinal targetState.ordinal)就一目了然了。7. 我在实际项目中的几点体会第一个体会是不要滥用 Lifecycle。有些逻辑其实和生命周期无关硬塞进观察者里反而增加理解成本。比如纯数据的加载用 ViewModel 协程就够了不需要再套一层 LifecycleObserver。第二个体会是自定义 LifecycleOwner 时状态推进要克制。我见过有人在自定义 View 里手动推状态结果推得太频繁观察者被反复回调性能直接崩。状态推进应该对应真实的生命周期节点不要为了触发回调而推状态。第三个体会是面试里问 Lifecycle重点在状态机。能把 State 和 Event 的映射、补发机制、正反向遍历讲清楚基本就到位了。源码里那些FastSafeIterableMap的细节属于加分项不是必答项。最后分享一个小技巧如果你想快速验证一个观察者的回调顺序可以在每个回调里打时间戳日志然后跑一遍页面的完整生命周期把日志按时间排出来和状态机的映射表对一遍哪里不对一目了然。这个方法比看源码快得多尤其适合排查“回调顺序和预期不一致”的问题。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询