安卓ViewModel作用域与数据共享

一、基本作用域

安卓ViewModel模型将数据与界面分开,对于只有简单界面也许看不出作用。但当界面复杂,与数据相关的界面元素多了后,ViewModel就体现出重大作用,极大提高了程序的可扩展性和可维护性。

最基本的ViewModel用法是与界面Activity或者Fragment一一对应。一个Activity(或Fragment)类对应一个ViewModel类。如下面的代码

class HomeFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,  savedInstanceState: Bundle?
    ): View {
        val homeViewModel =
            ViewModelProvider(this)[HomeViewModel::class.java]
   .......

  // 或者简化成
  val homeViewModel:HomeViewModel by viewModels()

这种情况下,ViewModel与Activity(或Fragment)生命周期相同,且两个同类的Activity(或Fragment)之间使用的ViewModel对象也不同,两个Activity(或Fragment)之间的数据是隔离的。

二、Activity作用域

很多时候,我们需要在一个Activity的多个Fragment中共享数据,这时可以通过改变ViewModel的作用域实现,多个Fragment使用同一个ViewModel。

private val homeViewModel:HomeViewModel by activityViewModels()
//或者
private val homeViewModel= 
          ViewModelProvider(activity!!)[HomeViewModel::class.java]

在这种情况下,同一个Activity所属的不同Fragment将共享同一个ViewModel,从而实现了在多个Fragment共享数据。

三、理解 ViewModelStoreOwner

前面两种用法ViewModel作用域还都是在一个Activity内。不同Activity据此创建的ViewModel 即使类型相同,对象也不同,无法共享其中数据。如果需要在多个不同的Activity内共享ViewModel,就需要自建ViewModelStoreOwner。理论上,同一个ViewModelStoreOwner 创建出来的同一个ViewModel类的对象也是同一个。

因此,简单的做法就是将 类MainActivity 的对象mainActivity 记录在全局变量里,然后在需要共享ViewModel的Activity里使用 mainActivity 作为ViewModelStoreOwner。

private val homeViewModel= 
          ViewModelProvider(mainActivity)[HomeViewModel::class.java]

这样,在使用mainActivity作为ViewModelStoreOwner的Activity里,就能共享同一个homeViewModel对象了。

更强大的方法是,自己新建一个ViewModelStoreOwner类

val vMStores = HashMap<String, SharedViewModelStore>()

inline fun <reified VM : ViewModel> LifecycleOwner.shareViewModels(
    scopeName:String,
    factory: ViewModelProvider.Factory?=null
):Lazy<VM> {
    val store: SharedViewModelStore
    if (vMStores.keys.contains(scopeName)) {
        store = vMStores[scopeName]!!
    } else {
        store = SharedViewModelStore()
        vMStores[scopeName] = store
    }
    store.register(this)
    return ViewModelLazy(VM::class,{store.viewModelStore},{factory?:ViewModelProvider.NewInstanceFactory()})
}

class SharedViewModelStore : ViewModelStoreOwner {

    private val bindTargets = ArrayList<LifecycleOwner>()
    private var vmStore: ViewModelStore? = null

    fun register(host: LifecycleOwner) {
        if (!bindTargets.contains(host)) {
            bindTargets.add(host)
            host.lifecycle.addObserver(object : LifecycleEventObserver {
                override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                    if (event == Lifecycle.Event.ON_DESTROY) {
                        host.lifecycle.removeObserver(this)
                        bindTargets.remove(host)
                        if (bindTargets.isEmpty()) {//如果当前商店没有关联对象,则释放资源
                            vMStores.entries.find { it.value == this@SharedViewModelStore }?.also {
                                vmStore?.clear()
                                vMStores.remove(it.key)
                            }
                        }
                    }
                }
            })
        }
    }

    override fun getViewModelStore(): ViewModelStore {
        if (vmStore == null)
            vmStore = ViewModelStore()
        return vmStore!!
    }
}

使用方法就是

private val viewModel:HomeViewModel by shareViewModels("mainscope")

在这种情况下,可以使用字符串定义共享域,按需控制共享ViewModel。使用同一个字符串”mainscope”创建同类ViewModel,则其对象是同一个。

这种方法创建的ViewModel完美解决了跨Activity共享ViewModel的问题。

四、延长ViewModel生命周期

上面的方法只是修改了ViewModel的作用域,使其能够被共享。但是,这些ViewModel的生命周期都是绑定了UI界面。当用户点击手机上的后退键时(鸿蒙系统就是左滑),由于Fragment被销毁了,其绑定的ViewModel也被销毁了,数据就丢失了。当多次后退,导致主Activity退出时,绑定主Activity的ViewModel也会被销毁。这时,虽然在应用列表里看程序还在内存,但实际上所有绑定UI的ViewModel都被销毁了。当通过应用列表将该程序选择到前台时,所有ViewModel都要重建。

在这种情况下还需保留数据,谷歌建议是保存的实例状态到磁盘,在重建时读回。具体方法,详见官方文档《ViewModel 的已保存状态模块》

这里,要介绍另一种解决方案,就是使用与Application关联的ViewModel。

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

AndroidViewModel 与application关联,具备了UI界面不存在的情况下,仍持续有效的基本条件。另一个重要条件就是,创建者不能是UI(否则该UI界面销毁后,AndroidViewModel重新被创建出来的对象与之前的对象就不一样了)。这时,这个ViewModel只能放到Application 里头创建了。

发表评论