一、基本作用域
安卓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 里头创建了。