输出倒逼输入
背景:
在android mvvm架构中,在跨Activity的复杂场景下,数据流通成了一种难题,我们希望有一种比较合理的方式解决,它应该兼容内存安全、开发成本低、又能跟现有的架构比较契合。
当前有几种方式支持跨Activity数据传递:
-
单例或者类似Manager管理类进行缓存传递,缺点是无法感知生命周期,且容易内存泄漏;
-
Intent传递,缺点是只能传递parcelable类型数据,有数据大小限制,需要序列化,且无法双向通信;
-
EventBus事件等框架,优点是简单并且解耦,缺点是无法感知生命周期,容易滥用,经常导致内存泄漏;
解决方案:
-
基于原有的ViewModel进行改造扩展,允许两个Activity传递共享同一个ViewModel;改造成本低,上手成本低;
-
共享的ViewModel的生命周期为多个Activity的生命周期的并集;当所有的Activity销毁之后,共享的ViewModel也会被销毁,不会导致内存泄漏等问题;
改造方案:
- 我们都知道ViewModel的使用:
// MyActivity.kt
val myViewModel = ViewModelProvider(context).get(MyViewModel::class.java)
- 如果两个Activity可以共享ViewModel,那跨Activity传递复杂数据也变得相对简单:
// MyActivity.kt
val myViewModel = GlobalViewModelProvider(context).get(MyViewModel::class.java)
// otherActivity:两个myViewModel共享一个实例,达到传递数据的目录
val myViewModel = GlobalViewModelProvider(context).get(MyViewModel::class.java)
怎么实现呢?
class GlobalViewModelProvider: ViewModelProvider {
companion object {
// 全局共享的viewModelStore
private val globalStore = ViewModelStore()
// ViewModel的引用计数
private val referenceCount = HashMap<String, Int>()
}
private val store: ViewModelStore
constructor(store: ViewModelStore): this(store, NewInstanceFactory())
constructor(store: ViewModelStore, factory: Factory): super(globalStore, factory) {
this.store = store
}
@MainThread
override fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
// 获取缓存model,并存入到globalStore中
val model = super.get(key, modelClass)
// 引用计数+1
model.increaseReference()
// 监听store的生命周期
val listener = object: ViewModel() {
override fun onCleared() {
super.onCleared()
// 引用计数-1
model.decreaseReference()
// 当viewModel没有被引用时候,从全局store移除,并调用ViewModel的onCleared()
if (model.referenceCount() == 0) {
globalStore.put(key, null)
}
}
}
store.put(key, listener)
return model
}
private fun ViewModel.increaseReference() {
val key = this.hashCode().toString()
referenceCount[key] = referenceCount() + 1
}
private fun ViewModel.decreaseReference() {
val key = this.hashCode().toString()
referenceCount[key] = maxOf(0, referenceCount() - 1)
}
private fun ViewModel.referenceCount(): Int {
val key = this.hashCode().toString()
return referenceCount[key] ?: 0
}
}
特点&不足:
- 没有考虑跨进程、activity摧毁重建等异常case;
- 上一个Activity被销毁时,下一个Activity还没获取ViewModel,此时共享的viewmodel可能已经被析构了;
- ViewModel生命周期容易被扩大,导致被滥用,甚至无法被销毁;
- ViewModelStore的get/put方法是package级别的,可能需要利用反射等方式调用;