ViewModel

1.什么是VeiwModel

ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

简单来说就是保存activity和fargment页面的数据的。

何谓配置变更?

配置变更指的是,应用在运行时,内置的配置参数变更从而触发的Activity重新创建

常见的场景有:旋转屏幕、深色模式切换、屏幕大小变化、更改了默认语言或者时区、更改字体大小或主题颜色等。

何谓异常重建?

异常重建指的是非配置变更情况下导致的 Activity 重新创建。

常见场景大多是因为 内存不足,从而导致后台应用被系统回收 ,当我们切换到前台时,从而触发的重建,这个机制在Android中为 Low Memory Killer 机制,简称 LMK

可以在开发者模式,限制后台任务数为1,从而测试该效果。

2.为什么需要ViewModel?

ViewModel 出现之前,对于 View 逻辑与数据,我们往往都是直接存在 Activity 或者 Fragment 中,优雅一点,会细分到具体的单独类中去承载。当配置变更时,无可避免,会触发界面重绘。相应的,我们的数据在没有额外处理的情况下,往往也会被初始化,然后在界面重启时重新加载。

但如果当前页面需要维护某些状态不被丢失呢,比如 选择、上传状态 等等? 此时问题就变得棘手起来。

稍有经验同学会告诉你,在 onSaveInstanceState 中重写,使用bundle去存储相应的状态啊?

但状态如果少点还可以,多一点就非常头痛,更别提包含继承关系的状态保存。

所以就有了ViewModel,但ViewModel并不是onSaveInstanceState()的替代品

随着 ViewModel 组件推出之后,上述因配置变更而导致的状态丢失问题就迎刃而解。

ViewModel 可以做到在配置变更后依然持有状态。所以,在现在的开发中,我们开始将 View数据 与 逻辑 藏于 ViewModel 中,然后对外部暴漏观察者,比如我们常常会搭配 LiveData 一起使用,以此更容易的保持状态同步。这其实就很接近之后要讲的MVVM架构。

关于 ViewModel 的生命周期,具体如下图所示:

虽然 ViewModel 非常好用,但 ViewModel 也不是万能,其只能避免配置变更时避免状态丢失。比如如果我们的App是因为 内存不足 而被系统kill 掉,此时 ViewModel 也会被清除 。

不过对于这种情况,仍然有以下三个方法可以依然保存我们的状态:

  • 重写 onSaveInstanceState()onRestoreInstanceState();
  • 使用 SavedState,本质上其实还是 onSaveInstanceState()
  • 使用 SavedStateHandle ,本质上是依托于 SaveState 的实现;

上述的后两种都是随着 JetPack 逐步被推出,可以理解为是对原有的onSavexx的封装简化,从而使其变得更易用。

3.ViewModel的使用

首先肯定是导入viewmodel的依赖

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"

然后定义viewmodel,一般情况我们都是结合LiveData一起使用:

/**
* ...
* @author RQ527 (Ran Sixiang)
* @email 1799796122@qq.com
* @date 2023/3/19
* @Description:
*/
class TestViewModel : ViewModel() {

val homeLiveData: LiveData<String>
get() = _mutableHomeLiveData
private val _mutableHomeLiveData = MutableLiveData<String>()

fun getHomeData() {
//这里从网络或者本地获取数据

_mutableHomeLiveData.value = "我获取到了数据"
}

}

一般是不建议viewmodel暴露mutableLivedata给activtiy,而是内部使用mutableLiveData去修改值,然后转化成LiveData暴露给activity。

注:为了保证Activity不被泄漏,不要在Activity传回调或者view进去,也不要在Viewmodel导入android.的包,(android.arch.除外),这样做是为了不要让ViewModel知晓Android的FrameWork,ViewModel只是用来写点逻辑代码和存数据的。

然后我们在activity去获取viewmodel:

class MainActivity : AppCompatActivity() {

private val mBinding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }

private val mViewModel by lazy {ViewModelProvider(this)[TestViewModel::class.java]}//懒加载

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)

mViewModel.homeLiveData.observe(this) {
mBinding.textView.text = it
}
mBinding.button.setOnClickListener {
mViewModel.getHomeData()
}
}

}

首先我们用ViewmodelProvider传了lifecycleowner进去,传我们自定义的viewmodel类。创建viewmodel大概就是这样。然后在下面去观察livedata,点击一个按钮获取数据。这是正确的创建viewmodel的方法,你直接new 的话是不正确的,完全没有效果。

ktx提供了很多的扩展函数简化我们创建Viewmodel的步骤,我们先导入依赖:

implementation"androidx.activity:activity-ktx:1.6.1"//activity
implementation"androidx.fragment:fragment-ktx:1.5.5"//fragment

然后我们的viewmodel就可以这么创建了:

//1
private val mViewModel by viewModels<TestViewModel>()

//2
private val mViewModel by viewModels<TestViewModel>{TestViewModelFactory()}

是不是比之前精简了很多,其本质也是调用ViewModelProvider.get。

假如你在viewmodel里需要application的话,可以使用AndroidVeiwModel:

class TestViewModel(application: Application) : AndroidViewModel(application) {

val homeLiveData: LiveData<String>
get() = _mutableHomeLiveData
private val _mutableHomeLiveData = MutableLiveData<String>()

val mApplication: Application
get() = getApplication()

fun getHomeData() {
Toast.makeText(mApplication, "aaaa", Toast.LENGTH_SHORT).show()
//这里从网络或者本地获取数据

_mutableHomeLiveData.value = "我获取到了数据"
}

}

继承自AndroidViewModel,然后getApplication就可以获取application了。

在Fragment里面的用法和fragment是一样的,就不再说了。同时,一个viewmodel多个Fragment或者activity可以共享。

假如viewmodel需要传参怎么办?有人会说直接这样:

class TestViewModel(val tag: String) : ViewModel() {

val homeLiveData: LiveData<String>
get() = _mutableHomeLiveData
private val _mutableHomeLiveData = MutableLiveData<String>()

fun getHomeData() {
//这里从网络或者本地获取数据

_mutableHomeLiveData.value = "我获取到了数据"
}

}

但是你怎么去创建viewmodel实例呢?要知道我们获取viewmodel是这样的:

private val mViewModel by lazy {ViewModelProvider(this)[TestViewModel::class.java]}

这样去创建viewmodel默认是无参数的,肯定会出错。那怎么办呢?

其实我们的ViewModelProvider的构造函数有三个:

第二参数是ViewModelProvider.Factory,是干什么的呢?用来实例化viewmodel的,默认是无参的,所以要传参我们肯定要自定义一个ViewModelProvider.Factory:

class TestViewModelFactory(val tag: String) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val testViewModel = TestViewModel(tag)
return testViewModel as T
}
}

创建一个有参数的Factory,实现ViewModelProvider.Factory这个接口然后创建ViewModel实例再把参数传进去就可以了。最后在activtiy获取viewmodel的时候把参数传进去:

private val mViewModel by lazy {
ViewModelProvider(
this,
TestViewModelFactory("我传了个参数")
)[TestViewModel::class.java]
}

这里我们new了一个我们自定义的Factory实例并把参数传了进去,这个参数你们可以自定义也可以从别的地方来,具体看你们的需求。

刚刚我们在上面提到了,如果我们的App是因为 内存不足 而被系统kill 掉,此时 ViewModel 也会被清除 。这时候我们可以:

  • 重写 onSaveInstanceState()onRestoreInstanceState();
  • 使用 SavedState,本质上其实还是 onSaveInstanceState()
  • 使用 SavedStateHandle ,本质上是依托于 SaveState 的实现;

上述的后两种都是随着 JetPack 逐步被推出,可以理解为是对原有的onSavexx的封装简化,从而使其变得更易用。

方法本质都是用onSaveInstanceState()我们就讲个方便的SavedStateHandle

首先导入依赖:

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1"

然后稍微修改一下ViewModel:

class TestViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

val homeLiveData: LiveData<String>
get() = _mutableHomeLiveData
private val _mutableHomeLiveData = MutableLiveData<String>()

companion object {
const val MY_KEY = "MY_KEY"
}

fun getHomeData() {
//这里从网络或者本地获取数据

_mutableHomeLiveData.value = "我传了个数据"
}

fun saveData() {
savedStateHandle[MY_KEY] = "我保存了一个数据"
}

fun getData(): String? {
return savedStateHandle[MY_KEY]
}

}

然后在Activity中修改一下viewmode的创建方式:

private val mViewModel by lazy {ViewModelProvider(this,SavedStateViewModelFactory(application, this))[TestViewModel::class.java]}

就是传一个SavedStateViewModelFactory然后把application和当前activity传进去。之后就可以调方法保存数据获取数据了。

4.详解viewmodel

大概的用法讲完了,我们看看viewmodel是怎么从出生到死亡的。

ViewModelProvider

我们先从获取viewmodel的实例说起,也就是:

private val mViewModel by lazy { ViewModelProvider(this)[TestViewModel::class.java] }

我们把当前的activity传进了ViewmodelProvider的构造函数,我们看一看

/**
* Creates `ViewModelProvider`. This will create `ViewModels`
* and retain them in a store of the given `ViewModelStoreOwner`.
*
*
* This method will use the
* [default factory][HasDefaultViewModelProviderFactory.getDefaultViewModelProviderFactory]
* if the owner implements [HasDefaultViewModelProviderFactory]. Otherwise, a
* [NewInstanceFactory] will be used.
*/
public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

/**
* Creates `ViewModelProvider`, which will create `ViewModels` via the given
* `Factory` and retain them in a store of the given `ViewModelStoreOwner`.
*
* @param owner a `ViewModelStoreOwner` whose [ViewModelStore] will be used to
* retain `ViewModels`
* @param factory a `Factory` which will be used to instantiate
* new `ViewModels`
*/
public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
owner.viewModelStore,
factory,
defaultCreationExtras(owner)
)

其实就是调用了三个参数的构造方法,首先就是viewModelStoreOwner,那我们的activity肯定实现了这个接口,我们先不管,往后看。之后是构造默认的Factory:

internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
if (owner is HasDefaultViewModelProviderFactory)
owner.defaultViewModelProviderFactory else instance

首先看是否实现了HasDefaultViewModelProviderFactory,否则返回instance,instance就是NewInstanceFactory:

@JvmStatic
public val instance: NewInstanceFactory
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
get() {
if (sInstance == null) {
sInstance = NewInstanceFactory()
}
return sInstance!!
}

而NewInstanceFactory就是new 一个viewmodel的实例:

public open class NewInstanceFactory : Factory {
@Suppress("DocumentExceptions")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return try {
modelClass.newInstance()
} catch (e: InstantiationException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
}

那我们看看实现了HasDefaultViewModelProviderFactory的情况,我们的MainActivity继承自AppCompatActivity,AppCompatActivity继承自FragmentActivity,FragmentActivity继承自ComponentActivity,而ComponentActivity实现了HasDefaultViewModelProviderFactory接口的:

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
ContextAware,
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
ActivityResultCaller,
OnConfigurationChangedProvider,
OnTrimMemoryProvider,
OnNewIntentProvider,
OnMultiWindowModeChangedProvider,
OnPictureInPictureModeChangedProvider,
MenuHost {
.........
@NonNull
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
if (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
getApplication(),
this,
getIntent() != null ? getIntent().getExtras() : null);
}
return mDefaultFactory;
}
.........
}

可以看到就是返回SavedStateViewModelFactory,那我们来看看它的create干了什么:

override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
?: throw IllegalStateException(
"VIEW_MODEL_KEY must always be provided by ViewModelProvider"
)

return if (extras[SAVED_STATE_REGISTRY_OWNER_KEY] != null &&
extras[VIEW_MODEL_STORE_OWNER_KEY] != null) {
val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]
val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)
val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {
findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
} else {
findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
}
// doesn't need SavedStateHandle
if (constructor == null) {
return factory.create(modelClass, extras)
}
val viewModel = if (isAndroidViewModel && application != null) {
newInstance(modelClass, constructor, application, extras.createSavedStateHandle())
} else {
newInstance(modelClass, constructor, extras.createSavedStateHandle())
}
viewModel
} else {
val viewModel = if (lifecycle != null) {
create(key, modelClass)
} else {
throw IllegalStateException("SAVED_STATE_REGISTRY_OWNER_KEY and" +
"VIEW_MODEL_STORE_OWNER_KEY must be provided in the creation extras to" +
"successfully create a ViewModel.")
}
viewModel
}
}

其实就是先判断SAVED_STATE_REGISTRY_OWNER_KEY和VIEW_MODEL_STORE_OWNER_KEY存不存在,不存在就创建,方法大同小异,一般是存在的情况,我们来看看,先根据是不是AndroidViewModel去findMatchingConstructor,而findMatchingConstructor是:

private val ANDROID_VIEWMODEL_SIGNATURE = listOf<Class<*>>(
Application::class.java,
SavedStateHandle::class.java
)
private val VIEWMODEL_SIGNATURE = listOf<Class<*>>(SavedStateHandle::class.java)

// it is done instead of getConstructor(), because getConstructor() throws an exception
// if there is no such constructor, which is expensive
internal fun <T> findMatchingConstructor(
modelClass: Class<T>,
signature: List<Class<*>>
): Constructor<T>? {
for (constructor in modelClass.constructors) {
val parameterTypes = constructor.parameterTypes.toList()
if (signature == parameterTypes) {
@Suppress("UNCHECKED_CAST")
return constructor as Constructor<T>
}
if (signature.size == parameterTypes.size && parameterTypes.containsAll(signature)) {
throw UnsupportedOperationException(
"Class ${modelClass.simpleName} must have parameters in the proper " +
"order: $signature"
)
}
}
return null
}

其实就是根据你自定义的ViewModel的构造参数去决定给你什么构造函数,你需要application就给你有application的构造函数,你需要saveStateHandle就给你saveStateHandle的构造函数。所以默认的Factory就是根据你是否需要application或者saveStateHandle去反射给你创建实例。

那好,现在构造函数我们搞清楚了,我们看看我们传进去的TestViewModel::class.java干了什么,那它肯定重写了操作符get,我们看看:

@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this `ViewModelProvider`.
*
* The created ViewModel is associated with the given scope and will be retained
* as long as the scope is alive (e.g. if it is an activity, until it is
* finished or process is killed).
*
* @param key The key to use to identify the ViewModel.
* @param modelClass The class of the ViewModel to create an instance of it if it is not
* present.
* @return A ViewModel that is an instance of the given type `T`.
*/
@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
// AGP has some desugaring issues associated with compileOnly dependencies so we need to
// fall back to the other create method to keep from crashing.
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}

就是调用了两个参数的get方法,第一个参数就是我们viewmodel的包名以此保证唯一性,第二个参数就是我们传进来的TestViewmodel。两个参数的get方法先从store里去获取viewmodel,这个sotre是ViewModelstore,是用来管理viewmodel的,等会说。然后判断你是否继承了抽象类viewmodel,如果存在就直接给你返回。否则,在最后调用factory的create方法去给你new 一个viewmodel的实例,然后存在viewmodelstore里面。这就是为什么我们直接去new ViewMdoel没用的原因,虽然不会报错,但是它就是一个普通的类,没啥用。

ViewModelStore

那viewmodelstore是个啥玩意儿呢?在上面我们知道,viewmodel的存取都是在viewmodelStore里面的,那viewmodel的生死消亡都跟viewmodelstore有关。我们刚刚分析ViewModelProvider的构造函数的时候只是分析了第二个参数factory,第一个参数我们还没分析我们看看:

public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

我们的activity肯定实现了ViewModelStoreOwner,分析它的继承关系可以发现,componentActivity实现了这个接口,我们看看它的getViewModelStore方法:

@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}

@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}

先是尝试从NonConfigurationInstances中去获取viewmodelstore,获取不到就直接new ,那NonConfigurationInstances是干什么的呢?它是Android官方提供给因配置改变而要保存的数据的。那在什么时候保存的呢?在onRetainNonConfigurationInstance()中。而Activity重建的时候生命周期:onPause —-> onSaveInstanceState —->onStop —>onRetainCustomNonConfigurationInstance —> onDestroy —> onCreate —> onStart —-> onRestoreInstanceState —-> onResume onStop和onDeatory的时候会在onRetainCustomNonConfigurationInstance (后面简称onRCNC) 中保存的自定义非配置实例。他的前身是onRetainNonConfigurationInstance(简称onRNC),但是现在onRCNC已经被废弃,onRNC被final修饰不能被复写,官方更推荐我们用viewmodel去保存非配置实例,而在onRetainNonConfigurationInstance()中:

@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();

ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
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;
}

很简单,就是先看看有没有已经保存的NonConfigurationInstances,有就直接把里面的viewmodelstore拿出来再存在新的NonConfigurationInstances里面,否则就创建一个viewmodel,那viewmode和viewmodelStore的存取我们都清楚了,而viewmodelStore也只是个map来保存viewmodel的:

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);
}

Set<String> keys() {
return new HashSet<>(mMap.keySet());
}

/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}

那他的clear方法在哪里调用的呢?也就是说viewmodel是在啥时候被清除的?肯定跟ComponentActivity的生命周期有关,研究他的生命周期你会发现,在初始化中有这么一段代码:

public ComponentActivity() {

......
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});

........
}

利用lifecycle的特性当activity即将detory的时候,调用isChangingConfigurations()去判断是否能因配置变更而导致的destory,如果是正常detroy的话就调用viewmodelstore.clear真正地清除viewmodel。

至此viewmodel的出生和消亡我们就分析结束了,在lifecycle的基础上他的实现还是很简单的。