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

kotlin和MVVM的结合使用总结(二)

MVVM 架构详解

核心组件:ViewModel 和 LiveData

在 Android 中,MVVM 架构主要借助 ViewModel 和 LiveData 来实现。ViewModel 负责处理业务逻辑,而 LiveData 则用于实现数据的响应式更新。

ViewModel 的源码分析

ViewModel 的核心逻辑在 ViewModelStore 类中。ViewModelStore 是一个存储 ViewModel 的容器,内部使用 HashMap 来存储不同的 ViewModel 实例。以下是 ViewModelStore 的关键代码:

public class ViewModelStore {private final HashMap<String, ViewModel> mMap = new HashMap<>();final void put(String key, ViewModel viewModel) {ViewModel oldViewModel = mMap.put(key, viewModel);if (oldViewModel != null) {oldViewModel.onCleared();}}final ViewModel get(String key) {return mMap.get(key);}public final void clear() {for (ViewModel vm : mMap.values()) {vm.onCleared();}mMap.clear();}
}

ViewModel 利用 ViewModelStore 保证在配置变更(如屏幕旋转)时数据不丢失,并且将业务逻辑与 ActivityFragment 分离,提高了代码的可维护性和可测试性。

LiveData 的源码分析

LiveData 基于观察者模式,通过 observe 方法添加观察者,当数据变化时,会调用 Observer 的 onChanged 方法更新 UI。

与 MVC 架构对比

MVC 架构的问题

在 MVC 架构中,Activity 既充当 View 又充当 Controller。在 Android 中,Activity 继承自 AppCompatActivity,其源码中包含大量视图初始化和事件处理代码,使得 Activity 变得复杂。例如:

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.button).setOnClickListener(v -> {// 处理点击事件});}
}

与 MVVM 相比,MVC 没有明确的职责划分,导致代码耦合度高,难以维护和扩展。

与 MVP 架构对比

MVP 架构的特点

MVP 中 Presenter 与 View 通过接口交互。Presenter 持有 View 接口的引用,在更新视图时调用接口方法。以下是一个简单的示例:

// View 接口
public interface MainView {void showData(String data);
}// Presenter 类
public class MainPresenter {private MainView view;public MainPresenter(MainView view) {this.view = view;}public void loadData() {// 模拟加载数据String data = "Hello, MVP!";view.showData(data);}
}

而 MVVM 采用数据绑定,ViewModel 无需持有 View 的引用,降低了耦合度,使得代码更加灵活和易于维护。

面试扩展:

MVVM 架构与其他架构区别

  1. 请详细阐述 MVVM、MVC 和 MVP 架构在数据绑定机制上的差异,并说明这种差异如何影响代码的可维护性和扩展性。
    回答

    • MVVM:在 Android 中借助 LiveData 实现数据绑定,以 LiveData 源码为例,它基于观察者模式,通过 observe 方法添加观察者,内部维护 mVersion 和 ObserverWrapper 的 mLastVersion 来控制数据分发。数据变化时,LiveData 会遍历观察者调用 onChanged 方法更新 UI,使得 View 和 ViewModel 之间的数据同步自动化,减少手动更新代码,提高可维护性与扩展性。例如在复杂 UI 界面中,数据更新无需在多处手动操作视图。
    • MVC:如在 Android 里 Activity 常兼具 View 和 Controller 职责,在 Activity 源码中,大量视图初始化和事件处理代码混杂,在更新视图时需手动在 Controller 部分(如 Activity 中的业务逻辑代码)编写更新视图的代码,这导致代码耦合度高,可维护性差。例如一个界面有多个视图需更新,代码中会有多处重复的视图更新逻辑,不利于扩展新功能。
    • MVP:Presenter 通过接口与 View 交互,更新视图时 Presenter 手动调用 View 接口方法。从代码结构看,Presenter 持有 View 引用,若业务逻辑复杂,Presenter 会变得臃肿,可维护性降低。且当 View 层变化时,Presenter 中调用 View 接口方法的代码也需大量修改,扩展性不佳。比如更换了视图框架,Presenter 中更新视图的方法基本都要调整。
  2. 从架构组件的生命周期角度,分析 MVVM 与 MVP 架构的不同。
    回答

    • MVVM:ViewModel 的生命周期由 ViewModelStore 和 ViewModelProvider 管理。ViewModelProvider 从 ViewModelStore 中创建或获取 ViewModel 实例,在配置变更(如屏幕旋转)时,ViewModel 实例不销毁,保证数据存储。而 LiveData 具有生命周期感知能力,通过 LifecycleOwner 控制观察者的注册与注销,只有当 LifecycleOwner 处于活跃状态(如 STARTED 或 RESUMED)时,观察者才会接收数据更新,避免内存泄漏等问题。
    • MVP:Presenter 与 View 通过接口交互,Presenter 持有 View 引用。但 Presenter 本身没有很好的生命周期管理机制,在配置变更时,若不妥善处理,Presenter 可能会持有旧的 View 引用,导致内存泄漏。例如在屏幕旋转时,若没有正确处理 Presenter 与 View 的关系,可能会出现空指针异常等问题。

LiveData 数据倒灌的源码级别分析

数据倒灌的原因

LiveData 内部维护了一个 mVersion 和 START_VERSIONmVersion 表示 LiveData 数据的版本号,每次数据更新时 mVersion 会递增。ObserverWrapper 是 Observer 的包装类,其中的 mLastVersion 记录了观察者最后一次接收到数据的版本号。

// LiveData 关键代码
private static abstract class ObserverWrapper {final Observer<? super T> mObserver;boolean mActive;int mLastVersion = START_VERSION;ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}// ...
}

当新的观察者注册时,LiveData 会检查 mLastVersion 和 mVersion,如果 mLastVersion 小于 mVersion,则会将数据发送给观察者,从而导致数据倒灌。

解决方法原理

以 SingleLiveEvent 为例,它通过一个标志位 mPending 来控制数据是否已被消费。

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;import java.util.concurrent.atomic.AtomicBoolean;public class SingleLiveEvent<T> extends MutableLiveData<T> {private final AtomicBoolean mPending = new AtomicBoolean(false);@Overridepublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {super.observe(owner, t -> {if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}});}@Overridepublic void setValue(T value) {mPending.set(true);super.setValue(value);}@Overridepublic void postValue(T value) {mPending.set(true);super.postValue(value);}
}

当数据更新时,mPending 设为 true,观察者接收到数据后将其设为 false,确保数据只被消费一次。

面试扩展:

  1. 请描述 LiveData 数据倒灌产生的原因,并给出至少两种基于源码层面的解决方案。
    回答
    • 原因:从 LiveData 源码看,它内部维护 mVersion 代表数据版本号,每次数据更新 mVersion 递增,ObserverWrapper 中的 mLastVersion 记录观察者最后接收数据版本号。新观察者注册时,若 mLastVersion 小于 mVersion,就会接收数据,导致数据倒灌。
    • 解决方案
      • SingleLiveEvent:它通过 AtomicBoolean 类型的 mPending 标志位控制数据消费。数据更新时,mPending 设为 true,观察者接收数据后设为 false,保证数据只被消费一次。从其源码实现可知,在 observe 方法中判断 mPending 状态决定是否通知观察者。
      • 自定义 LiveData 包装类:在自定义包装类中添加版本号或标记位。如在包装类中维护一个 AtomicInteger 类型的版本号,每次数据更新时版本号递增,在 observe 方法中,观察者接收数据后记录当前版本号,下次数据更新时对比版本号,若相同则不通知,从而避免数据倒灌。

ViewModel 对 UI 数据管理的源码级别分析

数据存储与生命周期管理

ViewModel 的生命周期由 ViewModelStore 和 ViewModelProvider 管理。ViewModelProvider 负责创建和获取 ViewModel 实例,它会先从 ViewModelStore 中查找是否存在该 ViewModel 实例,如果存在则直接返回,不存在则创建新的实例。

// ViewModelProvider 关键代码
public class ViewModelProvider {private final Factory mFactory;private final ViewModelStore mViewModelStore;public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {mFactory = factory;mViewModelStore = store;}@NonNull@MainThreadpublic <T extends ViewModel> T get(@NonNull Class<T> modelClass) {String canonicalName = modelClass.getCanonicalName();if (canonicalName == null) {throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");}return get(DEFAULT_KEY + ":" + canonicalName, modelClass);}@NonNull@MainThreadpublic <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {ViewModel viewModel = mViewModelStore.get(key);if (modelClass.isInstance(viewModel)) {//noinspection uncheckedreturn (T) viewModel;} else {//noinspection StatementWithEmptyBodyif (viewModel != null) {// TODO: log a warning.}}viewModel = mFactory.create(modelClass);mViewModelStore.put(key, viewModel);//noinspection uncheckedreturn (T) viewModel;}
}

这样,在配置变更时,ViewModel 实例不会被销毁,保证了数据的存储。

数据更新与通知

ViewModel 通常使用 LiveData 来存储和通知 UI 数据的变化。当 ViewModel 中的数据更新时,调用 LiveData 的 setValue 或 postValue 方法,LiveData 会遍历所有观察者并调用其 onChanged 方法。

// LiveData 关键代码
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);
}

通过这种方式,ViewModel 实现了对 UI 数据的管理和更新通知。

面试扩展:

  1. 从源码角度分析 ViewModel 如何确保在配置变更时 UI 数据不丢失,以及如何与 LiveData 协作更新 UI。
    回答
    • 配置变更时数据不丢失ViewModel 借助 ViewModelStore 和 ViewModelProvider 实现。ViewModelStore 内部用 HashMap 存储 ViewModel 实例,ViewModelProvider 在创建或获取 ViewModel 实例时,先从 ViewModelStore 查找,若存在则返回,不存在才创建新实例。例如在屏幕旋转等配置变更时,ViewModel 实例得以保留,确保 UI 数据不丢失。
    • 与 LiveData 协作更新 UIViewModel 通常使用 LiveData 存储和通知 UI 数据变化。当 ViewModel 中数据更新,调用 LiveData 的 setValue 或 postValue 方法,LiveData 会遍历观察者调用 considerNotify 方法,在 considerNotify 方法中判断观察者状态和版本号等条件后,调用观察者的 onChanged 方法通知 UI 更新。

相关文章:

  • 【工具】使用 MCP Inspector 调试服务的完全指南
  • 展锐Android13电池问题导致系统的崩溃,(2)电池电压计算和电池曲线
  • JAVA聚焦OutOfMemoryError 异常
  • STM32F407使用ESP8266实现阿里云OTA(下)
  • Red:1靶场环境部署及其渗透测试笔记(Vulnhub )
  • Python面向对象编程相关的单选题和多选题
  • 测试基础笔记第十一天
  • 济南国网数字化培训班学习笔记-第二组-2节-输电线路施工及质量
  • Linux基础篇、第四章_01软件安装rpm_yum_源码安装_二进制安装
  • Linux基础
  • yt-dlp 下载时需要 cookie
  • 【Redis】 Redis中常见的数据类型(二)
  • 【玩转全栈】—— 无敌前端究极动态组件库--Inspira UI
  • 大语言模型的“模型量化”详解 - 02:量化参数 主流量化参数全面解读与实战-Q/K/IQ/TQ 到 GGUF 的完整流程
  • SpringMVC处理请求映射路径和接收参数
  • Android studio进阶开发(四)--okhttp的网络通信的使用
  • 【云计算】云计算中IaaS、PaaS、SaaS介绍
  • Linux Awk 深度解析:10个生产级自动化与云原生场景
  • 大语言模型的“模型量化”详解 - 03:【超轻部署、极致推理】KTransformers 环境配置 实机测试
  • 函数模板 (Function Templates)
  • 牧原股份一季度归母净利润44.91亿元,同比扭亏为盈
  • 云南富源回应“岔河水库死鱼”事件: 初步研判与水体缺氧有关
  • 唐仁健违规收受礼品、礼金被点名!十起违反中央八项规定精神典型问题被通报
  • 为什么猛起身会头晕?你的身体在发出这个警报
  • 封江晚开江早,东北地区主要江河上一冰封期冰层较常年偏薄
  • 《亡命驾驶》:一场对于男子气概的终极幻想