彻底掌握Android中的ViewModel
ViewModel 属于Android Jetpack
库的一部分,是一种业务逻辑或屏幕状态容器。它提供了在配置更改(如屏幕旋转)后依旧保留相应状态的特性,帮助开发者以更加清晰和可维护的方式处理UI相关的数据,从而避免了在 Activity 或 Fragment 中直接处理数据持久化的问题。
ViewModel的使用
创建
日常开发中,ViewModel 经常充当 MVVM 架构的 VM 层,分担 Activity/Fragment 的部分逻辑,充当页面的数据存储容器。ViewModel 的创建方式有好几种,官方的 API 也改了几版,ViewModelProviders 已标为废弃,目前创建 ViewModel 统一使用 ViewModelProvider,代码实现有如下几种方式:
//无参构造函数ViewModel
class MyViewModel() : ViewModel() {
//...
}
1.通过ViewModelProdiver
Activity 中:
private val viewModel by lazy {
ViewModelProvider(this).get(MyViewModel::class.java)
}
Fragment 中:
private val viewModel by lazy {
ViewModelProvider(this).get(MyViewModel::class.java) //关联的是Fragment
}
private val viewModel by lazy {
ViewModelProvider(requireActivity()).get(MyViewModel::class.java) //关联的是Activity
}
2.通过Android KTX
KTX 扩展库提供了很多常用功能的简洁实现,KTX 分为若干模块,开发者需要按需引用,这里需要用到 Fragment KTX
模块,首先将该模块代码依赖到工程:
implementation "androidx.fragment:fragment-ktx:1.6.2"
然后就可以用以下方式进行 ViewModel 的创建了,代码非常简洁:
Activity 中:
private val viewModel by viewModels<MyViewModel>()
Fragment 中:
private val viewModel1 by viewModels<MyViewModel>() //关联的是Fragment
private val viewModel2 by activityViewModels<MyViewModel>() //关联的是Activity
3.有参数的ViewModel创建方式
上面两种创建的 ViewModel 构造器都是无参数的,但 ViewModel 有时候也需要依赖注入外部对象,这时 ViewModel 就需要提供有参数的构造器,重点是创建自定义的 ViewModel 的创建工厂。
先看下 UserViewModel 的定义:
//数据仓库层
object UserRepo {
//网络逻辑...
}
//构造器有参的ViewModel
class UserViewModel(val repo: UserRepo) : ViewModel() {
//...
}
一般情况下,重写 ViewModelProvider.Factory 一个参数的 create 方法即可:
class Factory1(val repo: UserRepo): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UserViewModel(repo) as T
}
}
创建代码:
//通过ViewModelProvider方式:
private val viewModel by lazy {
ViewModelProvider(this, UserViewModel.Factory1(UserRepo)).get(UserViewModel::class.java)
}
//通过KTX方式:
private val viewModel by viewModels<UserViewModel>(factoryProducer = { UserViewModel.Factory1(UserRepo) })
ViewModelProvider 中还提供了几个默认工厂:
NewInstanceFactory
:用来创建无参的 ViewModel,也是 ViewModelProvider 的默认工厂。AndroidViewModelFactory
:继承自 NewInstanceFactory ,用来创建构造函数需要 Application 参数的 ViewModel 实例,特殊情况下会调用 NewInstanceFactory 创建无参的 ViewModel。
其实 KTX 最后也是通过 ViewModelProdiver 进行创建的,只不过通过 Kotlin 的属性委托机制将语法简化了,源码如下:
//ViewModel会通过by关键字委托到该类,每次使用该属性时,都会走到get方法中
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory,
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
//最终还是通过ViewModelProvider进行创建的
ViewModelProvider(
store,
factory,
extrasProducer()
).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}
override fun isInitialized(): Boolean = cached != null
}
使用
ViewModel 一般作为 MVVM 架构的 VM 层,可以将 Activity/Fragment 的业务逻辑都封装到 ViewModel 中,比较常见的就是网络请求了。Google 推荐如下方式实现:
class MyViewModel : ViewModel() {
private val _userLiveData: MutableLiveData<User> = MutableLiveData<User>()
val userData: LiveData<User> //外部获取的类型是LiveData,不可变的,防止外部随意修改
get() = _userLiveData
fun doAction() {
//...比如请求网络,并更新user
_userLiveData.postValue(User("白泽..."))
}
}
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
viewModel.userData.observe(this) { //1.订阅数据变化
//update UI
}
findViewById<Button>(R.id.button).setOnClickListener {
viewModel.doAction() //2.触发数据更新
}
}
}
ViewModel是如何存储的
首先来看看 ViewModel 的几个重要类,分别是 ViewMdoel、ViewModelProvider、ViewModelStore 和 ViewModelStoreOwner,关系类图如下:
这不是严格的UML图,只需大概理解即可,下面介绍下每个类的职责:
-
ViewModelProvider
:只负责 ViewModel 的创建。无参构造器的 ViewModel 可以直接用其内部提供的 NewInstanceFactory 工厂创建,如果ViewModel 需要构造器参数,则需要实现 ViewModelProvider.Factory 接口并完善创建逻辑。 -
ViewModelStore
:负责 ViewModel 实例的存储,内部通过 HashMap 实现,map 的 value 就是 ViewMode 的实例,key 的生成规则如下:private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey" //... String canonicalName = modelClass.getCanonicalName(); //返回此类的规范名称,如com.xx.x.MyViewModel get(DEFAULT_KEY + ":" + canonicalName, modelClass); //...
所以同一 ViewModelStore 中,同一个类型的 ViewModel 并不会重复创建。
-
ViewModelStoreOwner
:负责提供ViewModelStore
,常见的 ViewModelStoreOwner 有 ComponentActivity、Fragment 等,它们的内部会对 ViewModelStore 进行管理,在适当的时机进行创建和回收。以 ComponentActivity 为例,其内部会监听生命周期,并在生命周期变动时调用如下代码,确保 mViewModelStore 的存在://1.Activity销毁时调用该方法临时保存ViewModelStore 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; } //2.Activity创建时恢复上次保存的ViewModelStore void ensureViewModelStore() { if (mViewModelStore == null) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { // Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore; //拿到上次保存的ViewModelStore } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); //创建新的ViewModelStore } } }
ViewModelStore
的销毁时机:Activity 走到 Destroy 并且使非配置更改(如正常finish)。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(); } } } });
面试常问的问题:
为什么 Activity 在旋转屏幕时,Activity 对象都发生重建了,但 ViewModel 却还是原来的对象?
ViewModel 是怎么保存和恢复的?
上面两个问题问的其实是
mViewModelStore
的保存和恢复,因为它是持有 ViewModel 实例的仓库。而mViewModelStore
的存储和恢复是通过onRetainNonConfigurationInstance
和getLastNonConfigurationInstance
来实现的。在配置更改时会调用
Activity#onRetainNonConfigurationInstance()
保存mViewModelStore
对象,并在 Activity 重建后通过getLastNonConfigurationInstance
方法获取上次保存的ViewModelStore
对象,如果有则直接使用,否则创建新的实例对象。状态保存:onRetainNonCongigurationInstance
该方法是 Android 提供的在配置更改时,临时保存 Activity 数据的 API。
onRetainNonConfigurationInstance()
允许 Activity 在配置改变之前返回一个对象,这个对象随后可以在 Activity 重新创建后的getLastNonConfigurationInstance()
方法中被检索到。下面是该机制的源码,在设备配置发生更改时(如旋转屏幕),会调用到
ActivityThread#handleRelaunchActivity
方法://看方法名字可以知道是处理Activity重建逻辑的 public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) { //... handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents, pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity"); //... } private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents, PendingTransactionActions pendingActions, boolean startsNotResumed, Configuration overrideConfig, String reason) { //1. 处理旧Activity的销毁,注意第三个参数是getNonConfigInstance,传入的是true handleDestroyActivity(r, false, configChanges, true, reason); //2. 处理Activity新建逻辑 handleLaunchActivity(r, pendingActions, customIntent); }
这个方法处理了两件事,一是旧 Activity 的回收,二是 Activity 的新建。先从 Activity 销毁开始看:
@Override public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason); //... } void performDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { //...注意:getNonConfigInstance为true if (getNonConfigInstance) { try { //调用旧activity的方法,并保存到ActivityClientRecord对象中 r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { //... } } //... }
NonConfigurationInstances retainNonConfigurationInstances() { Object activity = onRetainNonConfigurationInstance(); //1.调用了onRetainNonConfigurationInstance方法 HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); //2.可以缓存一些自定义数据 FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig(); //3.Fragment状态 //... NonConfigurationInstances nci = new NonConfigurationInstances(); nci.activity = activity; nci.children = children; nci.fragments = fragments; nci.loaders = loaders; if (mVoiceInteractor != null) { mVoiceInteractor.retainInstance(); nci.voiceInteractor = mVoiceInteractor; } return nci; }
可以看到,Activity 销毁做了两件事:
- 调用了 Activity 对象的
onRetainNonConfigurationInstance
方法拿到临时对象,并赋值给ActivityClientRecord#lastNonConfigurationInstances
变量。 - 调用 Activity 的 pause、stop、destroy 等生命周期方法。
接下来看 Activity 新建逻辑:
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) { //... final Activity a = performLaunchActivity(r, customIntent); //... } private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { //... Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); //1.通过反射创建Activity对象 activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); } //... try { if (activity != null) { //2.调用 attach 方法,将缓存信息传入 activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.activityConfigCallback, r.assistToken, r.shareableActivityToken); //... return activity; }
创建 Activity 流程同样做了两件事:
- 通过反射创建 Activity 实例对象
- 调用 attach 方法,将销毁时缓存在
ActivityClientRecord#lastNonConfigurationInstances
变量中的临时变量关联到新的 Activity 对象
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken, IBinder shareableActivityToken) { //赋值给了mLastNonConfigurationInstances mLastNonConfigurationInstances = lastNonConfigurationInstances; } public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null; }
执行完 Activity 的 attach 方法后,就可以通过
getLastNonConfigurationInstance
方法获取之前 Activity 销毁时保存的状态数据了,到此 Activity 保存和恢复数据的链路就通了。onRetainNonConfigurationInstance
方法用 final 修饰,并已标为废弃了,其实改保存数据方案在 Android 3.0 就已经废弃了,Google 不希望我们用这个机制去进行 Activity 状态保存,而是推荐用基于该机制上衍生的 ViewModel 进行状态保存,使用起来更简单、更安全。onRetainNonCongigurationInstance 和 onSaveInstanceState区别:
onSaveInstanceState
同样是用于处理 Activity 状态保存和恢复的方法,它与onRetainNonCongigurationInstance
方式区别如下:- 使用场景不同
onRetainNonCongigurationInstance
用于在设备配置更改时(如屏幕旋转)临时保存 Activity 的状态或数据。onSaveInstanceState
用于Activity 即将被销毁时(无论是由于用户离开、配置更改还是系统回收资源),保存 Activity 的状态或数据,是一个更通用、更灵活的状态保存机制。。
- 支持数据类型不同
onRetainNonCongigurationInstance
返回 Object 类型对象,可以是任何类型,包括 Activity 实例本身或大型数据结构,如果使用不当容易造成内存泄漏。onSaveInstanceState
通过 Bundle 对象来保存状态,只能存储基本数据类型、可序列化的对象或实现了 Parcelable 接口的对象,确保数据的安全性和可恢复性。
- 恢复数据方式不同
onRetainNonConfigurationInstance
在Activity重新创建后,可以通过调用getLastNonConfigurationInstance()
方法来检索之前保存的数据。onSaveInstanceState
在Activity重新创建时,系统会将之前保存的Bundle对象传递给onCreate(Bundle savedInstanceState)
或onRestoreInstanceState(Bundle savedInstanceState)
方法。
- 调用了 Activity 对象的
ViewModel的协程作用域
协程是 Kotlin 的又一高效编程利器,使用协程可以非常简单的进行多线程协作。ViewModel 提供了和其生命周期一致协程作用域,可以引入 KTX 的 ViewModel 模块,让使用更简单:
dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6"
}
使用时:
viewModel.viewModelScope.launch {
if (isActive) { //判断协程是否被取消了
//doAction...
}
}
下面来探索一下 ViewModel 协程作用域是如何管理的,首先看 viewModelScope 源码:
public val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY) //1.缓存的协程作用域对象
if (scope != null) {
return scope
}
//2.创建协程
return setTagIfAbsent(
JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
)
}
可以看到,如果没从缓存获取到则直接通过 setTagIfAbsent 方法创建,协程上下文类型是 SupervisorJob
和 Dispatchers.Main
,可以让发生异常时不影响其他协程,并且代码运行在主线程。
private final Map<String, Object> mBagOfTags = new HashMap<>();
//1.setIfAbsend 系列方法一般表示,如果存在旧值就不进行赋值了,防止多次创建
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
//2.通过同步锁,防止多线程场景错误
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
通过该方法可以看到,ViewModel 的协程作用域创建后会缓存在 ViewModel 中的 mBagOfTags 中,这是一个 map 结构,key 为 JOB_KEY
,value 为协程作用域对象。
知道了 ViewModel 的协程作用域是如何创建和保存的,下面看协程是如何取消的。
还记得上面 ViewModel 对象的保存逻辑吗,ComponentActivity 会监听 DESTROY
生命周期,Activity 正常销毁时,会执行 ViewModelStore 的 clear
方法,ViewModelStore 又会遍历所有保存的 ViewModel 对象,并调用其 clear
方法。
final void clear() {
mCleared = true;
//1.加锁,进行协程作用域的取消
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
//2.取消协程
closeWithRuntimeException(value);
}
}
}
//2.ViewModel销毁时会调用该方法,可以重写它进行长时间任务的清理工作
onCleared();
}
可见最终会调用到 closeWithRuntimeException
方法进行协程取消,内部其实是 coroutineContext.cancel()
ViewModel 的协程作用域会在获取时进行创建,并缓存在 ViewModel 的 mBagOfTags 映射表内部,在 ViewModel 销毁时取消。协程取消并不会强制终端代码逻辑,使用 ViewModel 协程作用域进行长时间任务时,注意使用 isActive
方法适时判断协程是否被取消了。
ViewModel 的协程作用域和 Lifecycle 的协程作用域有何区别?
Android 中除了 ViewModel 提供了协程作用域外,Lifecycle 也提供了 lifecycleScope 协程作用域,首先看下 Lifecycle 的协程作用域是如何创建、保存和销毁的:
//Lifecycle:
//1.AtomicReference让对象读,写都是原子操作,保证修改对象引用时的线程安全
public var internalScopeRef: AtomicReference<Any> = AtomicReference<Any>()
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = internalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
//2.创建协程作用域
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
//3.通过CAS机制设置对象
if (internalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
internal class LifecycleCoroutineScopeImpl(
override val lifecycle: Lifecycle,
override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
init {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
coroutineContext.cancel() //1.取消协程
}
}
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel()
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel() //2.取消协程
}
}
}
Lifecycle 的代码非常简单,同样在获取时创建协程作用域,并通过CAS机制保存到 internalScopeRef 对象引用,在 DESTROY 时进行协程的销毁操作。
通过上面分析 ViewModel 和 Lifecycle 的协程作用域相关代码,可以分析出以下几点区别:
- ViewModel 协程作用域创建时通过 synchronized 同步锁保证线程安全;Lifecycle 协程作用域创建时通过 while 循环 + CAS 机制保证线程安全。相对来说 CAS 机制更能保证效率,ViewModel 使用 synchronized,主要还是因为其中的 mBagOfTags,它是一个Map,Android 官方因为一些旧系统的限制,导致无法使用ConcurrentHashMap,所以才出此下策。
- ViewModel 和 Lifecycle 的协程作用域生命周期不同,因为销毁时机不一样,就像刚开始 ViewModel 的生命周期和 Activity 生命周期一样。
总结
Android 的 ViewModel 是一个强大的架构组件,它通过提供数据、管理状态以及生命周期感知等能力,帮助开发者构建更加健壮、易于维护和测试的应用。在 MVVM 代码架构中,ViewModel 是视图(View)与数据(Model)之间的桥梁,它负责为 UI组件提供数据,并管理 UI组件的状态,UI状态与业务逻辑分离,使得代码耦合性更低,更易于测试。