当前位置: 首页 > backend >正文

Android学习总结之类LiveData与ViewModel关系篇

1. ViewModel 和 LiveData 的强依赖关系

ViewModel 和 LiveData 虽非强依赖,但在 Android 架构中常紧密协作,这基于它们的设计理念和优势互补:

  • 数据与 UI 分离:ViewModel 的主要职责是存储和管理与 UI 相关的数据,而 LiveData 是一种可观察的数据持有者类。ViewModel 可以持有 LiveData 对象,将数据的变化通过 LiveData 通知给 UI 层,这样 UI 层只需要观察 LiveData 的变化,而不需要直接操作数据,实现了数据和 UI 的分离。
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;public class MyViewModel extends ViewModel {private MutableLiveData<String> data = new MutableLiveData<>();public LiveData<String> getData() {return data;}public void setData(String newData) {data.setValue(newData);}
}

ViewModel 持有 LiveData 对象,将数据的存储和更新逻辑封装在 ViewModel 中,而 UI 层只需要观察 LiveData 的变化,通过调用 getData() 方法获取 LiveData 对象并进行观察,实现了数据和 UI 的分离。 

  • 生命周期感知:LiveData 具有生命周期感知能力,它可以自动感知 Activity 或 Fragment 的生命周期状态。当 Activity 或 Fragment 处于活跃状态(STARTED 或 RESUMED)时,LiveData 会将数据的变化通知给观察者;当 Activity 或 Fragment 处于非活跃状态时,LiveData 会暂停通知,直到再次进入活跃状态。ViewModel 的生命周期与 Activity 或 Fragment 的生命周期绑定,当 Activity 或 Fragment 销毁时,ViewModel 才会被销毁。将 LiveData 放在 ViewModel 中,可以确保 LiveData 的生命周期与 ViewModel 一致,避免内存泄漏。
// LiveData.java
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {assertMainThread("observe");if (owner.getLifecycle().getCurrentState() == DESTROYED) {// ignorereturn;}LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);if (existing != null && !existing.isAttachedTo(owner)) {throw new IllegalArgumentException("Cannot add the same observer"+ " with different lifecycles");}if (existing != null) {return;}owner.getLifecycle().addObserver(wrapper);
}

在 observe 方法中,会创建一个 LifecycleBoundObserver 对象,它实现了 LifecycleEventObserver 接口,会监听 LifecycleOwner 的生命周期变化。当 LifecycleOwner 的状态变为 DESTROYED 时,LifecycleBoundObserver 会自动移除观察者,避免内存泄漏。

而 ViewModel 的生命周期与 Activity 或 Fragment 绑定,当 Activity 或 Fragment 销毁时,ViewModelStore 会调用 ViewModel 的 clear 方法:

// ViewModel.java
protected void onCleared() {
}// ViewModelStore.java
public final void clear() {for (ViewModel vm : mMap.values()) {vm.clear();}mMap.clear();
}

将 LiveData 放在 ViewModel 中,可以确保 LiveData 的生命周期与 ViewModel 一致,因为 ViewModel 会在 Activity 或 Fragment 销毁时才被销毁,保证了数据的一致性和内存的安全。

2. 把 LiveData 放在普通类里面是否可行

可以把 LiveData 放在普通类里,但会失去很多架构优势:

  • 生命周期管理缺失:普通类没有生命周期感知能力,若 LiveData 置于其中,无法自动响应 Activity 或 Fragment 的生命周期变化。例如,在 Activity 销毁后,LiveData 可能仍在发送数据更新,这会导致内存泄漏或出现空指针异常。
// LiveData.java
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {// ...owner.getLifecycle().addObserver(wrapper);// ...
}

如果将 LiveData 放在普通类中,由于普通类无法提供 Lifecycle 对象,LiveData 就无法感知生命周期的变化,当 Activity 或 Fragment 销毁时,LiveData 无法自动移除观察者,可能会导致内存泄漏。 

  • 数据持久化与配置变更处理不便ViewModel 在配置变更时可以保留数据,这是因为 ViewModelStore 会在 Activity 或 Fragment 重建时保留 ViewModel 实例。而普通类没有这样的机制,在配置变更时,普通类的实例会被销毁和重建,数据也会丢失。

3. LiveData 是否需要依赖 ViewModel 来取消订阅

LiveData 不需要依赖 ViewModel 来取消订阅,它自身具备生命周期感知能力,能自动处理订阅和取消订阅:

  • 自动取消订阅:当观察者的生命周期处于 DESTROYED 状态时,LiveData 会自动移除该观察者,避免内存泄漏。例如,当 Activity 或 Fragment 销毁时,LiveData 会自动取消与之关联的所有观察者的订阅。
  • 不依赖 ViewModel:即使 LiveData 不放在 ViewModel 中,只要观察者是通过 LifecycleOwner(如 Activity 或 Fragment)注册的,LiveData 就能自动管理订阅的生命周期。
// LiveData.java#LifecycleBoundObserver
@Override
public void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();if (currentState == DESTROYED) {removeObserver(mObserver);return;}Lifecycle.State prevState = null;while (prevState != currentState) {prevState = currentState;activeStateChanged(shouldBeActive());currentState = mOwner.getLifecycle().getCurrentState();}
}

在 onStateChanged 方法中,如果 LifecycleOwner 的状态为 DESTROYED,会调用 removeObserver 方法移除观察者,实现自动取消订阅。 

4. 一个 ViewModel 在 Fragment 销毁时执行哪些方法

在 Fragment 销毁时,ViewModel 本身不会执行特定的方法,但它会经历生命周期的结束阶段:

  • ViewModel 生命周期结束:当 Fragment 销毁时,ViewModel 会被保留,直到其宿主 Activity 销毁。如果 Fragment 是 Activity 的一部分,只有当 Activity 销毁时,ViewModel 才会被销毁。在这个过程中,ViewModel 没有像 Fragment 那样有明确的销毁方法回调,但它会被垃圾回收机制回收其占用的内存。
  • 数据清理(可选):如果在 ViewModel 中有一些需要手动清理的资源(如网络请求、数据库连接等),可以在 ViewModel 中实现 onCleared() 方法,在该方法中进行资源的释放操作。例如:
// ViewModel.java
public final void clear() {mCleared = true;if (mBagOfTags != null) {synchronized (mBagOfTags) {for (Object value : mBagOfTags.values()) {// see comment for the similar call in setTagIfAbsentcloseWithRuntimeException(value);}}}onCleared();
}

clear 方法会标记 ViewModel 为已清理状态,然后调用 onCleared 方法。onCleared 方法是一个空方法,我们可以在 ViewModel 的子类中重写该方法,进行资源的清理操作,例如取消网络请求、关闭数据库连接等。 

5. 解释一下 LiveData 以及它是如何感知生命周期的

解释 LiveData

LiveData 是一种可观察的数据持有者类,它具有以下特点:

  • 生命周期感知:LiveData 可以感知 Activity、Fragment 或 Service 的生命周期,确保只有在生命周期处于活跃状态时才会通知观察者数据的变化。
  • 自动取消订阅:当观察者的生命周期处于 DESTROYED 状态时,LiveData 会自动移除该观察者,避免内存泄漏。
  • 数据一致性:LiveData 会在数据发生变化时通知所有活跃的观察者,确保 UI 始终显示最新的数据。
// LiveData.java
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =new SafeIterableMap<>();protected void setValue(T value) {assertMainThread("setValue");mVersion++;mData = value;dispatchingValue(null);
}private void dispatchingValue(@Nullable ObserverWrapper initiator) {if (mDispatchingValue) {mDispatchInvalidated = true;return;}mDispatchingValue = true;do {mDispatchInvalidated = false;if (initiator != null) {considerNotify(initiator);initiator = null;} else {for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;
}private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;//noinspection uncheckedobserver.mObserver.onChanged((T) mData);
}

当调用 setValue 方法时,会更新数据版本号,然后调用 dispatchingValue 方法通知所有观察者。在 considerNotify 方法中,会检查观察者的活跃状态和数据版本号,确保只有活跃的观察者且数据版本号有更新时才会通知观察者。 

如何感知生命周期

LiveData 通过 LifecycleBoundObserver 来感知 LifecycleOwner 的生命周期变化。LifecycleBoundObserver 实现了 LifecycleEventObserver 接口,当 LifecycleOwner 的状态发生变化时,onStateChanged 方法会被调用:

// LiveData.java#LifecycleBoundObserver
@Override
public void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();if (currentState == DESTROYED) {removeObserver(mObserver);return;}Lifecycle.State prevState = null;while (prevState != currentState) {prevState = currentState;activeStateChanged(shouldBeActive());currentState = mOwner.getLifecycle().getCurrentState();}
}

在 onStateChanged 方法中,会根据 LifecycleOwner 的当前状态进行相应的处理。如果状态为 DESTROYED,会移除观察者;如果状态发生变化,会调用 activeStateChanged 方法更新观察者的活跃状态。

面试扩展追问:

1. ViewModel 和 LiveData 的强依赖关系

真题 1(字节跳动)
"ViewModel 如何保证在 Activity 重建时数据不丢失?请结合源码解释。"

解析
ViewModel 的数据持久化机制基于 ViewModelStore 和 NonConfigurationInstances

  1. Activity 重建流程
    当屏幕旋转等配置变更发生时,Activity 会经历销毁重建,但系统会通过 onRetainNonConfigurationInstance() 保存一个 NonConfigurationInstances 对象,其中包含 ViewModelStore

  2. ViewModelStore 缓存
    ViewModel 实例由 ViewModelStore 管理(内部是一个 Map),Activity 重建后会从 NonConfigurationInstances 中恢复该 Store,从而复用之前的 ViewModel。

  3. 源码验证

    // Activity.java
    final Object onRetainNonConfigurationInstance() {Object custom = onRetainCustomNonConfigurationInstance();ViewModelStore viewModelStore = mViewModelStore;// ...NonConfigurationInstances nci = new NonConfigurationInstances();nci.custom = custom;nci.viewModelStore = viewModelStore; // 保存 ViewModelStorereturn nci;
    }
    

真题 2(阿里)
"LiveData 如何保证数据更新时只通知活跃的观察者?"

解析
LiveData 通过状态管理和版本控制实现精准通知:

  1. 活跃状态判断
    观察者的活跃状态由 shouldBeActive() 方法决定,必须处于 STARTED 或 RESUMED 状态。

  2. 版本号机制
    每次调用 setValue() 时,LiveData 的 mVersion 会递增。观察者记录自己的 mLastVersion,只有当 mVersion > mLastVersion 时才会触发更新。

  3. 源码验证

    // LiveData.java
    private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) return; // 非活跃状态不通知if (observer.mLastVersion >= mVersion) return; // 版本相同不通知observer.mLastVersion = mVersion;observer.mObserver.onChanged((T) mData); // 通知更新
    }
    

2. 把 LiveData 放在普通类里面是否可行

真题 3(腾讯)
"将 LiveData 放在普通 POJO 类中会有什么问题?如何解决?"

解析
存在三大风险:

  1. 内存泄漏风险
    普通类无法感知生命周期,当 Activity/Fragment 销毁后,LiveData 可能仍持有其引用,导致无法被回收。

  2. 空指针异常
    若 Activity 已销毁,LiveData 仍发送数据更新,可能触发空指针(如更新 UI 操作)。

  3. 数据一致性问题
    配置变更时,普通类实例会重建,导致数据丢失(而 ViewModel 会被保留)。

解决方案

  • 使用 viewModelScope 或 lifecycleScope 绑定协程生命周期。
  • 手动管理观察者订阅(在 onDestroy() 中调用 removeObserver())。
  • 优先使用 ViewModel 作为 LiveData 的宿主。

3. LiveData 是否需要依赖 ViewModel 来取消订阅

真题 4(美团)
"LiveData 自动取消订阅的原理是什么?如果手动调用 removeObserver() 会发生什么?"

解析

  1. 自动取消订阅原理
    LifecycleBoundObserver 实现了 LifecycleEventObserver,当宿主状态变为 DESTROYED 时,会自动调用 removeObserver()

  2. 手动取消订阅场景

    • 当需要提前释放资源时(如 Fragment 隐藏但未销毁)。
    • 非 LifecycleOwner 作为观察者时(如普通 Java 对象)。
  3. 源码验证

    // LiveData.java
    class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@Overridepublic void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {if (source.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver); // 自动移除return;}}
    }
    

4. 一个 ViewModel 在 Fragment 销毁时执行哪些方法

真题 5(百度)
"Fragment 销毁时,ViewModel 的 onCleared() 一定会被调用吗?为什么?"

解析
不一定!关键点在于 Fragment 的生命周期与 ViewModel 的绑定逻辑

  1. 普通 Fragment 销毁
    若 Fragment 被移除但 Activity 未销毁,ViewModel 不会被清除(onCleared() 不调用)。因为 ViewModel 的生命周期绑定到 Activity。

  2. Activity 销毁
    当 Activity 销毁时,ViewModelStore 会调用 clear(),触发 onCleared()

  3. 内存不足场景
    系统在后台杀死 Activity 时,ViewModel 会被保留(在 onRetainNonConfigurationInstance() 中),但进程重启后 ViewModel 会重新创建,onCleared() 不会被调用。

5. 解释一下 LiveData 以及它是如何感知生命周期的

真题 6(字节跳动)
"如何自定义一个具有生命周期感知能力的 LiveData?"

解析
自定义 LiveData 需要继承 LiveData<T> 并实现生命周期监听:

public class MyLiveData extends LiveData<String> {private MyDataSource dataSource;public MyLiveData(MyDataSource dataSource) {this.dataSource = dataSource;}@Overrideprotected void onActive() {// 当第一个活跃观察者出现时调用dataSource.registerListener(this::setValue);}@Overrideprotected void onInactive() {// 当没有活跃观察者时调用dataSource.unregisterListener();}
}

关键点

  • onActive():开始监听数据源(如网络请求、数据库)。
  • onInactive():停止监听,释放资源。
  • 生命周期状态由 LiveData 内部的 mActiveCount 控制。

6. 高阶真题(常考设计题)

真题 7(阿里)
"如何实现一个支持黏性事件的 LiveData?"

解析
黏性事件指观察者订阅时能收到最近一次发送的数据。实现方案:

import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.LifecycleOwner;/*** 黏性 LiveData 实现类(支持新订阅者获取最新数据)* 核心功能:当新观察者订阅时,立即获取最新一次发送的数据(即使在订阅前数据已更新)* 设计原理:通过版本号机制记录数据更新次数,确保新订阅者能收到订阅前的最新数据,同时避免重复通知旧订阅者*/
public class StickyLiveData<T> extends LiveData<T> {// 缓存最新的数据(用于向新订阅者发送黏性事件)private T stickyData;// 数据版本号(每次数据更新时递增,用于判断数据是否为新的)private int version = 0;/*** 重写数据更新方法,增加版本号管理和数据缓存* @param value 新数据*/@Overridepublic void setValue(T value) {// 1. 版本号自增(确保每次更新都是一个新的版本)version++;// 2. 缓存最新数据(供新订阅者获取)stickyData = value;// 3. 调用父类方法触发数据更新通知(走 LiveData 原生通知流程)super.setValue(value);}/*** 重写订阅方法,使用自定义包装类记录订阅时的版本号* @param owner 生命周期宿主(如 Activity/Fragment)* @param observer 原始观察者*/@Overridepublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {// 创建自定义观察者包装类,传入原始观察者和当前版本号(订阅时的版本)StickyObserver stickyObserver = new StickyObserver(observer, version);// 调用父类方法注册包装后的观察者(利用 LiveData 原生的生命周期管理)super.observe(owner, stickyObserver);}/*** 自定义观察者包装类(核心逻辑:通过版本号过滤数据更新通知)*/private class StickyObserver implements Observer<T> {// 持有原始观察者(最终由它触发数据变化回调)private final Observer<? super T> observer;// 记录订阅时的版本号(用于判断后续收到的数据是否是新的)private int lastVersion;/*** 构造方法:初始化原始观察者和订阅时的版本号* @param observer 原始观察者* @param version 订阅时的当前版本号(即数据更新次数)*/public StickyObserver(Observer<? super T> observer, int version) {this.observer = observer;this.lastVersion = version; // 保存订阅时的版本(初始版本)}/*** 数据变化回调(重写 LiveData 的通知逻辑)* @param t 新数据*/@Overridepublic void onChanged(T t) {// 1. 版本号比较:仅当当前数据版本号 > 订阅时的版本号时触发通知//    - 新订阅者:首次收到数据时,lastVersion 是订阅前的版本,当前 version 已自增,会触发通知//    - 旧订阅者:若数据版本未更新(version 不变),不会重复通知if (lastVersion < version) {// 2. 更新本地记录的版本号(确保后续通知只处理更高版本的数据)lastVersion = version;// 3. 触发原始观察者的回调(传递最新数据)observer.onChanged(t);}// 注意:若版本号未变化(如重复调用 setValue 相同数据),不会触发通知,保持 LiveData 原生行为}}
}

关键逻辑说明:

  1. 版本号机制(核心设计):

    • version 每次调用 setValue() 时自增,确保每次数据更新都是一个唯一的 “版本”。
    • 新订阅者注册时,StickyObserver 记录当前 version(即订阅前的最新版本)。
    • 当数据更新时(version 变大),只有 lastVersion < version 的观察者会收到通知,确保新订阅者能获取订阅前的最新数据(黏性事件),而旧订阅者不会重复接收已处理的旧数据。
  2. 数据缓存

    • stickyData 存储最新数据,配合版本号机制,确保新订阅者首次回调时能获取到最新值(即使在订阅前数据已更新)。
    • 注意:stickyData 并非必须(父类 LiveData 已通过 mData 存储数据),此处主要为逻辑清晰而显式声明,实际可直接使用父类的 mData,但需通过反射访问(不推荐),因此独立缓存更简洁。
  3. 生命周期感知

    • 继承自 LiveData,天然支持 LifecycleOwner(如 Activity/Fragment)的生命周期管理,自动处理订阅和取消订阅,避免内存泄漏。
    • StickyObserver 复用父类的 observe() 方法,确保黏性逻辑与原生生命周期机制兼容。
  4. 线程安全

    • setValue() 要求在主线程调用(遵循 LiveData 规范),postValue() 可在子线程调用,内部会切换到主线程通知观察者,确保线程安全。
http://www.xdnf.cn/news/6131.html

相关文章:

  • 【Redis 进阶】分布式锁
  • Q1财报揭示:用户增长与客单价下跌对eBay卖家的蝴蝶效应
  • 最佳实践PPT | 数据架构设计总体规划方案数据中台架构数据架构图解决方案
  • 深度解析智能体:从概念到应用的全方位洞察
  • AI产品上市前的“安全通行证“
  • 7.DTH11和PWM波
  • React系列——nvm、node、npm、yarn(MAC)
  • 机器学习第十讲:异常值检测 → 发现身高填3米的不合理数据
  • Spring 事件监听机制的使用
  • flatbuffer实践
  • 操作系统实验 实验3 存储器分配与回收
  • 设计模式-中介者模式
  • Docker使用经验-从Image导出dockerfile并进行修改
  • 【Elasticsearch】DSL 篇
  • 什么是alpaca 或 sharegpt 格式的数据集?
  • Windows电脑端高效记事提醒工具推荐
  • 【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
  • SCDN能够运用在物联网加速当中吗?
  • Spring Web MVC————入门(2)
  • Spark处理过程--案例数据清洗
  • 大模型越狱:技术漏洞与安全挑战——从原理到防御
  • 正向代理与反向代理区别及应用
  • 威廉・巴拉德与格理集团:在高科技浪潮中的洞察与前行
  • 【极兔快递Java社招】一面复盘|数据库+线程池+AQS+中间件面面俱到
  • 【Linux网络】————详解TCP三次握手四次挥手
  • vue3:十三、分类管理-表格--slot插槽详细说明---表格内拼接字段、tag标签
  • 怎么查看当前vue项目,要求的node.js版本
  • Oracle — PL-SQL
  • JT/T 808 各版本协议字段级别对比与解析适配建议
  • NACOS基于长链接的⼀致性模型