【Android面试】ViewModel & LiveData & EventBus专题

文章目录

  • 一、ViewModel
    • [1. ViewModel 配置变更不重建的底层原理是什么?ViewModelStore、ViewModelStoreOwner、ViewModelProvider 三者关系与源码流程?**](#1. ViewModel 配置变更不重建的底层原理是什么?ViewModelStore、ViewModelStoreOwner、ViewModelProvider 三者关系与源码流程?**)
    • [2. ViewModel 为何能避免内存泄漏?与 ViewModel 生命周期绑定的核心机制是什么?onCleared() 触发时机与源码逻辑?](#2. ViewModel 为何能避免内存泄漏?与 ViewModel 生命周期绑定的核心机制是什么?onCleared() 触发时机与源码逻辑?)
    • [3. 自定义 ViewModelProvider.Factory 的核心作用?如何实现带参构造的 ViewModel 注入?源码中如何解析 Factory?](#3. 自定义 ViewModelProvider.Factory 的核心作用?如何实现带参构造的 ViewModel 注入?源码中如何解析 Factory?)
    • [4. ViewModel 共享数据(Fragment 间/Activity 与 Fragment)的实现原理?为何能做到数据同步?](#4. ViewModel 共享数据(Fragment 间/Activity 与 Fragment)的实现原理?为何能做到数据同步?)
    • [5. ViewModel 在配置变更(旋转)与正常销毁两种场景下,生命周期回调的源码差异是什么?ViewModelStore 何时被清空?**](#5. ViewModel 在配置变更(旋转)与正常销毁两种场景下,生命周期回调的源码差异是什么?ViewModelStore 何时被清空?**)
    • [6. ViewModel 中直接使用协程(Coroutine)有什么风险?正确的协程作用域(ViewModel.viewModelScope)源码原理是什么?](#6. ViewModel 中直接使用协程(Coroutine)有什么风险?正确的协程作用域(ViewModel.viewModelScope)源码原理是什么?)
    • [7. 当 Activity 嵌套多个 Fragment 时,不同层级的 ViewModel 作用域如何划分?如何实现 Activity 级、Fragment 级共享的隔离?**](#7. 当 Activity 嵌套多个 Fragment 时,不同层级的 ViewModel 作用域如何划分?如何实现 Activity 级、Fragment 级共享的隔离?**)
  • 二、LiveData
    • [1. LiveData 生命周期感知的源码实现?LifecycleBoundObserver 如何绑定生命周期、感知状态变化?**](#1. LiveData 生命周期感知的源码实现?LifecycleBoundObserver 如何绑定生命周期、感知状态变化?**)
    • [2. LiveData 粘性事件产生的根本原因(源码层面)?SingleLiveEvent / UnPeekLiveData 解决粘性的核心原理?**](#2. LiveData 粘性事件产生的根本原因(源码层面)?SingleLiveEvent / UnPeekLiveData 解决粘性的核心原理?**)
    • [3. postValue 与 setValue 源码差异?postValue 为何可能丢失数据?底层 Handler 机制如何处理?**](#3. postValue 与 setValue 源码差异?postValue 为何可能丢失数据?底层 Handler 机制如何处理?**)
    • [4. LiveData 数据倒灌(重复回调)的场景与源码原因?如何从设计上避免?**](#4. LiveData 数据倒灌(重复回调)的场景与源码原因?如何从设计上避免?**)
    • [5. LiveData 与 Kotlin Flow 本质区别?生命周期感知、粘性、背压、线程切换的差异与选型?](#5. LiveData 与 Kotlin Flow 本质区别?生命周期感知、粘性、背压、线程切换的差异与选型?)
    • [6. LiveData 的 observeForever 方法为什么会导致内存泄漏?它与普通 observe 在源码实现上的核心区别?](#6. LiveData 的 observeForever 方法为什么会导致内存泄漏?它与普通 observe 在源码实现上的核心区别?)
    • [7. 多个 Observer 订阅同一个 LiveData 时,数据分发的先后顺序由什么决定?源码中如何维护 Observer 列表?](#7. 多个 Observer 订阅同一个 LiveData 时,数据分发的先后顺序由什么决定?源码中如何维护 Observer 列表?)
    • [8.LiveData 的 dispatchingValue 与 considerNotify 两个核心方法的作用是什么?如何实现数据的延迟分发?](#8.LiveData 的 dispatchingValue 与 considerNotify 两个核心方法的作用是什么?如何实现数据的延迟分发?)
    • [9. MediatorLiveData 的源码原理是什么?如何实现多个 LiveData 源的合并与监听?为何能避免重复触发?**](#9. MediatorLiveData 的源码原理是什么?如何实现多个 LiveData 源的合并与监听?为何能避免重复触发?**)
  • 三、EventBus(4题,核心+深度)
    • [1. 定义及应用场景 **](#1. 定义及应用场景 **)
    • [2. EventBus 事件分发完整源码流程(register → post → invoke → unregister)?线程模式如何切换?](#2. EventBus 事件分发完整源码流程(register → post → invoke → unregister)?线程模式如何切换?)
    • [3. EventBus 内存泄漏场景与原因?为何必须手动 unregister?与 LiveData 生命周期感知的本质差异?**](#3. EventBus 内存泄漏场景与原因?为何必须手动 unregister?与 LiveData 生命周期感知的本质差异?**)
    • [4. 大型项目中 EventBus 弊端(耦合、可读性、调试困难)?替代方案(LiveData、Flow、接口回调)选型依据?](#4. 大型项目中 EventBus 弊端(耦合、可读性、调试困难)?替代方案(LiveData、Flow、接口回调)选型依据?)

一、ViewModel

1. ViewModel 配置变更不重建的底层原理是什么?ViewModelStore、ViewModelStoreOwner、ViewModelProvider 三者关系与源码流程?**

配置变更时,Activity 会重建,但系统会保留其持有的 ViewModelStore,新重建的 Activity 会复用同一个 ViewModelStore,从而取出旧的 ViewModel 实例,实现数据不丢失。

  • 三者核心关系与源码流程如下:
  • 关系
    ViewModelStoreOwner (Activity/Fragment 实现该接口)是 ViewModelStore 的持有者;ViewModelStore 内部以 HashMap 存储 ViewModel 实例(key 为 ViewModel 类名,value 为实例);ViewModelProvider 负责从 ViewModelStore 中获取已存在的 ViewModel,若不存在则通过 ViewModelProvider.Factory 创建实例并存入 ViewModelStore。
  • 源码流程
    调用 ViewModelProvider.get() 方法 → 先从 ViewModelStore 的 HashMap 中根据模型类查找实例 → 查找不到则调用 Factory.create() 方法创建 ViewModel 实例 → 将创建的实例存入 ViewModelStore → 返回实例供页面使用。

2. ViewModel 为何能避免内存泄漏?与 ViewModel 生命周期绑定的核心机制是什么?onCleared() 触发时机与源码逻辑?

  • 避免内存泄漏的核心原因
    ViewModel 的生命周期独立于 Activity/Fragment 的视图生命周期,且 ViewModel 本身不持有 View、Activity 等具有短生命周期的引用(规范使用下),不会因视图销毁而导致引用无法释放,从而避免内存泄漏。
  • 生命周期绑定机制
    ViewModel 由 ViewModelStore 管理,而 ViewModelStore 由 ViewModelStoreOwner(Activity/Fragment)持有ViewModel 的生命周期与 ViewModelStoreOwner 的"有效生命周期"(Activity 从 onCreate 到 finish,Fragment 从 onAttach 到 detach 且不再复用)绑定,而非视图的销毁重建周期。
  • onCleared() 触发时机与源码逻辑
    仅当 ViewModelStoreOwner 真正销毁(Activity finish、Fragment 彻底 detach 且不再使用)时触发,配置变更(屏幕旋转)不会触发。源码逻辑:系统在 ViewModelStoreOwner 销毁时,调用 ViewModelStore.clear() 方法,该方法会遍历 HashMap 中所有 ViewModel,依次调用其 onCleared() 方法,随后清空 HashMap,释放 ViewModel 实例。

3. 自定义 ViewModelProvider.Factory 的核心作用?如何实现带参构造的 ViewModel 注入?源码中如何解析 Factory?

  • 核心作用:
    系统默认的 ViewModelProvider.Factory(NewInstanceFactory)仅能创建无参构造的 ViewModel 实例;自定义 Factory 的核心作用是创建带参构造的 ViewModel(如需要注入 Repository、UseCase 等依赖的 ViewModel),支撑依赖注入(如 Hilt、Dagger)与 ViewModel 的结合使用。
  • 带参 ViewModel 注入实现
    自定义 Factory 实现 ViewModelProvider.Factory 接口,重写 create(Class modelClass) 方法,在方法内部通过反射或手动 new 的方式,传入 ViewModel 所需的参数,创建实例并返回。
  • 源码解析 Factory 流程:
    ViewModelProvider 初始化时会传入 Factory;调用 get() 方法获取 ViewModel 时,会调用 Factory 的 create() 方法;若未传入自定义 Factory,会默认使用 NewInstanceFactory,通过反射调用 ViewModel 的无参构造创建实例;若传入自定义 Factory,则执行自定义的 create() 逻辑,完成带参实例的创建。

4. ViewModel 共享数据(Fragment 间/Activity 与 Fragment)的实现原理?为何能做到数据同步?

  • 实现原理:
    核心是让多个 Fragment(或 Activity 与 Fragment)获取同一个 ViewModelStore 中的 ViewModel 实例。具体方式:Fragment 中通过 ViewModelProvider(requireActivity())[ViewModel::class.java] 获取 ViewModel,此时获取的是 Activity 作为ViewModelStoreOwner 所持有的 ViewModelStore 中的实例;多个 Fragment 若都通过这种方式获取,得到的是同一个 ViewModel 实例。
  • 数据同步原因:
    多个页面共享的是同一个 ViewModel 实例,ViewModel 中的数据(如 LiveData、普通变量)是实例级别的,当其中一个页面修改 ViewModel 中的数据时,其他页面持有同一个实例,自然能感知到数据变化,实现数据同步。

5. ViewModel 在配置变更(旋转)与正常销毁两种场景下,生命周期回调的源码差异是什么?ViewModelStore 何时被清空?**

  • 生命周期回调差异:
    配置变更(屏幕旋转) :Activity/Fragment 会销毁重建,但 ViewModelStore 会被系统(ActivityClientRecord)保留,ViewModel 不会被销毁,也不会调用 onCleared() 方法;新重建的页面会复用原有的 ViewModelStore 和 ViewModel 实例。
    正常销毁(Activity finish、Fragment 彻底 detach):ViewModelStoreOwner 会被销毁,系统会调用 ViewModelStore.clear() 方法,遍历调用所有 ViewModel 的 onCleared() 方法,随后清空 ViewModelStore 中的所有实例,ViewModel 被销毁。
  • ViewModelStore 清空时机
    仅当 ViewModelStoreOwner 真正销毁(Activity.isFinishing() == true、Fragment 彻底 detach 且不再复用)时,系统才会调用 ViewModelStore.clear() 清空 Store;配置变更时不会清空。

6. ViewModel 中直接使用协程(Coroutine)有什么风险?正确的协程作用域(ViewModel.viewModelScope)源码原理是什么?

  • 直接使用协程的风险
    若在 ViewModel 中手动创建 CoroutineScope(如 GlobalScope、自定义 Scope),未绑定 ViewModel 生命周期,当 ViewModel 被销毁(onCleared() 触发)时,协程若未完成,会继续执行,且协程可能持有 Activity/Fragment 的引用,导致内存泄漏;同时,无用的协程继续执行会浪费资源。
  • viewModelScope 源码原理
    viewModelScope 是 ViewModel 的扩展属性,内部基于 SupervisorJob() 和 Dispatchers.Main 构建 CoroutineScope;ViewModel 内部会监听自身的生命周期,当 onCleared() 触发时,会自动调用 viewModelScope.cancel() 方法,取消所有未完成的协程;从而保证协程与 ViewModel 生命周期同步,避免泄漏和资源浪费。

7. 当 Activity 嵌套多个 Fragment 时,不同层级的 ViewModel 作用域如何划分?如何实现 Activity 级、Fragment 级共享的隔离?**

作用域划分:ViewModel 的作用域由其所属的 ViewModelStoreOwner 决定,核心划分3类:

  • Activity 级作用域:ViewModelStoreOwner 为 Activity,通过 ViewModelProvider(requireActivity()) 获取,供 Activity 及所有子 Fragment 共享。
  • Fragment 自身作用域:ViewModelStoreOwner 为当前 Fragment,通过 ViewModelProvider(this) 或 viewModels() 方法获取,仅当前 Fragment 可使用,子 Fragment 无法共享。
  • 父 Fragment 级作用域:ViewModelStoreOwner 为父 Fragment,通过 ViewModelProvider(parentFragment!!) 获取,供父 Fragment 及其子 Fragment 共享,与 Activity 级、其他 Fragment 级隔离。
  • 隔离实现:不同 ViewModelStoreOwner 持有各自独立的 ViewModelStore,不同 Store 中的 ViewModel 实例互不干扰;通过选择不同的 ViewModelStoreOwner(Activity、当前 Fragment、父 Fragment)获取 ViewModel,即可实现不同层级的共享与隔离。

二、LiveData

1. LiveData 生命周期感知的源码实现?LifecycleBoundObserver 如何绑定生命周期、感知状态变化?**

  • 生命周期感知核心源码实现:
    LiveData 的 observe() 方法接收 LifecycleOwner(Activity/Fragment)和 Observer,内部会将 Observer 包装成 LifecycleBoundObserver 实例,LifecycleBoundObserver 同时实现了 LifecycleObserver 和 Observer 接口,通过绑定 LifecycleOwner 的生命周期,实现感知。
  • LifecycleBoundObserver 绑定与感知逻辑:
  • 绑定:在 observe() 方法中,将 LifecycleBoundObserver 注册到 LifecycleOwner 的 Lifecycle 中(lifecycle.addObserver(this))。
  • 感知状态变化 :LifecycleBoundObserver 重写了 onStateChanged() 方法,监听 Lifecycle 的状态变化;当 Lifecycle 状态为 STARTED 或 RESUMED 时,标记为活跃状态,LiveData 会分发数据给该 Observer;当 Lifecycle 状态为 DESTROYED 时,自动调用 LiveData.removeObserver(),移除当前 Observer,避免内存泄漏。

2. LiveData 粘性事件产生的根本原因(源码层面)?SingleLiveEvent / UnPeekLiveData 解决粘性的核心原理?**

  • 定义
    当一个观察者(Observer)订阅了一个 LiveData 实例时,它会自动接收到最近一次设置的值(即"粘住"了最后一次值),即使这个值是在观察者订阅之前设置的
  • 粘性事件根本原因(源码层面)
    LiveData 内部维护了 mData(当前数据)和 mVersion(数据版本号 )两个变量;当新的 Observer 注册时,LiveData 会调用 considerNotify() 方法,对比 Observer 的版本号与 LiveData 的 mVersion ,若 Observer 的版本号小于 mVersion,会立刻将当前 mData 分发给该 Observer;即"先发送数据,后注册 Observer",Observer 仍能收到之前发送的数据,形成粘性事件。
  • 解决原理
  • SingleLiveEvent :继承 MutableLiveData,内部添加一个 boolean 类型的标志位(如 mPending),标记数据是否已被消费;当 Observer 收到数据并处理后,将标志位置为 false;下次注册 Observer 时,若标志位为 false,不分发历史数据,仅分发新数据,实现单次消费,解决粘性。
  • UnPeekLiveData :为每个 Observer 维护独立的版本号,而非共用 LiveData 的 mVersion;当 Observer 注册时,将其版本号初始化为与 LiveData 的 mVersion 一致;只有当 LiveData 的 mVersion 大于该 Observer 的版本号时,才分发数据;从而阻止"订阅前的旧数据"分发,彻底解决粘性。

3. postValue 与 setValue 源码差异?postValue 为何可能丢失数据?底层 Handler 机制如何处理?**

  • 源码差异:
    setValue :仅能在主线程 调用,直接给 mData 赋值,同时 incrementVersion()(递增 mVersion),随后调用 dispatchingValue() 方法分发数据,同步执行,无延迟
    postValue :可在子线程调用,内部通过 ArchTaskExecutor.getMainThreadExecutor()(底层是 Handler)将赋值操作切换到主线程;先将数据存入 mPendingData(临时变量),再发送 Handler 消息;主线程收到消息后,调用 setValue(mPendingData),完成数据赋值与分发,异步执行。
  • postValue 丢失数据原因
    短时间内连续多次调用 postValue,会多次修改 mPendingData 的值 ,但 Handler 消息队列中仅会保留最后一次消息(前序消息会被覆盖);主线程执行消息时,仅会处理最后一次 mPendingData 的值,导致前序 postValue 的数据被丢失。
  • 底层 Handler 处理逻辑
  • postValue 内部通过 Handler 发送一个 Runnable 任务到主线程消息队列;该任务的核心逻辑是调用 setValue(),将 mPendingData 的值赋给 mData 并分发;由于 Handler 消息是串行执行的,且多次 postValue 会覆盖 mPendingData,因此仅最后一次数据会被分发。

4. LiveData 数据倒灌(重复回调)的场景与源码原因?如何从设计上避免?**

  • 数据倒灌场景:
    页面重建(如屏幕旋转、Fragment 重新 attach)时,Observer 重新注册到 LiveData,会再次收到之前已经分发过的旧数据,导致回调重复执行(如重复请求接口、重复更新 UI)。
  • 源码原因:
    粘性事件根源一致,LiveData 会保存当前 mData 和 mVersion;页面重建后,新的 Observer 注册时,其版本号初始化为 0,小于 LiveData 的 mVersion,considerNotify() 方法会判断版本差,触发数据分发,导致旧数据重复回调。
  • 设计上避免方案
  • 事件类数据(如点击事件、接口请求结果):使用 SingleLiveEvent、UnPeekLiveData,实现数据单次消费,避免重复回调。
  • 状态类数据(如 UI 显示状态、列表数据):区分"状态初始化"与"状态更新",在 Observer 中添加判断(如判断数据是否为初始值、是否与当前 UI 状态一致),避免重复处理。
  • 替代方案:使用 Kotlin StateFlow,其为冷数据流,仅在订阅后才会分发数据,天然避免数据倒灌。

5. LiveData 与 Kotlin Flow 本质区别?生命周期感知、粘性、背压、线程切换的差异与选型?

  • 本质区别:
    LiveData 是"生命周期感知的状态持有者 ",核心用于持有 UI 状态,被动分发数据;Kotlin Flow 是"异步数据流",核心用于处理异步操作、数据流转,主动发射数据,支持丰富的操作符。
  • 核心差异对比:
    生命周期感知 :LiveData 天然支持,无需额外处理;Flow 本身不支持,需通过 repeatOnLifecycle() 或 lifecycleScope 绑定生命周期,否则可能导致内存泄漏。
    粘性 :LiveData 天然有粘性(保存最新数据,新订阅者会收到旧数据);Flow 是冷流,无粘性(订阅前的发射数据不会被接收);SharedFlow 可配置粘性,但默认无粘性。
    背压 :LiveData 无背压机制,若数据分发速度快于 Observer 处理速度,会导致数据堆积;Flow 支持背压(如 buffer、conflate 等操作符),可灵活处理数据生产与消费的速度差。
    线程切换 :LiveData 仅能在主线程分发数据,线程切换需在数据源头处理;Flow 可通过 flowOn() 自由切换发射线程,通过 collect() 切换收集线程,操作更灵活。
    选型建议:简单 UI 状态(如开关状态、文本显示)、无需复杂异步操作,用 LiveData;复杂异步操作(如多接口联动、数据转换)、需要背压、多源数据合并,用 Flow/StateFlow/SharedFlow;页面内状态用 StateFlow,跨页面通信用 SharedFlow。

6. LiveData 的 observeForever 方法为什么会导致内存泄漏?它与普通 observe 在源码实现上的核心区别?

  • 内存泄漏原因
    observeForever() 方法不接收 LifecycleOwner 参数,内部会将 Observer 包装成 AlwaysActiveObserver 实例,该实例不会绑定任何生命周期,始终处于活跃状态;当页面(Activity/Fragment)销毁时,AlwaysActiveObserver 依然持有页面的引用,且 LiveData 不会自动移除该 Observer,导致页面无法被 GC 回收,产生内存泄漏。
  • 与普通 observe 的源码区别
    普通 observe:包装成 LifecycleBoundObserver,绑定 LifecycleOwner,监听生命周期,DESTROYED 时自动移除 Observer,无泄漏风险。
    observeForever:包装成 AlwaysActiveObserver,不绑定任何 Lifecycle,始终处于活跃状态,LiveData 不会自动移除该 Observer,必须手动调用 removeObserver(),否则必泄漏。

7. 多个 Observer 订阅同一个 LiveData 时,数据分发的先后顺序由什么决定?源码中如何维护 Observer 列表?

  • 分发顺序:
    多个 Observer 订阅同一个 LiveData 时,数据分发的先后顺序由"注册顺序"决定,先注册的 Observer 先收到数据,后注册的后收到。
  • 源码中 Observer 列表维护:
    LiveData 内部使用 SafeIterableMap(一个线程安全的有序映射表)存储 Observer 及其包装类(LifecycleBoundObserver/AlwaysActiveObserver);SafeIterableMap 会按 Observer 的注册顺序存储,遍历分发数据时,会按照存储顺序依次调用每个 Observer 的 onChanged() 方法,保证分发顺序与注册顺序一致;同时,SafeIterableMap 支持并发修改(如一边分发一边移除 Observer),避免并发异常。

8.LiveData 的 dispatchingValue 与 considerNotify 两个核心方法的作用是什么?如何实现数据的延迟分发?

  • 核心方法作用:
    dispatchingValue控制数据分发的流程 ,防止递归重入(如 Observer 中修改 LiveData 数据,导致再次触发分发);内部通过 mDispatchingValue 布尔变量标记是否正在分发,若正在分发则将数据加入 mPendingData,待当前分发完成后再处理,实现数据的有序分发。
    considerNotify判断是否需要向 Observer 分发数据 ,是数据分发的核心判断逻辑;主要做两件事:① 判断 Observer 对应的生命周期 是否处于活跃状态(STARTED/RESUMED);② 对比 Observer 的版本号与 LiveData 的 mVersion,若 Observer 版本号小于 LiveData 版本号,才会调用 Observer.onChanged() 分发数据。
  • 延迟分发实现
    当 LiveData 调用 setValue/postValue 时,若此时 Observer 处于非活跃状态(如页面后台),considerNotify() 会判断生命周期不活跃,不立即分发数据;当页面重新回到活跃状态(如从后台切回前台),LifecycleBoundObserver 会感知到状态变化,触发 LiveData 再次调用 dispatchingValue() 和 considerNotify(),此时生命周期活跃,完成数据分发,实现延迟分发。

9. MediatorLiveData 的源码原理是什么?如何实现多个 LiveData 源的合并与监听?为何能避免重复触发?**

  • 源码原理:
  • MediatorLiveData 继承自 MutableLiveData,内部维护了一个 HashMap(mSources ),用于存储添加的 LiveData 源(Source)及其对应的 Observer;每个添加的 LiveData 源都会被包装成 Source 实例,Source 内部持有一个 Observer,用于监听该 LiveData 源的数据变化,当数据变化时,会调用 MediatorLiveData 的 onChanged() 方法,将数据转发给 MediatorLiveData 的观察者。
  • 多源合并与监听实现:
    通过 addSource() 方法添加 多个 LiveData 源;addSource() 方法会创建 Source 实例,将该实例的 Observer 注册到对应的 LiveData 源上;当任一 LiveData 源的数据发生变化时,其对应的 Observer 会被触发,进而调用 MediatorLiveData 的 onChanged(),MediatorLiveData 再将数据分发给自己的观察者,实现多源合并与监听。
  • 避免重复触发原因
    MediatorLiveData 会通过版本号控制数据分发,每个 Source 对应的 LiveData 源都有自己的版本号,MediatorLiveData 会记录每个源的最新版本;当某个 LiveData 源的数据未发生变化(版本号未递增)时,不会触发转发,从而避免重复触发 MediatorLiveData 的观察者。

三、EventBus(4题,核心+深度)

1. 定义及应用场景 **

  • 定义
    EventBus 是一个基于发布-订阅模式事件总线库 ,主要用于在 Android 应用中进行组件间通信(解耦)。它由 GreenRobot 开发并维护,广泛用于早期 Android 架构中,以简化不同组件(如 Activity、Fragment、Service 等)之间的通信。
  • 应用场景
    组件间通信,全局事件广播(用户登录状态变更、全局网络状态变化),解耦业务逻辑

2. EventBus 事件分发完整源码流程(register → post → invoke → unregister)?线程模式如何切换?

  • 完整分发流程:
    ① register(注册):调用 EventBus.register(subscriber) → 查找订阅者的所有订阅方法(通过索引或反射)→ 将订阅方法信息(事件类型、线程模式、优先级等)存入缓存(如 subscriberMethodFinder、subscriptionsByEventType)→ 建立"事件类型 → 订阅方法列表"的映射。
    ② post(发送事件):调用 EventBus.post(event) → 将事件加入事件队列(PendingPostQueue)→ 调用 postSingleEvent() 方法,根据事件类型查找对应的订阅方法列表 → 遍历订阅方法列表,调用 postToSubscription() 方法,准备分发事件。
    ③ invoke(调用订阅方法):postToSubscription() 方法根据订阅方法的线程模式,切换到对应线程 → 通过反射调用订阅者的订阅方法,将事件传入,完成事件分发。
    ④ unregister(解绑):调用 EventBus.unregister(subscriber) → 从缓存中移除该订阅者的所有订阅方法信息 → 解除"事件类型 → 订阅方法"的映射,避免内存泄漏。
  • 线程模式切换逻辑
    POSTING:同线程分发,直接在 post() 调用的线程中反射调用订阅方法,无线程切换。
    MAIN:主线程分发,通过 Handler 发送消息到主线程,在主线程中调用订阅方法。
    BACKGROUND:子线程分发,若当前线程不是主线程,直接调用;若是主线程,将任务加入后台线程池,在子线程中执行。
    ASYNC:独立新线程分发,无论当前线程是什么,都创建一个新的独立线程,调用订阅方法,执行完成后销毁线程。

3. EventBus 内存泄漏场景与原因?为何必须手动 unregister?与 LiveData 生命周期感知的本质差异?**

  • 内存泄漏场景与原因:
    最常见场景是 Activity/Fragment 注册 EventBus 后,未在 onDestroy() 中调用 unregister();原因:EventBus 内部会通过缓存(subscriptionsByEventType)强引用订阅者(Activity/Fragment),当 Activity/Fragment 销毁时,EventBus 的强引用依然存在,导致 Activity/Fragment 无法被 GC 回收,产生内存泄漏。
  • 必须手动 unregister 的原因:
    EventBus 本身没有生命周期感知能力,无法判断订阅者(如 Activity)是否已经销毁,无法自动移除对订阅者的强引用;只有通过手动调用 unregister(),才能从 EventBus 的缓存中移除订阅者信息,释放强引用,避免内存泄漏。
  • 与 LiveData 生命周期感知的本质差异:
    LiveData 依赖 Lifecycle 组件,通过 LifecycleBoundObserver 绑定 LifecycleOwner 的生命周期,能自动感知页面销毁,自动移除 Observer,从机制上杜绝泄漏;而 EventBus 无任何生命周期感知机制,完全依赖开发者手动管理注册与解绑,若遗漏则必然导致泄漏。

4. 大型项目中 EventBus 弊端(耦合、可读性、调试困难)?替代方案(LiveData、Flow、接口回调)选型依据?

  • 大型项目中 EventBus 核心弊端:
    耦合度高:事件是全局的,发送方与接收方无直接关联,一个事件的修改可能影响多个接收方,代码耦合度高,维护成本高。
    可读性差:无法快速定位一个事件的发送方和接收方,代码逻辑分散,后期排查问题时,难以追踪事件流转路径。
    调试困难:事件分发是异步的,若出现异常,难以定位异常发生在发送端还是接收端,调试效率低。
    易遗漏解绑:大型项目中页面多,容易遗漏 unregister(),导致内存泄漏,排查难度大。
  • 替代方案选型依据
    接口回调:适用于"一对一"的简单通信(如 Activity 与 Fragment 通信、回调接口返回结果),耦合度低、可读性强,适合简单场景。
    LiveData :适用于页面内(Activity/Fragment)的状态管理、数据同步,天然生命周期安全,无泄漏风险,适合 UI 相关的状态通信。
    **Kotlin Flow(**StateFlow/SharedFlow):适用于复杂异步通信、多源数据合并、跨页面通信;StateFlow 适合页面内状态管理,SharedFlow 适合跨页面全局事件通信,支持背压,灵活性高,是大型项目的首选。
    补充:跨进程通信不适合用 EventBus,优先使用 BroadcastReceiver、AIDL 等方案。
相关推荐
Meepo_haha2 小时前
Maven Spring框架依赖包
java·spring·maven
迷藏4942 小时前
# 发散创新:用Rust构建高性能分布式账本节点——从零实现共识算法与链上数据存储
java·python·rust·共识算法·分布式账本
深念Y2 小时前
乐播投屏电视广告逆向分析实录:从Activity追踪到放弃
android
Flittly2 小时前
【SpringAIAlibaba新手村系列】(5)Prompt 提示词基础与多种消息类型
java·笔记·spring·ai·springboot
Moment2 小时前
如果想转 AI 全栈?推荐你学一下 Langchain!
前端·后端·面试
言之。2 小时前
Apache ZooKeeper 核心技术全解(面试+实战版)
zookeeper·面试·apache
handsomethefirst2 小时前
【算法与数据结构】【面试经典150题】【题36-题40】
数据结构·算法·面试
庞轩px2 小时前
面试回答第十五问:类加载
jvm·面试·职场和发展·常量池·类加载·字节码·klass