Jetpack 系列第三篇,这次回顾 ViewModel,ViewModel 作为 MVVM 架构中的 VM 层,具有自己的生命周期,且生命周期贯穿整个 Activity 或 Fragment,相较于之前 MVP 的 Presenter,它的存活时间更长,所以作为界面数据的持有者和控制者会更有优势 。
一、ViewModel 有什么用
- 首先,ViewModel 可以将视图层的逻辑剥离出来,所有与视图相关的数据应该存在于 ViewModel 中,而不是 Activity 或 Fragment,这一点它与 Presenter 作用类似,皆致力于减少 View 层逻辑;
- 相较于之前 MVP 的 Presenter,它的生命周期更长,在 Activity 因配置改变而重建时,ViewModel 的数据仍可得到保存。
- 可指定作用域,实现 Activity 与 Fragment 之间或 Fragment 与 Fragment 之间数据共享。
二、ViewModel 怎么用
ViewModel 作为一个抽象类,当我们自己定义一个类继承自它的时候,由于它也是一个普通的类,所以也可以用 new 的方式创建实例,但是这样做的话就失去它的意义了,在 Activity 重建时,ViewModel 也会重新创建。
package com.qinshou.jetpackdemo.viewmodelimport androidx.lifecycle.ViewModelclass TestViewModel : ViewModel() {var i = 0
}
package com.qinshou.jetpackdemo.viewmodelimport android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.qinshou.jetpackdemo.Rclass ViewModelActivity : AppCompatActivity() {companion object {const val TAG = "ViewModelActivity"}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_viewmodel)val testViewModel = TestViewModel()Log.i(TAG, "testViewModel--->$testViewModel")findViewById<Button>(R.id.btn_set_value).setOnClickListener {testViewModel.i++}findViewById<Button>(R.id.btn_get_value).setOnClickListener {Toast.makeText(this, "Value of view model is ${testViewModel.i}", Toast.LENGTH_SHORT).show()}}
}
直接 new 创建 ViewModel 实例,然后横竖屏切换一下,可以看到 ViewModel 不是同一个实例,而且 ViewModel 中持有的数据也被重新初始化了:
正确的做法应该是使用 ViewModelProvider 去创建 ViewModel,我们将创建 ViewModel 实例的方式修改一下,然后重新运行:
val testViewModel = ViewModelProvider(this).get(TestViewModel::class.java)
可以看到横竖屏切换后,ViewModel 内存地址不变,而且持有的数据也得以保存。那么为什么通过 ViewModelProvider 就可以保证这样的效果呢,小追一下源码。
首先 ViewModelProvider 有三个构造方法:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory(): NewInstanceFactory.getInstance());}public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {this(owner.getViewModelStore(), factory);}public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {mFactory = factory;mViewModelStore = store;}
这三个构造方法最后走到了同一个地方,通常情况下我们使用只穿一个 ViewModelStoreOwner 参数的构造方法即可,最后它会保存 Factory 和 ViewModelStore 实例。在默认实现中,这两个实例由 ComponentActivity 或者 Fragment 去创建(这里仅贴了 ComponentActivity 中的代码):
void ensureViewModelStore() {if (mViewModelStore == null) {NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// Restore the ViewModelStore from NonConfigurationInstancesmViewModelStore = nc.viewModelStore;}if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}}}
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {if (getApplication() == null) {throw new IllegalStateException("Your activity is not yet attached to the "+ "Application instance. You can't request ViewModel before onCreate call.");}if (mDefaultFactory == null) {mDefaultFactory = new SavedStateViewModelFactory(getApplication(),this,getIntent() != null ? getIntent().getExtras() : null);}return mDefaultFactory;}
然后我们是通过 ViewModelProvider 的 get() 方法获取 ViewModel:
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {String canonicalName = modelClass.getCanonicalName();if (canonicalName == null) {throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");}return get(DEFAULT_KEY + ":" + canonicalName, modelClass);}
该方法将 ViewModel 的类名加上 DEFAULT_KEY 前缀作为 key 再次调用了 get 方法重载:
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {ViewModel viewModel = mViewModelStore.get(key);if (modelClass.isInstance(viewModel)) {if (mFactory instanceof OnRequeryFactory) {((OnRequeryFactory) mFactory).onRequery(viewModel);}return (T) viewModel;} else {//noinspection StatementWithEmptyBodyif (viewModel != null) {// TODO: log a warning.}}if (mFactory instanceof KeyedFactory) {viewModel = ((KeyedFactory) mFactory).create(key, modelClass);} else {viewModel = mFactory.create(modelClass);}mViewModelStore.put(key, viewModel);return (T) viewModel;}
这个方法主要做的事就是先从 ViewModelStore 中获取 ViewModel 实例,有就直接返回,没有的话就通过 Factory 创建然后放到 ViewModelStore 中再返回,相当于一个缓存机制,而获取和存储 ViewModel 的是在 ViewModelStore 中,那再来看看它是如何存储的:
public class ViewModelStore {private final HashMap<String, ViewModel> mMap = new HashMap<>();final void put(String key, ViewModel viewModel) {ViewModel oldViewModel = mMap.put(key, viewModel);if (oldViewModel != null) {oldViewModel.onCleared();}}final ViewModel get(String key) {return mMap.get(key);}Set<String> keys() {return new HashSet<>(mMap.keySet());}/*** Clears internal storage and notifies ViewModels that they are no longer used.*/public final void clear() {for (ViewModel vm : mMap.values()) {vm.clear();}mMap.clear();}
}
可以看到 ViewModelStore 中其实是用一个 HashMap 去存储 ViewModel 实例的,那它到底是怎么保证 Activity 在异常销毁时保证 ViewModel 不变的呢?是不是只要在 Activity 异常销毁时,保存 ViewModelStore 的实例,重新创建 Activity 时再恢复就好了呢?事实上就是这样:
public final Object onRetainNonConfigurationInstance() {// Maintain backward compatibility.Object custom = onRetainCustomNonConfigurationInstance();ViewModelStore viewModelStore = mViewModelStore;if (viewModelStore == null) {// No one called getViewModelStore(), so see if there was an existing// ViewModelStore from our last NonConfigurationInstanceNonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {viewModelStore = nc.viewModelStore;}}if (viewModelStore == null && custom == null) {return null;}NonConfigurationInstances nci = new NonConfigurationInstances();nci.custom = custom;nci.viewModelStore = viewModelStore;return nci;}
在 Activity 异常销毁时会通过调用 onRetainNonConfigurationInstance(),将 ViewModelStore 保存在 NonConfigurationInstances 对象中,而刚才创建 ViewModelStore 的代码中,首先会从最后保存 NonConfigurationInstances 对象中获取,如果之前有保存,就直接拿来使用,所以系统内部就已经帮我们做好了异常销毁时的状态保存了。
三、ViewModel 作用域
关于这一点,其实我们通过刚才的源码知道 ViewModel 是如何创建出来的,那它作用域的问题就一目了然了。因为 ViewModel 其实是通过 ViewModelStore 对象获取的,而 ViewModelStore 是在创建 ViewModelProvider 对象时赋的值。那么在 Fragment 中,如果我们传递的 ViewModelStoreOwner 是 this,那就是它自己的 ViewModelStore,如果传递是 requireActivity(),那使用的就是宿主 Activity 的 ViewModelStore,这样就能达到多个子 Fragment 中共享数据的目的了。小做演示:
class ViewModelFragment1 : Fragment() {companion object {const val TAG = "ViewModelFragment1"}override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {return LayoutInflater.from(context).inflate(R.layout.fragment_viewmodel, null, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)val testViewModel = ViewModelProvider(this).get(TestViewModel::class.java)Log.i(TAG, "testViewModel--->$testViewModel")view.findViewById<TextView>(R.id.text_view).text = "Value of view model is ${testViewModel.i}"}
}
class ViewModelFragment2 : Fragment() {companion object {const val TAG = "ViewModelFragment1"}override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {return LayoutInflater.from(context).inflate(R.layout.fragment_viewmodel, null, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)val testViewModel = ViewModelProvider(requireActivity()).get(TestViewModel::class.java)Log.i(TAG, "testViewModel--->$testViewModel")view.findViewById<TextView>(R.id.text_view).text = "Value of view model is ${testViewModel.i}"}
}
这两个 Fragment 代码除了创建 ViewModelProvider 传递的参数不一样,其他都一样,用了一个文本框去显示 ViewModel 中的数据,然后在刚才的 Activity 中增加两个按钮分别显示这两个 Fragment(代码太简单就不贴了),效果如下:
可以看到 ViewModelFragment1 中的 ViewModel 对象内存地址与宿主 Activity 不一样,里面的数据值也不一样,而 ViewModelFragment2 是通过宿主 Activity 去获取的 ViewModelProvider 从而获取的 ViewModel 对象,所以内存地址和数据值都跟宿主 Activity 是一样的。
四、总结
ViewModel 作为 MVVM 中的重要一环,相当于 Presenter 的升级版,结合 LiveData 使用,可以节省很多之前需要注意的操作。ViewModel 的使用也不难,在了解它的特性是如何去实现后,相信运用起来会更加得心应手。