首页 / 资讯中心 / 文章详情

Android Lifecycle源码拆解:状态机与事件分发机制

Android Lifecycle源码拆解:状态机与事件分发机制 ★ FEATURED ARTICLE
1. 从一次“幽灵回调”说起为什么源码篇才是绕不开的坎先说一件我自己的真事。有一年我负责维护一个在线上跑了三个大版本的老项目里面有一个自定义的LocationTracker它在Activity.onStart()里注册监听在onStop()里注销。听起来很规矩对吧但线上时不时有用户反馈App退到后台之后悬浮窗里的定位坐标还在跳。查了半天最后发现某个版本的营销弹窗 Activity 在onStop()之后又调了一次startLocation()导致监听器被注册了两次而onStop()里的注销逻辑只注销了一次。这种问题用眼睛根本看不出来只能在源码层面搞懂 Lifecycle 的状态流转才能设计出“怎么调用都不会漏”的组件。Lifecycle这个组件很多人处于“会用”的阶段——写个OnLifecycleEvent注解方法或者实现DefaultLifecycleObserver在回调里打个日志就完事了。真正到了线上疑难问题比如“为什么我的 Observer 少收到一次回调”“为什么状态降级的时候事件顺序是乱的”“为什么子线程里更新状态会崩”没有源码功底的人基本是懵的。这篇文章专门走源码路线把LifecycleRegistry的状态机结构、事件分发流程、线程处理和重入逻辑全部拆开揉碎。适合已经写过 Lifecycle 相关代码、但总感觉差点意思的 Android 开发者也适合准备做框架层设计的进阶选手。源码篇的意义不在于背几行代码而在于当你设计自己的组件或者排查诡异 Bug时能像看表盘一样看清每一根指针是怎么动的而不是只能看着时间干瞪眼。2. Lifecycle核心设计的源码骨架Observer、Owner、Registry三件套整个androidx.lifecycle包的设计抽象下来就三个角色LifecycleObserver观察者、LifecycleOwner被观察者、LifecycleRegistry中转调度中心。这很像一个订阅通知系统但它的核心难点在于事件是状态驱动的而不是简单的回调触发。2.1 LifecycleObserver只有声明没有方法的顶层接口从 1.0 版本到现在LifecycleObserver一直是个空接口没有任何抽象方法。它的作用只有一个标记“这个类可以接收生命周期事件”。真正的回调方法要么靠注解反射要么靠接口实现。如果你拿到早期源码会看到这样的结构// androidx.lifecycle.LifecycleObserver public interface LifecycleObserver { }而注解时代的核心是OnLifecycleEvent和反射调用的ReflectiveGenericLifecycleCallbacks。当一个类标注了OnLifecycleEvent(Lifecycle.Event.ON_RESUME)注册的时候会用反射找到这些方法包装成MethodCllbacks进行调用。到了 2.x 版本官方引入了DefaultLifecycleObserver这是一个带所有默认空实现的接口public interface DefaultLifecycleObserver extends FullLifecycleObserver { default void onCreate(NonNull LifecycleOwner owner) {} default void onStart(NonNull LifecycleOwner owner) {} default void onResume(NonNull LifecycleOwner owner) {} default void onPause(NonNull LifecycleOwner owner) {} default void onStop(NonNull LifecycleOwner owner) {} default void onDestroy(NonNull LifecycleOwner owner) {} }这里有个很多人没注意到的设计意图FullLifecycleObserver是包私有接口外部正常写法都会去实现DefaultLifecycleObserver。为什么因为用接口的方式可以在编译期确定方法集合比注解反射更快更安全同时省掉了反射生成的方法表。我在实际项目里用DefaultLifecycleObserver替换注解方式之后不仅代码更清晰启动耗时里反射扫描的耗时也明显降下来了虽然数值不大但模块多了之后积少成多。2.2 LifecycleOwner一个getLifecycle()方法背后的契约LifecycleOwner的定义非常简单public interface LifecycleOwner { NonNull Lifecycle getLifecycle(); }就这么一个方法。它的奥妙在于任何类只要实现了它并返回一个可用的Lifecycle对象就被纳入到了生命周期管理体系中。而这背后的实现ComponentActivity和Fragment用的是同一个LifecycleRegistry。从源码上看ComponentActivity早期是通过ReportFragment无 UI 的隐藏 Fragment来感知生命周期事件的。在Activity的onCreate等回调里ReportFragment会通过LifecycleRegistry.handleLifecycleEvent()把事件投递进去。后来改成了直接由ComponentActivity自身分发但设计模型没变。如果你自定义一个LifecycleOwner比如自己的View或Presenter要实现生命周期感知只要实现接口并内部维护一个LifecycleRegistry即可。这里有个关键点getLifecycle()永远不能返回 null而且每个实例最好对应唯一的LifecycleRegistry。我见过有人在自定义View里每次 new 了一个新的LifecycleRegistry返回导致注册的观察者全部散落在各个新状态机上事件永远分发不到正确的地方。2.3 LifecycleRegistry单机版的事件总线还是状态机LifecycleRegistry是整个生命周期管理的发动机。很多人把它理解成“事件总线”我觉得不完全准确更贴切的称呼是带事件补发的状态机。看它的核心字段就明白了public class LifecycleRegistry extends Lifecycle { private int mAddingObserverCounter 0; private boolean mHandlingEvent false; private boolean mNewEventOccurred false; private final ReentrantLock mEnforceMainThread new ReentrantLock(); private final MapLifecycleObserver, ObserverWithState mObserverMap new LinkedHashMap(); private int mLifecycleOwnerHashCode 0; private State mState; // 当前状态 }我来逐个解释这些字段的用途。mObserverMap保存所有观察者以及它们各自上次接收事件后的状态mState代表当前 owner 的状态mHandlingEvent表示当前是不是正在向观察者分发事件mNewEventOccurred记录在分发过程中是否有新事件产生锁和mAddingObserverCounter用来处理线程安全与注册重入。这种设计的核心原因是观察者并不是每个都从头开始接收事件。比如一个 Observer 在Activity已经是STARTED状态时才注册它并不会收到ON_CREATE和ON_START而只会收到之后的事件。如果你直接来一个ON_RESUME中间缺少的转换就没法补上。所以 Lifecycle 用“状态对比”来分发而不是“收到什么就发什么”这是源码理解的分水岭。3. 状态机的运转逻辑从handleLifecycleEvent到sync()的源码级走读这一章是全文的核心。我会把LifecycleRegistry从收到一个事件到逐个通知所有观察者中间经过了哪些判断和循环一步步带大家走完。3.1 State与Event为什么需要两套枚举先看两个官方枚举public enum Event { ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY, ON_ANY } public enum State { DESTROYED, INITIALIZED, CREATED, STARTED, RESUMED }Event描述“发生了什么”State描述“当前处于什么阶段”。两者之间有一个映射关系比如ON_PAUSE事件的目标状态就是STARTED因为onPause之后 Activity 就处于 STARTED 状态。下面是官方源码里的对应方法static State eventToState(NonNull Lifecycle.Event event) { switch (event) { case ON_CREATE: case ON_STOP: return State.CREATED; case ON_START: case ON_PAUSE: return State.STARTED; case ON_RESUME: return State.RESUMED; case ON_DESTROY: return State.DESTROYED; case ON_ANY: throw new IllegalArgumentException(ON_ANY cannot be converter to a state); } throw new IllegalArgumentException(Unexpected event value: event); }这看起来是简单的 switch但里面藏着一个大坑同一个目标状态可以由两个不同的事件产生。比如ON_CREATE和ON_STOP都能把状态变成CREATED但前者是“升级”后者是“降级”。状态机在分发事件时必须判断当前状态和目标状态的大小关系才能决定补发哪些中间事件。官方还给了一张状态迁移表我这里整理成更容易理解的形式当前状态触发事件新状态补发的中间事件INITIALIZEDON_CREATECREATED无直接升级CREATEDON_STARTSTARTED无STARTEDON_RESUMERESUMED无RESUMEDON_PAUSESTARTED无STARTEDON_STOPCREATEDON_PAUSE先降级到 STARTED 的上一层实际是从 RESUMED 开始降级时才会出现见下文CREATEDON_DESTROYDESTROYEDON_STOP关键就在降级过程。Activity 从 RESUMED 一路走到 DESTROYED正常回调顺序是onPause - onStop - onDestroy对应的事件就是ON_PAUSE - ON_STOP - ON_DESTROY。如果观察者注册时的“锚点”状态比较高比如已经收到了ON_RESUME那么当 owner 直接收到ON_DESTROY且观察者当前还是RESUMED时状态机必须补发ON_PAUSE和ON_STOP然后才发ON_DESTROY。这就是源码中“状态降级时从最上层的状态开始往下发”的由来。3.2 关键源码拆解moveToState与sync的执行流程当 Activity 生命周期变化时调用的是handleLifecycleEventpublic void handleLifecycleEvent(NonNull Lifecycle.Event event) { State next eventToState(event); moveToState(next); }moveToState做两件事更新mState然后调用sync()。private void moveToState(State next) { if (mState next) { return; // 状态相同直接跳过 } mState next; if (mHandlingEvent) { mNewEventOccurred true; return; } sync(); }注意看这个代码逻辑。如果此时正在分发事件mHandlingEvent true那么新的状态变化并不会立刻处理而是把mNewEventOccurred置为 true等当前这一轮分发结束后再重新同步。这个机制是为了防止在回调中修改状态导致循环分发。比如一个观察者的onStop回调里又调用了onStop如果没有保护就会死循环。sync()的完整流程我不逐行贴但可以画重点遍历mObserverMap里的每个ObserverWithState。比较观察者当前状态和 owner 状态的大小isAtLeast类似方法。如果观察者状态低于 owner 状态则向上补发事件。如果观察者状态高于 owner 状态则向下补发事件。在遍历过程中如果发生了mNewEventOccurred跳出循环并从头部重新开始因为事件顺序变了。这里注意顺序问题。向上补发升级时是从观察者当前状态开始往上一级一级发向下补发降级时是从观察者当前状态开始往下一级一级发。代码里while循环每次都判断直到两个状态一致。3.3 version增量与控制反转Observer怎么追平状态ObserverWithState是一个内部静态类它保存了观察者与当前状态static class ObserverWithState { final LifecycleObserver lifecycleObserver; State mState; ObserverWithState(LifecycleObserver lifecycleObserver, State initialState) { this.lifecycleObserver lifecycleObserver; this.mState initialState; } }当一个新 Observer 注册时addObserver会把它放入mObserverMap初始状态设为DESTROYED。然后调用sync()进行状态追平。也就是说无论你在哪个时机注册它都会从DESTROYED状态开始通过补发事件逐步升到你期望的状态。这里有一个很关键的细节如果 owner 当前已经是STARTED那么新注册的 Observer 会先收到ON_CREATE再收到ON_START跟从头开始经历一样。这个设计让观察者永远认为自己是从头开始经历整个生命周期的不会因为注册时机的不同少掉前半程事件。唯一的例外是 owner 已经DESTROYED此时新 Observer 连注册都是不准的会直接拒绝。源码中addObserver还会处理一个边界情况观察者自己可能在dispatchEvent的回调里调用removeObserver把自己移除此时mObserverMap就会被修改所以源码里用版本号和索引来避免ConcurrentModificationException。这块代码虽然不多但设计得非常精妙。4. 进阶场景的源码实践Service、ProcessLifecycleOwner与自定义Owner理解了基本状态机之后接下来看几个高频进阶场景。这些都是我在项目里实际用过的有些还是踩过坑之后才真正看懂的。4.1 LifecycleService让Service拥有生命周期LifecycleService是androidx.lifecycle包里的一个类继承自普通 Service内部直接维护了一个LifecycleRegistrypublic class LifecycleService extends Service implements LifecycleOwner { private final LifecycleRegistry mRegistry new LifecycleRegistry(this); Override public void onCreate() { mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); super.onCreate(); } Override public void onStart(Intent intent, int startId) { mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); super.onStart(intent, startId); } Override public void onDestroy() { mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); super.onDestroy(); } }注意它没有ON_RESUME和ON_PAUSE因为 Service 并没有前台/后台的语义。你如果自定义 Service 想实现 LifecycleOwner也不必强行模拟这两个事件照搬 LifecycleService 的事件序列是最稳的。我在实际项目中使用 LifecycleService 做后台定位上传任务配合一个实现DefaultLifecycleObserver的定位调度器可以做到Service 启动时自动开始定位销毁时自动停止完全不用在 Service 里写开关代码。代码减少不说还不会出现忘记清理导致的内存泄漏。4.2 ProcessLifecycleOwner整个进程的生命周期为什么这么难ProcessLifecycleOwner可以监听整个应用进程的前后台切换。它内部用LifecycleRegistry加一个空的ReportFragment来感知 Activity 生命周期。但有一点很多人会误解它并不会收到每个 Activity 的独立事件而是聚合整个进程的状态。例如第一个 ActivityonResume进程状态变成RESUMED最后一个 ActivityonStop进程状态变成CREATED不是STARTED也不是DESTROYED。官方注释明确说ON_DESTROY永远不会被分发。我在一个需要全局管理网络长连接的项目里就用它做前后台切换判断。但第一次接入时踩了一个坑进程内有两个 Activity用户在 A 页面点进 B 页面A 的onStop会导致短暂出现进程状态为STARTED然后 B 的onStart又被触发接着onResume又回到RESUMED。如果你的观察者在状态变为STARTED的瞬间就停掉了网络请求会造成不必要的抖动。解决方案是观察者里不要过度响应CREATED和STARTED之间的小波动重活都挂在ON_RESUME和ON_STOP上。源码中ProcessLifecycleOwner的实现用了一个延迟消息机制专门用来“等一等”确保多个 Activity 切换导致的中间状态不会乱发。这也解释了官方这个组件为什么代码量不大但是很难自己实现。4.3 自定义LifecycleOwner的最佳实践如果你的自定义组件需要配一个 Owner最稳妥的写法就是照抄官方模式public class MyCustomHost implements LifecycleOwner { private final LifecycleRegistry mLifecycleRegistry new LifecycleRegistry(this); public void start() { mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME); } public void stop() { mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE); mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP); } NonNull Override public Lifecycle getLifecycle() { return mLifecycleRegistry; } }有两个经验分享第一ON_DESTROY要不要发如果你的对象是一次性的、结束后就不再复用那就发并且发完之后mLifecycleRegistry应该被置空。但注意getLifecycle()不能返回 null所以如果你不打算再用了最好把整个 Owner 对象遗弃而不是把mLifecycleRegistry变成 null。第二线程安全。LifecycleRegistry.handleLifecycleEvent内部有锁但是addObserver和removeObserver也会锁。如果你在主线程注册观察者、后台线程更新状态会出现锁竞争。我的建议是所有生命周期事件统一在主线程分发因为很多 Android 组件比如 Handler、View本身就要求主线程。源码的enforceMainThreadIfNeeded方法用ReentrantLock强制了主线程约束所以你在源码里看到锁不要慌它一方面保证了线程安全另一方面也把调用线程卡住了。5. 源码级问题排查与避坑指南这部分是我实际排错的经验总结每个场景我都尽量给出现象、根因和解决思路。5.1 场景一Observer收不到事件多半是状态没追上现象用addObserver注册了一个DefaultLifecycleObserver但在onDestroy时没有回调。排查思路先去getLifecycle().getCurrentState()看当前状态。如果已经是DESTROYED理论上注册后要么直接收到ON_DESTROY要么直接不会收到任何回调。这里有个关键细节源码在addObserver的时候会判断当前状态是否为DESTROYED如果是会直接拒绝或者分发ON_DESTROY给那些还没结束的观察者。我遇到过的情况是自定义 Owner 在stop()中只发了ON_STOP没有发ON_DESTROY然后宿主对象被回收。观察者守着ON_DESTROY做资源清理就永远等不到。最后我在设计层面约定凡是进入终态的对象必须发ON_DESTROY否则观察者无法确定生命周期真的结束了。5.2 场景二子线程dispatch的崩溃和卡顿现象在子线程里调用handleLifecycleEvent偶发崩溃或者卡顿。根因LifecycleRegistry内部有主线程校验在 debug 包下直接抛出异常。就算你关闭了校验子线程分发事件会导致观察者回调发生在非主线程而很多 UI 操作必须在主线程执行于是出现CalledFromWrongThreadException。解决所有handleLifecycleEvent调用尽量通过主线程 Handler 投递。如果实在要在子线程处理耗时逻辑再回调生命周期请先回主线程再调用。源码里那句enforceMainThreadIfNeeded的注释写得明明白白这不是 bug是特性。5.3 场景三removeObserver后的残留引用现象内存泄漏LeakCanary 报出来是某个 View 被 LifecycleRegistry 持有了。排查LifecycleRegistry.mObserverMap是一个LinkedHashMap它强引用着每个 Observer。如果这个 Observer 又持有 View 或 Context你只调用了getLifecycle().removeObserver(observer)但没把 observer 本身置空它依然会留在 Map 里。源码没有自动“空引用清理”机制所以必须在onDestroy生命周期事件里主动调用removeObserver并做好空判断。我自己的习惯是在onDestroy里先lifecycle.removeObserver(this)再释放其他大对象。这样即使 Owner 被 GC 了Map 里的引用也断得干净。5.4 设计生命周期感知组件的黄金法则源码看完之后我总结出三条做生命周期感知组件的黄金法则适用于任何自定义组件第一注册和释放必须配对。你在onStart注册了监听就必须在onStop注销在onCreate创建了大对象就在onDestroy置空。第二不要把重活放在构造函数。生命周期组件应该等ON_START或ON_RESUME再初始化真正需要的资源因为 owner 不一定会走到这些状态。第三所有回调要保证幂等。由于状态机可能因为重入而重复分发某个事件观察者方法本身要能承受重复调用。源码里mNewEventOccurred机制虽然避免死循环但没法保证业务方法的幂等性。当年那款定位悬浮窗的 bug我在源码层面搞清楚LifecycleRegistry之后把监听逻辑重构成了生命周期感知组件注册和注销都绑定在同一个事件对上。从那之后这个模块再也没出过类似的内存泄漏或重复注册问题。把源码吃透收益真的是一劳永逸。最后再分享一个小技巧阅读 Lifecycle 源码时不要只盯着LifecycleRegistry一个文件最好把LifecycleOwner、LifecycleService、ProcessLifecycleOwner和ViewTreeLifecycleOwnerAndroidX 里 View 与 Lifecycle 的桥接一起看。这几个类合在一起才是完整的生命周期管理全景图。看懂了它们你再回来看你的业务代码会突然发现很多之前“说不清为什么”的写法原来都是状态机在背后替你兜底。
阅读完成 · 觉得有帮助?
咨询建站