资讯动态

Android Lifecycle 实战指南:状态机、协程与自定义 Owner

发布时间:2026/10/1 4:30:35 来源:尧图企业网站定制
最早写 Android 的时候我习惯在 Activity 里手动管理一切onResume 里注册广播onPause 里注销广播onDestroy 里释放资源。刚开始还挺顺可一旦播放器、定位、传感器、埋点上报凑到同一个页面里onStart/onStop 里的代码就跟打补丁一样越堆越多。真正要解决的其实是一个再普通不过的问题——组件怎么知道界面“还活着”Android 官方给出的标准答案就是 Lifecycle 组件一个把 Activity、Fragment 的生命周期抽成状态机让任何对象都能安全订阅的机制。这篇文章是使用篇我会从状态机模型讲起把 DefaultLifecycleObserver、自定义 LifecycleOwner、生命周期感知的协程方案、以及我在实际项目里改造定位组件的完整过程都过一遍最后附上踩坑清单希望能让你在生命周期管理这件事上少走弯路。1. 重新认识 Lifecycle状态机到底在干什么1.1 从手动同步到自动订阅先聊聊我见过的比较典型的“手动管理”代码长什么样。一个视频播放器页面Activity 在 onStart 里调用 player.start()在 onStop 里调用 player.pause()如果播放器自己还持有 Context那 onDestroy 里还得先释放播放器再调 super。你可能会说这不挺简单的吗问题在于业务组件一多每个组件都要在 Activity 里占一个回调位置Activity 变成了一个巨大的“调度中心”。今天加个定位明天加个耳机监听后天加个前台埋点onStart/onStop 越来越长而且稍不留神就会漏掉某个组件的恢复逻辑。Lifecycle 组件解决的是“反向依赖”问题。Activity 不必知道每个业务组件具体要做什么组件自己实现 LifecycleObserver然后某处只需要一句 lifecycle.addObserver(component)之后 Activity 状态变化时观察者就会按顺序收到对应的事件。这有点像把“我在前台/后台”这件事变成广播谁关心谁订阅而不是由 Activity 挨个通知。这种控制反转让 Activity 非常干净业务组件也完全不依赖具体页面类型可以复用在任何实现了 LifecycleOwner 的地方。1.2 State 与 Event搞懂这两个概念成功一半Lifecycle 里有两个核心概念很多人初学时会混一个是 State状态一个是 Event事件。简单说State 是“当前处在哪个生命周期阶段”Event 是“正在发生什么转变”。官方文档那张状态机图建议你有空去看一眼我在这里用一句话概括每个 Event 都会把 State 推到下一个位置。ON_CREATE 触发后状态从 INITIALIZED 进入 CREATEDON_START 触发后状态从 CREATED 进入 STARTEDON_RESUME 触发后状态从 STARTED 进入 RESUMEDON_PAUSE 触发后状态从 RESUMED 退回 STARTEDON_STOP 触发后状态从 STARTED 退回 CREATEDON_DESTROY 触发后状态从 CREATED 进入 DESTROYED完整对应关系可以用下面这张表来看实际使用中我建议以“状态”作为判断依据不要臆测“接下来会发生什么”。当前事件旧状态新状态ON_CREATEINITIALIZEDCREATEDON_STARTCREATEDSTARTEDON_RESUMESTARTEDRESUMEDON_PAUSERESUMEDSTARTEDON_STOPSTARTEDCREATEDON_DESTROYCREATEDDESTROYED为什么强调区分它们因为你在代码里经常要做判断。比如“界面可见时更新 UI”应该判断 currentState.isAtLeast(STARTED)而不是去数“刚才是不是已经 ON_START 了”。State 是快照更适合做条件判断Event 是通知更适合做动作触发。这两种用途一旦搞反代码就会变得很别扭比如有人试图用监听 ON_RESUME 的次数来控制状态结果回退时各种对不上。另一个很容易忽略的点是状态机是有方向的前进和后退事件是完全不同的序列。注册一个 Observer 时如果当前 Owner 已经处于 RESUMED框架会补发 ON_CREATE、ON_START、ON_RESUME 三个事件如果 Owner 正从 RESUMED 往 DESTROYED 退也会按顺序收到 ON_PAUSE、ON_STOP、ON_DESTROY。这一点对于理解后面要讲的“注册时机”非常重要。2. 基础使用让组件自己“听”生命周期2.1 首选 DefaultLifecycleObserver而不是注解反射很多老项目里能看到这样的写法Deprecated(Lifecycle 2.6.0 起已标记废弃) class LegacyLocationObserver : LifecycleObserver { OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun resume() { // 开始定位 } OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun pause() { // 停止定位 } }注解方案在早期确实能用但它是通过反射扫描注解方法的每个 Observer 都要做一次方法解析类一多、启动路径一长这部分开销就会成为不必要的负担。而且注解方法名可以随便写IDE 无法帮你校验参数和事件是否匹配。所以从 Lifecycle 2.2 开始官方推荐直接实现 DefaultLifecycleObserver它把事件定义成接口方法IDE 自动提示没有任何反射开销。class LocationObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // 开始定位 } override fun onStop(owner: LifecycleOwner) { // 停止定位 } }注意这里的方法是有默认实现的你不需要重写所有方法只需要关注自己关心的生命周期阶段。我实际写业务组件时几乎大部分只重写 onStart / onStop 或者 onResume / onPause 这一对因为这两对状态分别代表“后台不可见”和“前台可见”资源开关放在这里最合适。还有一个调试技巧如果你只是想在控制台看生命周期事件可以直接注册一个 LifecycleEventObserver它只有一个 onStateChanged 方法能拿到每个 Event 和当前的 State。lifecycle.addObserver(LifecycleEventObserver { _, event - Log.d(Lifecycle, 事件: $event) })这个写法在排查状态乱序问题时特别顺手我后面讲避坑时会再提到。2.2 注册时机与状态补齐机制addObserver 的调用时机直接影响观察者收到的事件顺序。网上常见的问题是“为什么我在 onCreate 里 addObserver但观察者的 onCreate 没有被调用”答案是如果你在 super.onCreate() 之后注册此时 Activity 的状态已经进入 CREATED而 Observer 被加入后会从当前 Owner 状态向下对齐。Owner 是 CREATED所以会补发 ON_CREATE。但如果你是在 onCreate 之前就 addObserverOwner 还处于 INITIALIZEDON_CREATE 就要等 Activity 真正创建时才会触发。这个“补齐机制”可以类比成新员工入职你入职当天公司已经是周五了HR 会把周一到周五的流程全给你补一遍让你知道现在的进度。补发事件是同步的Observer 加入后回调在 addObserver 调用栈内直接执行。因此不要在 addObserver 函数之后的同一段代码里假设“刚才那个 Observer 还没执行完”它大概率已经同步跑过了。Fragment 里还有一个容易选错的 Owner如果用 Fragment 的 lifecycle那事件跟随 Fragment 整体包括 Fragment 的视图销毁但 Fragment 还在的场景如果操作的是 ViewBinding、RecyclerView 里的列表项 UI更建议用 viewLifecycleOwner因为视图销毁时相关的协程和监听就应该立刻结束而不是等 Fragment 整个销毁。这部分建议多写一步能用 viewLifecycleOwner 的地方不要图省事直接用 Fragment 的 lifecycle。3. 手写 LifecycleRegistry自定义 LifecycleOwner3.1 什么时候需要自定义 OwnerActivity、Fragment、ViewModel 这些官方类默认实现了 LifecycleOwner直接拿 lifecycle 用就行。真正的麻烦出现在“没有一个现成 Owner但你清楚地知道某个对象有自己的生命周期”的场景。我举几个最常见的自定义 Service。Service 有明确的 onCreate / onStartCommand / onDestroy但它没有实现 LifecycleOwner。如果你有一个后台下载器、一个定位服务想让内部组件感知服务启动和销毁就得自己套一层 Owner。全局或页面级业务控制器。比如一个网络请求管理器、一个埋点聚合器它有初始化、启动、停止、销毁这几个阶段但又不能直接继承任何 Android 组件。自定义 UI 容器。有些复杂的组合控件有自己的可见性变化想把它自己的生命周期暴露给内部子组件也会用到自定义 Owner。官方提供了一个 LifecycleService直接在依赖里加 androidx.lifecycle:lifecycle-serviceService 就自带 lifecycle 了这是最简单的方案。但如果你不想引入额外依赖或者你封装的是一个普通 Java/Kotlin 类手动写 LifecycleRegistry 反而更灵活。3.2 一个极简实现LifecycleRegistry 是 Lifecycle 的标准实现创建一个自定义 LifecycleOwner 的最小代码如下class SimpleController : LifecycleOwner { private val registry LifecycleRegistry(this) override val lifecycle: Lifecycle get() registry fun startWork() { // 模拟业务组件启动 registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) registry.handleLifecycleEvent(Lifecycle.Event.ON_START) registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) } fun stopWork() { registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) registry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) } }使用方式就很简单了val controller SimpleController() controller.lifecycle.addObserver(MyComponent()) controller.startWork()这段代码看起来没什么技术含量但它揭示了 Lifecycle 的本质它就是一个“手动推动的状态机”。Activity 的默认实现之所以看起来“自动”是因为框架总会在合适时机调用 handleLifecycleEvent自定义场景下这个推动时机就得你自己保证。我强烈建议你在 Creator 之外不要省略 ON_CREATE 直接发 ON_START状态机虽然能处理但中间漏掉事件会让 Observer 收不到应有的回调后续排查时非常难定位。3.3 事件分发的两个关键细节第一接口方法必须在主线程调用。LifecycleRegistry 本身不是线程安全的它的事件分发和状态读取都默认在主线程。你可以在子线程里触发业务逻辑但最终 handleLifecycleEvent 的调用一定要切回主线程否则偶发并发修改会导致状态错乱甚至崩溃。我记得之前在一个 MVP 架构里网络请求回调在子线程里调用 presenter.onDestroy()结果里面触发 handleLifecycleEvent直接抛了异常从那以后我就在封装层加了一个 Main Thread 判断。第二事件的推动必须按顺序。不要求你把每一个中间态都补齐但至少要符合“一步一步走”的原则。比如从 RESUMED 要回到 CREATED就发 ON_PAUSE、ON_STOP而不是直接发一个 ON_STOP 就完事。Observer 可能同时关注 ON_PAUSE 和 ON_STOP少发任何一个它的内部状态就和你 Owner 的真实状态不一致了。另外如果你的自定义 Owner 是会被反复使用的比如一个对象池里的控制器别想着“销毁后再重新 onCreate”。LifecycleRegistry 进入 DESTROYED 之后重复使用很容易出现状态异常。我更建议销毁后就丢弃实例用一个新的控制器对象干净又安全。4. 协程时代Lifecycle Flow 的完整姿势4.1 lifecycleScope绑定在 Owner 上的作用域Lifecycle 不只是用来回调的Kotlin 协程出来之后它又多了一个重要身份协程作用域的宿主。lifecycleScope 是 LifecycleOwner 的内置扩展它和 ViewModelScope 最大的区别在于取消时机不一样。ViewModelScope 在 ViewModel 销毁时取消lifecycleScope 在 LifecycleOwner 销毁时取消。对 Fragment 来说如果你用的是 viewLifecycleOwner.lifecycleScope那视图销毁协程就结束恢复视图后会重新启动这两个作用域的边界一定要分清。lifecycleScope.launch { // 在这里做一些需要跟随页面销毁而取消的工作 }是不是所有任务都要丢进 lifecycleScope不是。比如一个联网请求页面销毁了但结果可能还有缓存价值那我更倾向于放到 ViewModelScope结果通过状态容器回传lifecycleScope 更适合做 UI 相关的瞬时操作例如监听传感器回调、更新页面进度条、在启动后执行一次任务。用错作用域轻则浪费资源重则状态错乱。4.2 repeatOnLifecycle按需收集后台不空转lifecycleScope 能保证“页面销毁就取消”但它挡不住另一个问题页面退到后台时协程还在跑。比如你在 lifecycleScope.launch 里直接 collect 一个位置信息流用户把 App 切到后台Flow 依然在发射数据collect 依然在处理。表面上没崩实际上电量、流量、CPU 全在白白消耗。正确的做法是用 repeatOnLifecycle它会在 Lifecycle 进入指定状态时开始执行内部代码块低于该状态时自动取消再次回到该状态时重新执行。lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { locationFlow.collect { location - updateUI(location) } } }这里我把状态选在 STARTED是因为只要页面在后台我就完全不想管位置更新。如果你希望页面完全可见并处于交互状态才收集可以选 RESUMED差别在于是否处理“可见但失去焦点”的情况。实际项目里绝大多数 UI 数据收集用 STARTED 就够了。需要提醒的是repeatOnLifecycle 内部代码块在每次重新进入状态时都会重新执行所以不要在里面放“只允许执行一次”的逻辑比如埋点上报、计数自增。这类一次性逻辑应该放在 repeatOnLifecycle 外层或者自己加一个防重标记。我见过有人把一段上报代码塞进 repeatOnLifecycle 里结果每次从后台切回来就重复上报后台数据直接翻倍。4.3 flowWithLifecycle 与 repeatOnLifecycle 怎么选flowWithLifecycle 是 Flow 的扩展函数它在内部也是基于生命周期来实现的但只能作用于单个 FlowlocationFlow .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect { location - updateUI(location) }和 repeatOnLifecycle 相比flowWithLifecycle 更适合你已经有一个现成的 Flow只想在收集链路中间加一个“生命周期阀门”的场景。它的缺点是灵活度差一些因为里面是 callbackFlow 实现如果你需要同时收集多个 Flow或者需要在生命周期恢复后执行一段非 Flow 代码就不如 repeatOnLifecycle 直接。我的习惯是单个 Flow 收集用 flowWithLifecycle多个 Flow 合流或用可重入代码块时用 repeatOnLifecycle。两者并不冲突只是侧重点不同。5. 实战一个不泄漏的 GPS 定位组件5.1 旧写法的问题先看一个常见的“反面教材”。很多项目里的定位代码是这么写的在 Activity 的 onResume 里调定位管理器在 onPause 里移除更新。如果定位组件自己持有 Activity 的 Context问题更大——页面销毁后定位还在跑Activity 无法被回收直接内存泄漏。就算组件持有的是 ApplicationContext定位一直在后台跑也非常耗电而且多开几个页面每个页面各开一套定位互相干扰。我把问题总结成三点一是资源开关散落在页面回调里业务组件完全被动二是页面销毁和定位停止之间没有强关联很容易漏三是多个页面都要做同一套注册注销代码重复。这三个问题本质上都是生命周期管理和业务逻辑没有解耦。5.2 用 Lifecycle 改造改造思路很明确让定位组件自己实现 DefaultLifecycleObserver由外部生命周期去驱动它的开关。组件内部只关心“可见时就启动不可见时就停止”。class LocationTracker( private val context: Context ) : DefaultLifecycleObserver { private val locationManager by lazy { context.getSystemService(Context.LOCATION_SERVICE) as LocationManager } private var started false SuppressLint(MissingPermission) override fun onStart(owner: LifecycleOwner) { if (!started) { locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 5000L, 10f, callback ) started true } } SuppressLint(MissingPermission) override fun onStop(owner: LifecycleOwner) { if (started) { locationManager.removeUpdates(callback) started false } } }在 Activity 里注册就一行class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycle.addObserver(LocationTracker(applicationContext)) } }为什么这里我用 applicationContext 而不是 Activity 本身这个细节很关键LifecycleOwner 销毁后Observer 就不再收到后续事件但 LocationTracker 内部如果持有 Activity 引用它还是会被 Activity 间接持有而这个 Observer 实例又挂在 Activity 的 Lifecycle 上等于互相引用形成泄漏链。用 applicationContext 可以保证即便 Observer 生命周期异常延长也不会把界面 Activity 拖住。这是我在项目里一直坚持的规则凡是可能活过页面的对象一律不要持有界面级 Context。5.3 再进一步把位置放进 StateFlow上面的方案只能做到开关控制页面从后台切回前台时onStart 会再次触发但你想立刻拿到最新位置还得重新等待卫星定位。更好的数据组织方式是让 LocationTracker 内部维护一个 StateFlow外部通过收集这个状态流来响应class LocationTracker(context: Context) : DefaultLifecycleObserver { private val _location MutableStateFlowLocation?(null) val location: StateFlowLocation? _location.asStateFlow() override fun onStart(owner: LifecycleOwner) { // 启动定位并在回调里执行 _location.value location } override fun onStop(owner: LifecycleOwner) { // 停止定位 } }页面侧用前面提到的 repeatOnLifecycle 去收集lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { tracker.location.collect { location - // 更新地图、标记位置 } } }这样一来组件本身不碰任何 UI数据流过 StateFlow 对外暴露页面是否收集完全由页面自己决定。即使定位组件在后台短暂继续运行页面也不会收到无用的 UI 刷新回调。这种“生命周期控制组件开关 Flow 控制数据驱动”的组合是我目前在生产环境里比较满意的一套模板。6. 避坑清单我踩过的 Lifecycle 的坑6.1 高频问题与解决方案下面这些坑几乎都是我在真实项目里遇到过或者帮同事排查过的。整理成了一张速查表方便你对照。症状根因解决办法Observer 收不到 onCreate注册时机太晚状态补齐只补当前及后续事件确认注册时 Owner 的状态晚注册时不要依赖已过事件后台切回前台repeatOnLifecycle 代码块重复执行代码块本身就是每次进入状态都会重新执行一次性逻辑放外层或加执行标记页面销毁后协程还在跑用了 applicationScope / 全局 scope改用 lifecycleScopeFragment 内视图相关用 viewLifecycleOwner定位、传感器在页面销毁后仍在工作业务组件没有感知销毁实现 DefaultLifecycleObserver由生命周期驱动开关自定义 LifecycleOwner 在子线程推动状态LifecycleRegistry 非线程安全事件统一发到主线程处理同一个 Observer 被重复 addObserver注册代码在重建时执行了多次用唯一 Key 管理或在 addObserver 前 removeObserver预置的一个常用检查项如果你在 Fragment 里直接用了 lifecycle 而不是 viewLifecycleOwner然后发现 Fragment 处于后台但视图已经销毁时很多 UI 相关协程还在跑那八成就是这个原因。别小看这个差异ViewBinding 在视图销毁后访问 Fragment 里的控件是会出问题的。还有一个比较隐蔽的坑ProcessLifecycleOwner。它提供的是整个 App 的前后台状态而不是单一页面状态。有些开发者以为 ProcessLifecycleOwner 可以替代页面级别的 owner用它来驱动 UI 更新结果页面状态根本没被正确同步。ProcessLifecycleOwner 适合做全局资源管理、前台埋点、网络状态监听这类应用级任务不适合做页面级任务。这两者一定要区分开。6.2 调试生命周期状态的手段生命周期问题最让人头疼的是“时好时坏”后台切回来偶尔崩页面销毁后偶发异常。这类问题不建议靠肉眼盯代码直接在开发环境里打日志更快。我常用的一个做法是注册一个全局观察者lifecycle.addObserver(LifecycleEventObserver { _, event - Log.d(LifecycleDebug, 当前 Owner 收到: $event) })在 Activity 基类或者 Fragment 基类里统一打一份日志对比正常页面和异常页面的状态序列几秒钟就能看出是哪一步没对齐。另一个技巧是自定义 LifecycleOwner 时在 handleLifecycleEvent 的调用点也打日志同时带上 Thread这样能顺带发现子线程调用的隐患。如果日志层面对不上我建议直接看 LifecycleRegistry 源码里的 sync 和 moveToState 方法。源码不复杂核心就是把当前状态和目标状态之间的事件补齐或回退理解了这一段很多“为什么这个 Observer 先收到 onStop 又收到 onResume”的疑问就迎刃而解。最后再说一点个人体会。我最初接触 Lifecycle 时总觉得它就是“官方帮你封装了生命周期回调方便你少写两行代码”。实际用下来它真正的价值是逼你把“依赖生命周期的东西”和“不依赖生命周期的东西”分开。定位、播放、传感器、协程、UI 刷新哪些该跟着页面走哪些不该跟着页面走只要在代码里把这条边界画清楚了很多线上内存泄漏和状态错乱问题根本不会发生。每次新页面需要接入一堆组件时我都会先问自己一句这个组件到底该订阅哪个 owner 的生命周期想清楚这一点比写代码本身更有意义。

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

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

免费获取报价 →
↑