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
:
-
Activity 重建流程:
当屏幕旋转等配置变更发生时,Activity 会经历销毁重建,但系统会通过onRetainNonConfigurationInstance()
保存一个NonConfigurationInstances
对象,其中包含ViewModelStore
。 -
ViewModelStore 缓存:
ViewModel 实例由ViewModelStore
管理(内部是一个 Map),Activity 重建后会从NonConfigurationInstances
中恢复该 Store,从而复用之前的 ViewModel。 -
源码验证:
// 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 通过状态管理和版本控制实现精准通知:
-
活跃状态判断:
观察者的活跃状态由shouldBeActive()
方法决定,必须处于STARTED
或RESUMED
状态。 -
版本号机制:
每次调用setValue()
时,LiveData 的mVersion
会递增。观察者记录自己的mLastVersion
,只有当mVersion > mLastVersion
时才会触发更新。 -
源码验证:
// 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 类中会有什么问题?如何解决?"
解析:
存在三大风险:
-
内存泄漏风险:
普通类无法感知生命周期,当 Activity/Fragment 销毁后,LiveData 可能仍持有其引用,导致无法被回收。 -
空指针异常:
若 Activity 已销毁,LiveData 仍发送数据更新,可能触发空指针(如更新 UI 操作)。 -
数据一致性问题:
配置变更时,普通类实例会重建,导致数据丢失(而 ViewModel 会被保留)。
解决方案:
- 使用
viewModelScope
或lifecycleScope
绑定协程生命周期。 - 手动管理观察者订阅(在
onDestroy()
中调用removeObserver()
)。 - 优先使用 ViewModel 作为 LiveData 的宿主。
3. LiveData 是否需要依赖 ViewModel 来取消订阅
真题 4(美团)
"LiveData 自动取消订阅的原理是什么?如果手动调用 removeObserver()
会发生什么?"
解析:
-
自动取消订阅原理:
LifecycleBoundObserver
实现了LifecycleEventObserver
,当宿主状态变为DESTROYED
时,会自动调用removeObserver()
。 -
手动取消订阅场景:
- 当需要提前释放资源时(如 Fragment 隐藏但未销毁)。
- 非 LifecycleOwner 作为观察者时(如普通 Java 对象)。
-
源码验证:
// 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 的绑定逻辑:
-
普通 Fragment 销毁:
若 Fragment 被移除但 Activity 未销毁,ViewModel 不会被清除(onCleared()
不调用)。因为 ViewModel 的生命周期绑定到 Activity。 -
Activity 销毁:
当 Activity 销毁时,ViewModelStore 会调用clear()
,触发onCleared()
。 -
内存不足场景:
系统在后台杀死 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 原生行为}}
}
关键逻辑说明:
-
版本号机制(核心设计):
version
每次调用setValue()
时自增,确保每次数据更新都是一个唯一的 “版本”。- 新订阅者注册时,
StickyObserver
记录当前version
(即订阅前的最新版本)。 - 当数据更新时(
version
变大),只有lastVersion < version
的观察者会收到通知,确保新订阅者能获取订阅前的最新数据(黏性事件),而旧订阅者不会重复接收已处理的旧数据。
-
数据缓存:
stickyData
存储最新数据,配合版本号机制,确保新订阅者首次回调时能获取到最新值(即使在订阅前数据已更新)。- 注意:
stickyData
并非必须(父类LiveData
已通过mData
存储数据),此处主要为逻辑清晰而显式声明,实际可直接使用父类的mData
,但需通过反射访问(不推荐),因此独立缓存更简洁。
-
生命周期感知:
- 继承自
LiveData
,天然支持LifecycleOwner
(如 Activity/Fragment)的生命周期管理,自动处理订阅和取消订阅,避免内存泄漏。 StickyObserver
复用父类的observe()
方法,确保黏性逻辑与原生生命周期机制兼容。
- 继承自
-
线程安全:
setValue()
要求在主线程调用(遵循 LiveData 规范),postValue()
可在子线程调用,内部会切换到主线程通知观察者,确保线程安全。