如何让不同Activity之间共享同一个ViewModel
问题背景
存在一个场景,在Acitivity1可以跳转到Activity2,但是两个Activty之间希望能共享数据
提出假设的手段
可以定义一个ViewModel,让这两个Activity去共享这个ViewModel
存在的问题
根据不同的LifecycleOwner创建出来的ViewModel是不同的实例,所以在两个不同的Activity之间无法创建同一个ViewModel对象
问题分析
先来梳理一下一个正常的ViewModel是怎么被构造出来的:
- ViewModel是由ViewModelFactoty负责构造出来
- 构造出来之后,存储在ViewModelStore里面
但是问题是ViewModelStore是 和 (宿主Activity或者Fragment)是一一对应的关系
具体代码如下
@MainThread
public inline fun <reified VM : ViewModel> Fragment.viewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this },
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val owner by lazy(LazyThreadSafetyMode.NONE) { ownerProducer() }
return createViewModelLazy(
VM::class,
{ owner.viewModelStore },
{
(owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
?: CreationExtras.Empty
},
factoryProducer ?: {
(owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelProviderFactory
?: defaultViewModelProviderFactory
})
}
看到上面的代码第9行,viewModelStore和owner是对应关系,所以原则上根据不同LifecycleOwner无法构造出同一个ViewModel对象
解决思路
- 无法在不同的LifecycleOwner之间共享ViewMode对象的原因是:ViewModel的存储方ViewModelStore是和LifecycleOwner绑定,那如果可以解开这一层绑定关系,理论上就可以实现共享;
- 另外我们需要定义ViewModel的销毁时机:
我们来模拟一个场景:由Activty1跳转到Activity2,然后两个Activity共享同一个ViewModel,两个activity都要拿到同一个ViewModel的实例,那这个时候ViewModel的销毁时机应该是和Acitivity1的生命周期走,也就是退出Activity1(等同于Activity1走onDestroy)的时候,去销毁这个ViewModel。
所以按照这个思路走,ViewModel需要在activity1中被创建出来,并且保存在一个特定的ViewModelStore里面,要保证这个ViewModelStore可以被这两个Activity共享;
然后等到Activity2取的时候,就直接可以从这个ViewModelStore把这个ViewModel取出来;
最后在Activity1进到destroy的时候,销毁这个ViewModel
具体实现
重写一个ViewModelProvider实现如下功能点:
- 把里面的ViewModelStore定义成一个单例供所有的LifecycleOwner共享
- 定义ViewModel的销毁时机: LifecycleOwner走到onDestroy的时机
// 需要放到lifecycle这个包,否则访问不到ViewModelStore
package androidx.lifecycle
class GlobalViewModelProvider(factory: Factory = NewInstanceFactory()) :
ViewModelProvider(globalStore, factory) {
companion object {
private val globalStore = ViewModelStore()
private val globalLifecycleMap = HashMap<String, MutableSet<Lifecycle>>()
private const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
}
@MainThread
fun <T: ViewModel> get(lifecycle: Lifecycle, modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get(lifecycle, "$DEFAULT_KEY:$canonicalName", modelClass)
}
@MainThread
fun <T: ViewModel> get(lifecycle: Lifecycle, key: String, modelClass: Class<T>): T {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
throw IllegalStateException("Could not get viewmodel when lifecycle was destroyed")
}
val viewModel = super.get(key, modelClass)
val lifecycleList = globalLifecycleMap.getOrElse(key) { mutableSetOf() }
globalLifecycleMap[key] = lifecycleList
if (!lifecycleList.contains(lifecycle)) {
lifecycleList.add(lifecycle)
lifecycle.addObserver(ClearNegativeVMObserver(lifecycle, key, globalStore, globalLifecycleMap))
}
return viewModel
}
private class ClearNegativeVMObserver(
private val lifecycle: Lifecycle,
private val key: String,
private val store: ViewModelStore,
private val map: HashMap<String, MutableSet<Lifecycle>>,
): LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
val lifecycleList = map.getOrElse(key) { mutableSetOf() }
lifecycleList.remove(lifecycle)
if (lifecycleList.isEmpty()) {
store.put(key, null)
map.remove(key)
}
}
}
}
}
具体使用
@MainThread
inline fun <reified VM: ViewModel> LifecycleOwner.sharedViewModel(
viewModelClass: Class<VM> = VM::class.java,
noinline keyFactory: (() -> String)? = null,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null,
): Lazy<VM> {
return SharedViewModelLazy(
viewModelClass,
keyFactory,
{ this },
factoryProducer ?: { ViewModelProvider.NewInstanceFactory() }
)
}
@PublishedApi
internal class SharedViewModelLazy<VM: ViewModel>(
private val viewModelClass: Class<VM>,
private val keyFactory: (() -> String)?,
private val lifecycleProducer: () -> LifecycleOwner,
private val factoryProducer: () -> ViewModelProvider.Factory,
): Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
return cached ?: kotlin.run {
val factory = factoryProducer()
if (keyFactory != null) {
GlobalViewModelProvider(factory).get(
lifecycleProducer().lifecycle,
keyFactory.invoke(),
viewModelClass
)
} else {
GlobalViewModelProvider(factory).get(
lifecycleProducer().lifecycle,
viewModelClass
)
}.also {
cached = it
}
}
}
override fun isInitialized() = cached != null
}
场景使用
val vm : MainViewModel by sharedViewModel()
作者:红鲤驴
来源:juejin.cn/post/7366913974624059427
来源:juejin.cn/post/7366913974624059427