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) {
        // ignore
        return;
    }
    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 setTagIfAbsent
                closeWithRuntimeException(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 unchecked
    observer.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; // 保存 ViewModelStore
        return 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 {
        @Override
        public 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;
    }
    
    @Override
    protected void onActive() {
        // 当第一个活跃观察者出现时调用
        dataSource.registerListener(this::setValue);
    }
    
    @Override
    protected 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 新数据
     */
    @Override
    public void setValue(T value) {
        // 1. 版本号自增(确保每次更新都是一个新的版本)
        version++;
        // 2. 缓存最新数据(供新订阅者获取)
        stickyData = value;
        // 3. 调用父类方法触发数据更新通知(走 LiveData 原生通知流程)
        super.setValue(value);
    }

    /**
     * 重写订阅方法,使用自定义包装类记录订阅时的版本号
     * @param owner 生命周期宿主(如 Activity/Fragment)
     * @param observer 原始观察者
     */
    @Override
    public 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 新数据
         */
        @Override
        public 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() 可在子线程调用,内部会切换到主线程通知观察者,确保线程安全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值