前言
作为一名Android开发者,如果你熟悉MVVM架构
,熟悉Jetpack
组件,那么相信你肯定使用过ViewModel
。
正如它的名字一样,它是Google推出的一个类,方便我们实现MVVM架构
中的ViewModel层
。我们在其中处理View层
所需的数据,然后在特定条件下通知View层
进行UI更新
。
正如官方所介绍:
ViewModel
类以注重生命周期的方式存储和管理界面相关的数据。ViewModel
类让数据可在发生屏幕旋转等配置更改后继续留存。
我们抓一下这句话的重点:
- 注重生命周期的方式:会在合适的时间进行自我回收,防止出现内存泄漏。
- 存储和管理界面相关的数据:符合
MVVM架构
中ViewModel层
的理念。 - 在发生屏幕旋转等配置更改后继续留存数据:为什么要这么设计?怎么做到的。
接下来,就让我们带着问题,深入学习一下ViewModel类。
使用方法
在阅读源码前,让我们先来简单回顾一下ViewModel的使用方法。
class MainViewModel(private val repository: MainRepo) : ViewModel() {
private val _textMld = MutableLiveData()
val textLd: LiveData = _textMld
fun getTextInfo() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
//做异步网络请求工作,获取到textData
repository.getTextInfo()
}.apply {
_textMld.postValue(textData)
}
}
}
}
class MainActivity : AppCompatActivity() {
fun setVmFactory(): ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
return MainViewModel(MainRepo()) as T
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val vm = ViewModelProvider(this, setVmFactory())[MainViewModel::class.java]
vm.textLd.observe(this, Observer {
binding.textTv.text = it
})
}
}
方法步骤可以简单分为两步,分别为:
-
继承ViewModel
类实现自定义ViewModel
,如:MainViewModel。 - 通过
ViewModelProvider
来实例化ViewModel
。
源码
现在,根据上方所介绍的使用方法,我们进一步来看一下ViewModel的源码。
public abstract class ViewModel {
....
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
....
onCleared();
}
....
}
ViewModel 是一个抽象类,提供 onCleared()
方法供我们在ViewModel销毁前做一下清除工作。
接下来,我们来看看是如何通过ViewModelProvider
来实例化ViewModel对象的。
其实就是两步:
- 实例化一个
ViewModelProvider
对象。 - 调用
ViewModelProvider.get()
方法来得到一个ViewModel
对象。
先来看看它的构造函数:
public open class ViewModelProvider(
private val store: ViewModelStore,
private val factory: Factory
) {
public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner))
public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
owner.viewModelStore,
factory
)
......
}
通过 ViewModelProvider
的构造函数可以看出,一共有两个参数:ViewModelStore
与 Factory
。
分别来看看这两个参数代表着什么意思。
public class ViewModelStore {
private final HashMap mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
//所以这里key需要注意,不要使用相同的key,否则后创建的VM会替换到老的VM
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set keys() {
return new HashSet<>(mMap.keySet());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
//调用ViewModel的clear方法,表示不再使用
vm.clear();
}
//清除集合中的所有ViewModel
mMap.clear();
}
}
ViewModelStore
:正如其名,就是用来存储ViewModel
对象的,通过内部维护一个HashMap
来实现对ViewMoel对象的存储与管理工作。
再来看看上面所介绍的 Factory
。
而Factory则是工厂接口的,用来实例化ViewModel,具体实现可以参考使用方法中所介绍的 setVmFactory()
方法,用来实例化 MainViewModel。
public interface Factory {
public fun create(modelClass: Class): T
}
接下来,我们来看看 ViewModelProvider
中的 get()
方法。
public open operator fun get(modelClass: Class): T {
val canonicalName = modelClass.canonicalName
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
public open operator fun get(key: String, modelClass: Class): T {
//1.根据key从ViewModelStore中取出ViewModel
var viewModel = store[key]
//2.通过工厂方法来实例化viewModel
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
}
viewModel = if (factory is KeyedFactory) {
factory.create(key, modelClass)
} else {
factory.create(modelClass)
}
//3.将实例化的ViewModel放入ViewModelStore
store.put(key, viewModel)
return viewModel
}
概括一下 get()
方法:
- 根据传入的形参
Class.canonicalName
做为key,从ViewModelStore
中取出ViewModel。 - 再通过工厂方法来实例化ViewModel。
- 最后将实例化后的ViewModel放入
ViewModelStore
中,并返回。
OK,根据使用方法来分析源码的话,我们好像已经分析完了。本篇文章到此结束?
这还远远不够!
不知你有没有想过它为什么会取名叫做ViewModel
呢?它跟MVVM
架构模式中的ViewModel
是怎么样的一种关系呢?它又是怎么感知到生命周期的呢?为什么要设计成屏幕旋转后继续留存ViewModel
呢?
接下来,让我们带着问题,进一步进行学习思考。
为什么要叫做ViewModel
这其实就是Google设计这个类的初衷问题了。我们知道在Android的架构模式中,从最初始的MVC
,到MVP
,Google其实没有专门为了这架构模式而设计一些类来支持提供我们开发者使用,这也间接导致开发者会拥有一套属于自己的设计风格。直到MVVM
架构模式的出现,Google为了减小开发者架构难度,提升开发效率,为我们新设计了一些类来支持MVVM
架构模式,其中就有ViewModel
这个类,用来处理MVVM
模式中ViewModel层
所负责的工作。这也就是为什么它会被命名叫做ViewModel
。
ViewModel是如何感知到生命周期的
正如官网所介绍的,ViewModel
是有生命周期的,如下图所示:
不知你有没有思考过ViewModel
是如何感知到生命周期的呢?
如果你熟悉 Jetpack
的话,你肯定会脱口而出答案:Lifecycle
。事实也证明的确是通过 Lifecycle
来感知到生命周期的。
其实官方文档也介绍了:
ViewModel 对象存在的时间范围是获取 ViewModel 时传递给
ViewModelProvider
的Lifecycle
。ViewModel 将一直留在内存中,直到限定其存在时间范围的Lifecycle
永久消失.
那接下来就让我们来进一步看看ViewModel是如何拿到Lifecycle
来感知到生命周期的。
还记得使用方法中所介绍的使用ViewModelProvider
来实例化ViewModel吗?
val viewModel = ViewModelProvider(this, setVmFactory())[getViewModelClass()]
关于 ViewModelProvider
的构造函数,我们之前已经介绍过了,分别是ViewModelStore
与 Factory
。回到调用方法里,这里的this
就是指当前所在的Activity
,也对应着构造函数中的ViewModelStore
。
Activity
对应 ViewModelStore
?这是什么对应关系?
这时,我扫了一眼 ViewModelStore
类注释,发现了一句话:
Use {@link
ViewModelStoreOwner#getViewModelStore()
} to retrieve a {@codeViewModelStore
} for activities and fragments.
通过 ViewModelStoreOwner#getViewModelStore()
方法来给 Activity、Fragment
获取到 ViewModelStore
public interface ViewModelStoreOwner {
/**
* 返回 ViewModelStore
*/
@NonNull
ViewModelStore getViewModelStore();
}
到这一步,应该想到了吧,我们的 Activity | Fragment
实现了ViewModelStoreOwner
接口,并实现了getViewModelStore()
方法来得到 ViewModelStore
。
果然,Activity
的父类ComponentActivity
类实现了ViewModelStoreOwner
接口并实现了 getViewModelStore()
接口方法。
ComponnetActivity.kt
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner
...... {
public ViewModelStore getViewModelStore() {
//检查一下当前 mViewModelStore 是否为空
if (mViewModelStore == null) {
//检索一下是否有先前保存的非配置实例数据
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
//从先前保存的非配置实例数据中取出 ViewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
//先前没有保存过非配置实例数据,则新建一个 ViewModelStore
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
}
简单概括一下:在获取 ViewModelStore
这一步中,会先去检索一下是否有先前保存的非配置实例数据,如果有保存,则取出其中的 ViewModelStore
; 反之,如果之前没有保存过,则新创建一个 ViewModelStore
。
所以,在 ViewModelProvider
的构造函数中通过调用 owner.viewModelStore
方法来获取到 viewModelStore
,这一步其实就是取在 Activity | Fragment
的 getViewModelStore()
方法中创建的 ViewModelStore
,也就是我们刚刚所介绍的。
到了这,好像也没有看出跟 Lifecycle
有任何关系啊。
接着我们进一步看看 viewModelStore
与 Lifecycle
之间的调用关系。
public ComponentActivity() {
......
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
//检查一下是否发生配置变更
if (!isChangingConfigurations()) {
//调用ViewModelStore中的clear()方法来清除其ViewModel
getViewModelStore().clear();
}
}
}
});
......
}
通过Lifecycle
来感知Activity的生命周期,当Activity处于销毁状态时,检查一下是否发生配置变更,如果未发生配置变更,则调用clear()
方法来清除ViewModelStore
及其保存的ViewModel
。
关于配置变更,我们放到文章后面来分析,这里先不展开。
总结一下ViewModel是如何感知到生命周期的。
首先我们知道ViewModel
是存储在ViewModelStore
中的,当我们进入Activity时,会自动创建一个ViewModelStore
,当我们在onCreate()
方法中调用ViewModelProvider.get()
方法时,就会将创建的ViewModel
存到该ViewModelStore
中。通过Lifecycle
来得知Activity的生命周期,当Activity处于销毁时,检查一下是否发生配置变更,如果未发生配置变更,则调用clear()
方法来清除ViewModelStore
及其保存的ViewModel
。
为什么屏幕旋转后ViewModel数据可以继续留存
当我们没有为Activity设置 configChanges
属性时,Activity会在我们旋转屏幕时发生重建,而此时ViewModel会继续留存这些数据。
这是如何做到的呢?
正如上文中所提到的,当Activity处于销毁时,会去检查一下是否发生配置变更,如果未发生配置变更,则调用clear()
方法来清除ViewModelStore
及其保存的ViewModel
。
那这么看的话,想必我们在旋转屏幕时,是发生了配置变更的,因为正是发生了配置变更才让我们的ViewModel没有被清除掉,得以留存这些状态数据。
当Activity因为配置更改(如:旋转屏幕)而被销毁重建时,系统会立即调用onRetainNonConfigurationInstance()
方法。
ComponentActivity.java
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
//获取先前创建的非配置实例数据
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
//如果先前没有创建过非配置实例数据,则新创建一个,并返回。
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
会先通过 getLastNonConfigurationInstance()
方法来获取先前创建的非配置实例数据,如果先前没有创建过非配置实例数据,则新创建一个,并返回。
Activity.java
NonConfigurationInstances mLastNonConfigurationInstances;
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
final void attach(NonConfigurationInstances lastNonConfigurationInstances ...) {
...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
...
}
static final class NonConfigurationInstances {
Object activity;
HashMap children;
FragmentManagerNonConfig fragments;
ArrayMap loaders;
VoiceInteractor voiceInteractor;
}
可以看出,mLastNonConfigurationInstances
是在Activity的 attach()
方法中被赋值的,所以我们需要往上看Activity的启动流程。
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
Activity activity = null;
activity.attach(r.lastNonConfigurationInstances ...);
r.lastNonConfigurationInstances = null;
......
return activity;
}
也就是说最初始的非配置实例是在ActivityThread
中的ActivityClientRecord
所创建的,当Activity发生配置变更而重建时ActivityClientRecord
是不受影响的,所以这样当屏幕旋转Activity重建(配置变更)时,先是onRetainNonConfigurationInstance()
方法会被调用返回一个包含当前ViewModelStore
的非配置实例对象,然后后续通过getLastNonConfigurationInstance()
方法来获取到该非配置实例,所以保存在其ViewModelStore
中的ViewModel
是不是被清除的。
关于配置变更,这里补充一点。
- 旋转屏幕其实就是一次配置变更,isChangingConfigurations = true。
- 当跳转到另外一个Activity时(Activity正常退出或系统杀死)配置未变更,isChangingConfigurations = false。
为什么要设计成配置变更后ViewModel依然存在
现在我们知道当Activity发生配置变更重建的时,ViewModel是不会被清除的,所以他保存的数据依然存在。
不知你有没有想过,为什么要这么设计呢?
我们知道ViewModel类负责处理的是MVVM架构模式
中ViewModel层
的工作,所以ViewModel保留的是UI状态数据。当你屏幕旋转时,Activity会发生重建工作,将我们的xml布局从portrait
切换到了landscape
,但其实布局中展示的数据是一样的。所以,我们完全不需要再去Model层
重新获取一下数据,直接复用ViewModel中保留的数据即可,从而节省系统开销。
另外,在ViewModel出现之前,当我们想要在Activity发生销毁重建时保留数据需要通过覆写 onSaveInstanceState()
方法来实现,该方法通过Bundle
来将数据序列化保存在磁盘中,实现步骤相对复杂且又有大小限制。相对比,ViewModel将数据保存在内存中,读写速度更快,而且又没有大小限制,所以是一种很好的替代方案。
总结
本篇文章,我们通过ViewModel的使用方法来学习了ViewModel的源码,又对ViewModel的设计进行了进一步思考,就 “它为什么会取名叫做ViewModel
?它跟MVVM
中的ViewModel
是怎么样的一种关系?他又是怎么感知到生命周期的?为什么要设计成屏幕旋转后继续留存ViewModel
?” 这几个问题进行了进一步的学习,相信你现在对ViewModel有了进一步的认识与理解。
到此本篇文章就结束啦,如果你有任何疑问或者不同的想法,欢迎在评论区留言与我一起探讨。
其实分享文章的最大目的正是等待着有人指出我的错误,如果你发现哪里有错误,请毫无保留的指出即可,虚心请教。
另外,如果你觉得文章不错,对你有所帮助,请帮我点个赞,就当鼓励,谢谢~。
推荐
【Android framework教程】2022最新整理178P合集含framework面试题解析(Binder/Handler/AMS/WMS/插件化)_哔哩哔哩_bilibili
2021最新Android程序员中高级进阶学习Framework全套教程(51集大全)/WMS/AMS/Handler/Binder/插件化_哔哩哔哩_bilibili
【Android面试题】2022最新Android中高级大厂高频面试题汇总助力金三银四高薪必备_哔哩哔哩_bilibili
价值100W+Android实战项目大全/高级UI/灵动的锦鲤/QQ空间热修复/插件化框架/组件化框架设计/网络访问框架/RXJava/IOC/MVVM/NDK_哔哩哔哩_bilibili
音视频开发全系列教程_哔哩哔哩_bilibili
Android项目实战-从0开始手把手实现组件化路由SDK项目实战_哔哩哔哩_bilibili
【Android开发教程】一节课解剖Retrofit源码内核_哔哩哔哩_bilibili