
Lifecycle 的使用与实现原理
Lifecycle 的使用
在 Activity 或 Fragment 中,可以通过 getLifecycle() 方法获取对应的 Lifecycle 对象,然后添加 LifecycleObserver 接口的实现类来观察生命周期变化。例如,定义一个类实现 LifecycleObserver 并注解相应的生命周期方法,如下:
1 | import androidx.lifecycle.Lifecycle |
然后在 Activity 的 onCreate 方法中添加观察者:
1 | class MainActivity : AppCompatActivity() { |
这样,MyObserver 类中的 onStart 方法就会在 Activity 进入 ON_START 生命周期阶段时自动被调用,同样可以注解其他生命周期事件对应的方法来响应不同阶段的变化,方便进行一些与生命周期相关的资源管理、业务逻辑处理等操作。
Lifecycle 的实现原理
LifecycleOwner 接口:LifecycleOwner 是一个接口,像 Activity、Fragment 等组件都实现了这个接口,用于表示拥有 Lifecycle 的对象,其核心方法就是 getLifecycle(),返回对应的 Lifecycle 实例。通过这个接口,Lifecycle 相关机制可以统一从不同的组件获取到生命周期信息。
LifecycleRegistry 类:这是 Lifecycle 实现的核心类,它内部维护了当前生命周期的状态(如 CREATED、STARTED、RESUMED 等状态),并且通过一个列表来管理所有的 LifecycleObserver。当组件的生命周期发生变化时(比如 Activity 的 onStart 方法被系统调用),LifecycleRegistry 会根据当前状态变化去通知所有注册的观察者,它会检查每个观察者上标注的生命周期事件注解,匹配当前发生的事件,然后调用对应的方法,实现了生命周期变化的监听和分发机制。
事件分发机制:采用了一种基于状态机的思想,每个生命周期事件的发生会导致 LifecycleRegistry 内部状态的转移,并且在状态转移过程中,按照既定的规则(例如从 STARTED 到 RESUMED 状态时,会依次通知处于这两个状态变化之间有对应注解的观察者方法)去触发观察者的相应处理逻辑,保证生命周期变化能被准确地传递给各个观察者并执行对应的操作。
LiveData 粘性事件源码分析
粘性事件现象
LiveData 是一种可观察的数据持有类,粘性事件是指当一个新的观察者订阅 LiveData 时,如果 LiveData 已经有了数据(之前被设置过值),那么这个新观察者会立即收到这个旧的数据,就好像这个数据 “粘” 在了 LiveData 上,等待新的观察者获取一样。
源码分析
版本号机制:
LiveData 内部通过版本号来管理数据状态和观察者的同步。它有一个 mVersion 属性表示自身的数据版本,每次调用 setValue 或者 postValue(用于更新数据)时,mVersion 会递增。同时,每个观察者也有一个对应的 version 属性(在 ObserverWrapper 类中,它是对实际观察者的一个包装类),当观察者订阅 LiveData 时,会将 LiveData 的当前版本号赋值给观察者的 version。
数据分发判断:
在 LiveData 的 considerNotify 方法(用于判断是否通知观察者)中,会对比 LiveData 的 mVersion 和观察者的 version,如果 LiveData 的版本号大于观察者的版本号,就会触发通知,将数据传递给观察者。所以当新观察者订阅时,如果 LiveData 已经更新过数据(版本号大于 0),新观察者的初始版本号为 0,满足 mVersion > version 的条件,就会收到之前的数据,实现了粘性事件的效果。
设计目的:
这样设计的目的是为了保证新订阅的观察者能获取到最新的数据状态,在很多场景下是合理的,比如配置信息的 LiveData,新的界面组件订阅时应该立即获取到当前最新的配置内容,不过在某些只期望获取后续新数据的场景下可能就需要处理粘性问题了。
Jetpack 中的状态机是如何管理生命周期
状态机概述
在 Jetpack 中,以 Lifecycle 为例,它类似一个状态机来管理生命周期。状态机有不同的状态(如上述提到的 CREATED、STARTED、RESUMED 等),并且定义了不同状态之间的转换规则以及对应的事件触发机制。
生命周期状态转换与管理
状态定义:
Lifecycle 定义了多个明确的状态来对应组件(如 Activity、Fragment)不同的生命周期阶段,比如 INITIALIZED 是初始未启动状态,随着组件的创建、启动、恢复等操作,状态逐步转变为 CREATED(创建完成但未可见)、STARTED(开始可见)、RESUMED(完全可交互处于前台)等状态,这些状态之间是有顺序且有条件转换的。
事件驱动转换:
通过各种生命周期事件来驱动状态的转换,例如 onCreate 方法被调用对应着从 INITIALIZED 状态向 CREATED 状态的转变,这个转变过程由 LifecycleRegistry 等相关机制来协调处理。当状态发生转换时,会按照预先设定的逻辑通知注册的 LifecycleObserver,不同的观察者可以根据关注的状态变化来执行相应的操作,比如在 STARTED 状态时开始加载网络数据等,通过这种状态机式的管理,清晰地把控了组件生命周期各个阶段的逻辑处理以及不同阶段之间的流转顺序。
状态回退与异常处理:
在一些特殊情况,比如系统内存回收导致 Activity 重建等场景下,状态机也能处理好状态的回退和重新恢复等操作,根据实际的生命周期回调重新调整状态,并正确通知观察者,确保整个生命周期管理的完整性和健壮性。
hook 实现 LiveData 非粘性功能
Hook 基本思路
Hook(钩子)的基本思想是在不修改原有代码逻辑的基础上,通过拦截、替换等方式改变其运行时的行为。对于 LiveData 实现非粘性功能,就是要干预它原本的粘性数据分发机制,让新的观察者订阅时不会收到之前的数据。
具体实现步骤
基于反射实现 Hook
1 | import androidx.lifecycle.LiveData; |
在实际使用中,可以在创建 LiveData 对象后,调用上述 hook 方法来改变其粘性数据分发行为,例如:
1 | LiveData<String> liveData = new MutableLiveData<>(); |
LiveData 递归调用源码是如何做容错的
递归调用问题场景
在某些复杂的使用场景下,可能会出现 LiveData 的递归调用情况,比如在观察者的回调方法中又去更新了同一个 LiveData 的值,这可能导致无限循环调用,最终引发栈溢出等异常情况。
源码中的容错机制
dispatchingValue 方法中的标记处理: LiveData 在内部的 dispatchingValue 方法(用于分发数据给观察者)中有一个标记变量(类似布尔型的标志位)来记录当前是否正在进行数据分发。当开始分发数据时,会先检查这个标记,如果已经处于分发状态,就不会再次进入分发逻辑,避免因为递归调用等情况导致的重复、无限循环的分发操作,从流程上进行了第一层的拦截和容错。
观察者状态管理: LiveData 对观察者的状态也有相应的管理机制,在 ObserverWrapper 类(包装实际观察者)中,有关于观察者活跃性等状态的记录,当检测到一些异常的递归调用可能影响到观察者状态或者数据分发的合理性时,会根据观察者的当前状态(如是否处于活跃状态等)来决定是否继续执行后续的数据分发操作,避免不合理的数据传递和无限循环调用对整个系统造成的影响,通过这种多方面的状态判断和流程控制来实现递归调用场景下的容错处理,保障 LiveData 机制的稳定运行。
LiveDataBus 封装实现
基本思路
LiveDataBus 的目的是借助 LiveData 的可观察特性来实现类似事件总线的功能,让不同的组件(如 Activity、Fragment、ViewModel 等)之间可以方便地进行消息传递和事件通知,类似于传统的 EventBus 框架,但基于 LiveData 的机制来实现。
1 | import androidx.lifecycle.LiveData; |
在上述代码中:
- 单例模式: 通过 getInstance 方法实现单例模式,保证整个应用中只有一个 LiveDataBus 实例,方便统一管理消息总线。
- 消息通道管理: 使用 with 方法根据给定的 key 创建或者获取对应的 MutableLiveData 对象,不同的 key 可以看作是不同的消息通道,用于区分不同类型的消息传递。
- 消息发送: 通过 post 方法,按照指定的 key 将相应的值发送出去,这样订阅了对应 key 的 MutableLiveData 的组件就能收到消息并进行相应处理,实现了组件间基于 LiveData 机制的消息传递功能,类似于传统的事件总线效果。
LiveData 使用与实现原理分析
LiveData 的使用
创建 LiveData 对象:可以创建 MutableLiveData(可变的 LiveData,能在外部修改数据)对象,例如
1 | MutableLiveData<String> liveData = new MutableLiveData<>(); |
这里创建了一个存储字符串类型数据的 LiveData 对象。
更新数据:通过 setValue(必须在主线程使用)或者 postValue(可以在任何线程使用,内部会切换到主线程更新数据)方法来更新 LiveData 中的数据,如 liveData.postValue("Hello");
就更新了 LiveData 存储的字符串值。
观察数据:在 Activity、Fragment、ViewModel 等组件中,使用 observe 方法来订阅 LiveData,以便在数据发生变化时收到通知,例如在 Activity 的 onCreate 方法中:
1 | liveData.observe(this, new Observer<String>() { |
实现原理
数据持有与可观察性: LiveData 内部持有数据,并通过观察者模式实现可观察性。它维护了一个观察者列表(通过 mObservers 字段存储,是一个 SafeIterableMap 类型的集合),当数据更新时,会遍历这个列表,通知所有注册的观察者。
生命周期感知: 与 Lifecycle 紧密结合,LiveData 能够感知到与之关联的组件(如 Activity)的生命周期状态。在 observe 方法中,会将观察者和对应的 Lifecycle 进行绑定,当组件处于活跃状态(如 RESUMED 状态的 Activity)时,才会真正接收到 LiveData 的数据更新通知,避免在组件处于不可见或者不活跃状态时进行不必要的数据传递和 UI 更新操作,节省资源并保证数据传递的合理性,这一特性通过与 Lifecycle 组件的交互以及内部对生命周期状态的判断机制来实现。
线程切换机制: 如前面提到的 postValue 方法,它利用了 Android 的主线程调度机制(比如通过 Handler 等相关方式),将在其他线程更新数据的请求切换到主线程来执行 setValue 操作,确保了数据更新以及后续 UI 相关操作都在主线程安全地进行,符合 Android 中 UI 操作的线程要求。
LiveData的生命周期是怎么监听的?
LiveData会持有可被观察的数据。 并且同时它也是一种可感知生命周期的组件,意味着该组件重视其他app组件的生命周期,如Activity、Fragment,该组件能确保,仅仅在Activity\Fragment等组件都处于活跃的生命周期状态的时候,才去更新app组件。
1 | public void observe(super T> observer) { LifecycleOwner owner, Observer<? |
可以看到其实LiveData就是借助了Lifecycle感知生命周期的。将LifecycleBoundObserver wrapper添加为观察者,当有生命周期变化时将会执行:onStateChanged
1 | class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver { |
在activeStateChanged中如果当前改变的状态为活跃状态(shouldBeActive():true
),则会调用到considerNotify方法通知数据更新:
1 | private void considerNotify(ObserverWrapper observer) { |
数据在每次修改时,都会进行mVersion++,因此可以使用mVersion判断数据是否发生修改,如果未发生变化则不会发起回调。
1 | //postValue也会最终调用到setValue |
所以,LiveData的生命周期是通过Lifecycle监听的,同时LiveData会维护一个mVersion作为数据的版本号。当数据有修改时,才会进行通知回调。