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
保证在配置变更(如屏幕旋转)时数据不丢失,并且将业务逻辑与 Activity
、Fragment
分离,提高了代码的可维护性和可测试性。
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 架构与其他架构区别
-
请详细阐述 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 中更新视图的方法基本都要调整。
- MVVM:在 Android 中借助
-
从架构组件的生命周期角度,分析 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 的关系,可能会出现空指针异常等问题。
- MVVM:ViewModel 的生命周期由
LiveData 数据倒灌的源码级别分析
数据倒灌的原因
LiveData 内部维护了一个 mVersion
和 START_VERSION
,mVersion
表示 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
,确保数据只被消费一次。
面试扩展:
- 请描述 LiveData 数据倒灌产生的原因,并给出至少两种基于源码层面的解决方案。
回答:- 原因:从
LiveData
源码看,它内部维护mVersion
代表数据版本号,每次数据更新mVersion
递增,ObserverWrapper
中的mLastVersion
记录观察者最后接收数据版本号。新观察者注册时,若mLastVersion
小于mVersion
,就会接收数据,导致数据倒灌。 - 解决方案:
- SingleLiveEvent:它通过
AtomicBoolean
类型的mPending
标志位控制数据消费。数据更新时,mPending
设为true
,观察者接收数据后设为false
,保证数据只被消费一次。从其源码实现可知,在observe
方法中判断mPending
状态决定是否通知观察者。 - 自定义 LiveData 包装类:在自定义包装类中添加版本号或标记位。如在包装类中维护一个
AtomicInteger
类型的版本号,每次数据更新时版本号递增,在observe
方法中,观察者接收数据后记录当前版本号,下次数据更新时对比版本号,若相同则不通知,从而避免数据倒灌。
- SingleLiveEvent:它通过
- 原因:从
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 数据的管理和更新通知。
面试扩展:
- 从源码角度分析 ViewModel 如何确保在配置变更时 UI 数据不丢失,以及如何与 LiveData 协作更新 UI。
回答:- 配置变更时数据不丢失:
ViewModel
借助ViewModelStore
和ViewModelProvider
实现。ViewModelStore
内部用HashMap
存储ViewModel
实例,ViewModelProvider
在创建或获取ViewModel
实例时,先从ViewModelStore
查找,若存在则返回,不存在才创建新实例。例如在屏幕旋转等配置变更时,ViewModel
实例得以保留,确保 UI 数据不丢失。 - 与 LiveData 协作更新 UI:
ViewModel
通常使用LiveData
存储和通知 UI 数据变化。当ViewModel
中数据更新,调用LiveData
的setValue
或postValue
方法,LiveData
会遍历观察者调用considerNotify
方法,在considerNotify
方法中判断观察者状态和版本号等条件后,调用观察者的onChanged
方法通知 UI 更新。
- 配置变更时数据不丢失: