【进阶】跨页面/Activity数据共享ViewModel

2,330 阅读2分钟

输出倒逼输入

背景:

在android mvvm架构中,在跨Activity的复杂场景下,数据流通成了一种难题,我们希望有一种比较合理的方式解决,它应该兼容内存安全、开发成本低、又能跟现有的架构比较契合。

当前有几种方式支持跨Activity数据传递:

  1. 单例或者类似Manager管理类进行缓存传递,缺点是无法感知生命周期,且容易内存泄漏;

  2. Intent传递,缺点是只能传递parcelable类型数据,有数据大小限制,需要序列化,且无法双向通信;

  3. EventBus事件等框架,优点是简单并且解耦,缺点是无法感知生命周期,容易滥用,经常导致内存泄漏;

解决方案:

  1. 基于原有的ViewModel进行改造扩展,允许两个Activity传递共享同一个ViewModel;改造成本低,上手成本低;

  2. 共享的ViewModel的生命周期为多个Activity的生命周期的并集;当所有的Activity销毁之后,共享的ViewModel也会被销毁,不会导致内存泄漏等问题;

改造方案:

  1. 我们都知道ViewModel的使用:
// MyActivity.kt
val myViewModel = ViewModelProvider(context).get(MyViewModel::class.java)
  1. 如果两个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
    }
}

特点&不足:

  1. 没有考虑跨进程、activity摧毁重建等异常case;
  2. 上一个Activity被销毁时,下一个Activity还没获取ViewModel,此时共享的viewmodel可能已经被析构了;
  3. ViewModel生命周期容易被扩大,导致被滥用,甚至无法被销毁;
  4. ViewModelStore的get/put方法是package级别的,可能需要利用反射等方式调用;