kotlin与MVVM结合使用总结(三)

1. MVVM 架构详细介绍及源码层面理解

整体架构

MVVM(Model - View - ViewModel)架构是为了解决视图和数据模型之间的耦合问题而设计的。它通过引入 ViewModel 作为中间层,实现了视图和数据的分离,提高了代码的可维护性和可测试性。

  • View(视图层):在 Android 中,View 主要关联 Activity、Fragment 以及 XML 布局文件等,负责呈现界面与响应用户交互。像 Activity 里设置布局、初始化视图元素,以及处理用户点击等操作都属于 View 范畴。例如在一个登录界面 Activity 里,布局文件定义了输入框、按钮等 UI 元素的样式与位置,Activity 则处理按钮点击事件,这些都在 View 层完成。
  • Model(数据模型层):负责数据的获取、存储与处理。比如从网络请求用户信息、将数据存储到本地数据库等。在电商应用中,从服务器获取商品列表数据,或是将用户的购物车信息保存到本地数据库,都由 Model 层执行。
  • ViewModel(视图模型层):作为连接 View 与 Model 的纽带,承担关键的界面显示逻辑处理任务。它从 Model 获取数据,并将其转换为适合 View 展示的形式。例如在新闻应用中,Model 获取到原始新闻数据列表,ViewModel 可对数据进行加工,如截取新闻摘要、处理图片链接等,让数据能更好地在 View 中展示。在数据更新时,ViewModel 通过 LiveData 等机制通知 View 进行相应更新。从源码层面看,ViewModel 借助 ViewModelProvider 来创建与管理。ViewModelProvider 内部运用 ViewModelStore 存储 ViewModel 实例,确保配置变更(如屏幕旋转)时,ViewModel 实例不会被销毁,维持数据的稳定性。
观察者模式在 MVVM 中的应用

在 MVVM 架构里,观察者模式发挥着核心作用。ViewModel 持有数据,以 LiveData 为例,View 作为观察者监听 LiveData 数据变化。LiveData 内部维护了观察者列表,当数据变更时,会调用 dispatchingValue 方法遍历观察者列表。在 considerNotify 方法中,判断观察者状态,若活跃则通过 observer.mObserver.onChanged((T) mData) 通知观察者,View 接收到通知后更新界面,实现了 View 与 ViewModel 低耦合通信,这在诸多面试真题里都有涉及,是理解 MVVM 架构的关键。ViewModel 是通过 ViewModelProvider 来创建和管理的。

2. LiveData 实例化方法及源码分析

实例化方法
  • MutableLiveDataMutableLiveDataLiveData 的子类,它公开了 setValue()postValue() 方法,允许外部修改其持有的数据。
java 复制代码
MutableLiveData<String> liveData = new MutableLiveData<>();

在源码中,MutableLiveData 只是简单地继承了 LiveData 并暴露了修改数据的方法。

java 复制代码
public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}
  • 使用 Transformations 类进行转换Transformations 类提供了一些静态方法,如 map()switchMap(),可以对 LiveData 进行转换。
java 复制代码
LiveData<Integer> source = new MutableLiveData<>();
LiveData<String> transformed = Transformations.map(source, input -> "Transformed: " + input);

map() 方法的源码实现如下:

java 复制代码
public static <X, Y> LiveData<Y> map(
        LiveData<X> source,
        final Function<X, Y> mapFunction) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() {
        @Override
        public void onChanged(@Nullable X x) {
            result.setValue(mapFunction.apply(x));
        }
    });
    return result;
}

3. LiveData 如何实现生命周期绑定问题

LiveData 生命周期绑定机制在面试中频繁被问到,其实现依赖于 Android 的 Lifecycle 组件。

当调用 LiveData.observe() 方法时,会创建 LifecycleBoundObserver 对象,它实现了 LifecycleEventObserver 接口来监听 LifecycleOwner(如 Activity、Fragment)的生命周期变化。在 observe() 方法源码中,先检查 LifecycleOwner 状态,若已销毁则直接返回;否则创建 LifecycleBoundObserver 并添加到观察者列表,同时将其注册到 LifecycleOwner 的生命周期观察者中。

java 复制代码
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        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);
}

LifecycleBoundObserveronStateChanged 方法会在 LifecycleOwner 生命周期状态变化时被调用。在此方法中,根据生命周期状态决定是否更新观察者。当状态变为 DESTROYED 时,从 LiveData 的观察者列表移除该观察者,防止内存泄漏;当状态为 STARTEDRESUMED 时,认为观察者活跃,可接收数据更新。

java 复制代码
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    @NonNull
    final LifecycleOwner mOwner;

    LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
        super(observer);
        mOwner = owner;
    }

    @Override
    boolean shouldBeActive() {
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        activeStateChanged(shouldBeActive());
    }

    @Override
    boolean isAttachedTo(LifecycleOwner owner) {
        return mOwner == owner;
    }

    @Override
    void detachObserver() {
        mOwner.getLifecycle().removeObserver(this);
    }
}

这种机制确保 LiveData 仅在 LifecycleOwner 处于活跃状态(STARTEDRESUMED)时更新观察者,有效避免内存泄漏与空指针异常,是 LiveData 的重要特性。

4. LiveData 粘性事件的深入分析

粘性事件的概念

粘性事件是指当一个观察者注册到 LiveData 时,即使该 LiveData 在观察者注册之前已经有了更新,观察者仍然会接收到这些之前的更新。这是因为 LiveData 会记录最新的值,当有新的观察者注册时,会立即将最新的值发送给它。

源码层面分析

LiveDataobserve() 方法中,当新的观察者注册时,会调用 dispatchingValue() 方法,该方法会检查观察者的状态,并将最新的值发送给它。

java 复制代码
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;
    // 调用观察者的 onChanged 方法,发送最新的值
    observer.mObserver.onChanged((T) mData);
}

considerNotify() 方法中,会比较观察者的 mLastVersion 和 LiveData 的 mVersion,如果 mLastVersion 小于 mVersion,则会调用观察者的 onChanged() 方法,将最新的值发送给它。

解决粘性事件的方法

为了避免粘性事件的影响,可以考虑使用一些第三方库,如 SingleLiveEvent 或自定义 LiveData 实现。以下是一个简单的自定义 LiveData 实现,用于避免粘性事件:

java 复制代码
import androidx.lifecycle.LiveData;

import java.util.concurrent.atomic.AtomicBoolean;

public class NonStickyLiveData<T> extends LiveData<T> {

    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @Override
    public void observeForever(@NonNull Observer<? super T> observer) {
        super.observeForever(new Observer<T>() {
            @Override
            public void onChanged(T t) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @Override
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(T t) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @Override
    protected void setValue(T value) {
        mPending.set(true);
        super.setValue(value);
    }

    @Override
    protected void postValue(T value) {
        mPending.set(true);
        super.postValue(value);
    }
}

在这个自定义的 LiveData 中,使用 AtomicBoolean 来标记是否有新的值需要发送,只有当 mPendingtrue 时,才会调用观察者的 onChanged() 方法,从而避免了粘性事件的影响。

粘性事件总结

在 LiveData 机制里,不活跃观察者(对应 LifecycleOwner 处于 STOPPEDPAUSED 状态)正常情况下不会接收数据更新事件。只有当观察者再次变为活跃状态时,LiveData 才会将最新数据发送给它。这是因为在 LifecycleBoundObservershouldBeActive 方法中,依据 LifecycleOwner 的当前生命周期状态判断观察者是否活跃,不活跃则不进行数据分发。

然而,LiveData 存在粘性事件问题,这在面试中常被提及。粘性事件指新观察者注册时,即便 LiveData 之前已有更新,观察者仍会收到这些之前的更新数据。从源码层面分析,在 LiveDataobserve() 方法中,新观察者注册后会调用 dispatchingValue() 方法。在 dispatchingValue() 内部的 considerNotify() 方法里,通过比较观察者的 mLastVersion 和 LiveData 的 mVersion 来决定是否通知观察者。若 mLastVersion 小于 mVersion,则调用观察者的 onChanged() 方法发送最新数据,导致粘性事件发生。

为解决粘性事件问题,常见方法如下:

  • 使用 SingleLiveEvent :自定义一个继承自 MutableLiveData 的类,重写相关方法确保事件只被消费一次。例如在一些开源项目中,SingleLiveEvent 类通过设置标志位,在 observe() 方法中判断标志位,仅在首次观察时触发数据更新,后续不再响应之前的粘性数据。
  • 使用 Event 包装类 :将数据包装在 Event 类中,通过标记数据是否已被处理来避免重复触发。在观察者获取数据时,先检查标记位,若未处理则处理数据并设置标记位,防止重复处理粘性数据。
  • 使用 MediatorLiveDataMediatorLiveData 可监听其他 LiveData 变化,并在必要时过滤粘性事件。通过添加源 LiveData 的观察者,在数据变化时进行相应处理,如更新自身数据后移除源 LiveData,避免粘性事件传递给新观察者。
相关推荐
Kapaseker10 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z2 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton3 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream3 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam3 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker4 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
郑州光合科技余经理4 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1234 天前
matlab画图工具
开发语言·matlab
dustcell.4 天前
haproxy七层代理
java·开发语言·前端
norlan_jame4 天前
C-PHY与D-PHY差异
c语言·开发语言