本文旨在理解ViewModel的设计思想以及本质解决了哪些问题。在阅读本文之前,你需要理解LiveData、Lifecycle。关于ViewModel的使用不在复述直接看官方文档。
- JetPack | LiveData 如何安全的观察数据
- JetPack | Lifecycle 如何做到感知生命周期
ViewModel 是JetPack中一个非常重要的组件,ViewModel 旨在为界面准备数据,管理状态。以生命周期的方式管理界面相关的数据。ViewModel 本质(目标):
- ViewModel可以持久化Activity/Fragment/Application作用域依据生命周期持久化数据与清理数据。
- ViewModel可以持久化LiveData,通过LiveData解决生命周期有效范围内异步回调问题,防止内存泄漏。
- ViewModel在MVVM设计模式中可以隔离Model层和View层。
- ViewModel可以实现状态共享。
ViewModel 本质
ViewModel 在Google I/O 大会上很早就推出了包括LiveData、Lifecycle,这三个是一起推出的因为LiveData依赖于Lifecycle,而ViewModel依赖于LiveData。在前两篇文章中Lifecycle是感知生命周期、LiveData依赖Lifecycle在活跃的生命周期内发布/订阅数据、ViewModel管理界面(View层)的数据和状态。
Lifecycle、LiveData、ViewModel 可以说是三剑客缺一不可。
三剑客的关系:ViewModel持有View层的数据和状态,通过LiveData发布数据,依赖Lifecycle在活跃的生命周期状态分发数据,View层通过ViewModel订阅LiveData,ViewModel状态改变并更新UI。 View层只需要持有ViewModel的引用即可,Model层和View层进行了隔离,ViewModel状态改变,自动更新View层(但是没有DataBinding View层无法自动更新,需要进行单独的设置才可以),这样就形成了MVVM的设计模式(其实在Android三剑客基础上加上DataBinding才是真正的MVVM)。 在ViewModel层,实现LiveData/其他数据在依据作用域内的有效范围内持久化数据。
如下图MVVM的设计实现
这里是重点: ViewModel 存在的生命周期与传递
ViewModelStoreOwner
有关。
ViewModel状态管理
创建ViewModel
class RotatedViewModel:ViewModel() {
//ViewModel与LiveData结合 LiveData尽量不要让外部获取到 防止发生不可预期的问题
private val numberData = MutableLiveData<Int>()
public fun observerNumber(owner: LifecycleOwner,observer: Observer<Int>){
/**
* Observe 如之前讲述的LiveData 可以拿到最新的数据 然后调用回调
*/
numberData.observe(owner,observer)
}
public fun getNumber():Int{
return numberData.value!!
}
public fun setNumber(value:Int){
numberData.value = value
}
}
Activity 引用ViewModel,并且订阅数据更新,并且观察当屏幕旋转后Activity重建,ViewModel是否会保存状态。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
rotatedViewModel = ViewModelProvider(this).get(RotatedViewModel::class.java)
//屏幕旋转会导致tv重建 需要重新赋值
tv = findViewById(R.id.tv)
//查看屏幕旋转 重建activity后viewmodel是否可以做到数据持久化操作
rotatedViewModel.observerNumber(this){
Log.e("TAG", "onCreate: ${lifecycle.currentState}" )//TAG: onCreate: STARTED RESUMED
tv.text = it.toString()
}
}
override fun onDestroy() {
super.onDestroy()
//屏幕旋转 activity是否会销毁重建 查看viewmodel 是否会重新实例化
Log.e("ViewModelActivity->>>>", "onDestroy: ${hashCode()} viewmodel:${rotatedViewModel.hashCode()}")
}
通过一个按钮点击事件,来更改ViewModel的持有的数据:
fun onChange(view: View) {
rotatedViewModel.setNumber(11)
}
先变更数据旋转屏幕,查看状态:如下图Activity重建了也就是重新初始化了,会重新走onCreate方法,而ViewModel并没有重新实例化,还是获取之前的由于LiveData有粘性事件,STARTED状态下异步回调会返回最新的数据,对状态进行了恢复(对textView重新设置值)。
横屏下状态:
似乎看到这里我们可以看懂在ViewModel
的官方文档的一张图,ViewMode
在Activity的生命周期,在Activity屏幕旋转的状态下ViewModel
并不会被销毁,这样ViewModel
只要持有Activity的所有数据,都可以进行状态恢复,而不会在通过savedInstanceState
保存状态在恢复状态,而ViewModel
只需要通过订阅LiveData
,当屏幕旋转,Activity的状态为STARTED
时订阅数据会通过异步回调返回,根据订阅的异步回调恢复数据。
看到这里
ViewModel
完美的解决了Activity的状态问题,通过ViewModel
在Activity屏幕发生旋转后不会被销毁持久化数据,然后通过LiveData
在活跃的生命周期状态下(STARTED
、RESUMED
)安全的观察数据,避免内存泄漏的问题。
看到这里我们似乎明白了ViewModel的设计缘由了,简直太棒了,但这还不是ViewModel的全部。
ViewModel作用域
ViewModel作用域可以实现界面间的通信,实现状态共享。 作用域:Activity、Fragment、Application
ViewModel还有一个重要的特性:状态共享。而状态共享和ViewModel的作用域存在重要的关联,而ViewModel的作用域和ViewModelStoreOwner
有关。ViewModelStoreOwner
在Activity和Fragment中都进行了实现,外部可能无感知,ViewModelStoreOwner
的实现其实已经封装到了Activity和Fragment中。
Fragment 之间状态共享-Activity 作用域
在Activity中设置Fragments
//viewmodel 可以在fragment之间互相通信 需要将ViewModel的作用域设置为Activity
val beginTransaction = supportFragmentManager.beginTransaction()
beginTransaction
.add(R.id.fl_master, MasterFragment.newInstance())
.add(R.id.fl_detail, DetailFragment.newInstance())
.commitAllowingStateLoss()
MasterFragment
发送数据:ViewModel
设置为Activity的ViewModelStoreOwner
-此时ViewModel的作用域为Activity
class MasterFragment:Fragment() {
//ViewModel 设置为Activity的ViewModelStoreOwner-
private val model:SharedViewModel by activityViewModels() //ktx 扩展方法
.......
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// model = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
val btn_master = view.findViewById<Button>(R.id.btn_master)
btn_master.setOnClickListener {
model.select("Master")
}
}
}
DetailFragment 接收数据:ViewModel
设置为Activity的ViewModelStoreOwner
-此时ViewModel的作用域为Activity
class DetailFragment:Fragment() {
......
private val model:SharedViewModel by activityViewModels()
......
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val tv_detail = view.findViewById<TextView>(R.id.tv_detail)
// model = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
model.selected.observe(viewLifecycleOwner){
tv_detail.text = it
}
}
}
如下图所示:
看到这里至于Fragment的作用域就不用再介绍了ViewModelProvider(fragment)
Activity 之间状态共享 - Application 作用域
ViewModel 如何进行Activity的状态共享呢?那么就需要将ViewModel的作用域设置为Application。上述讲过作用域和
ViewModelStoreOwner
有关,也就是需要在Application上实现ViewModelStoreOwner
class App:Application(),ViewModelStoreOwner {
/**
* 存在Application作用域的ViewModel
*/
private lateinit var mAppViewModelStore:ViewModelStore
override fun onCreate() {
super.onCreate()
mAppViewModelStore = ViewModelStore()
}
override fun getViewModelStore(): ViewModelStore {
return mAppViewModelStore
}
}
那么Activity设置作用域就是这样的:
//get ViewModelStore获取ViewModel对象如果没有则创建 ViewModel的作用域设置为Application 可以在Activity之间共享状态
rotatedViewModel = ViewModelProvider(this.applicationContext as App).get(RotatedViewModel::class.java)
通过按钮发送数据:
fun onActChange(view: View) {
rotatedViewModel.setNumber(10)
startActivity(Intent(this,TestActivity::class.java))
}
在另一个Activity接收数据:
class TestActivity : AppCompatActivity() {
val tv:TextView by lazy { findViewById(R.id.tv) }
val model:RotatedViewModel by lazy {
ViewModelProvider(this.applicationContext as App).get(RotatedViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
model.observerNumber(this){
tv.text = it.toString()
}
}
fun onChange(view: View) {
model.setNumber(100)
}
}
ViewModel的作用域就是通过
ViewModelStoreOwner
来反映作用域的。同一个 ViewModel 子类,基于不同的作用域,获取到的实例并非同一个。根据传入的 ViewModelStoreOwner 便能拿到符合实际场景所需的 ViewModel 实例。
ViewModel状态保存原理
在Activity的ComponentActivity源码中:
Activity 初始化ViewModelProvider
传递ViewModelStoreOwner 其实调用的owner.getViewModelStore()
获取ViewModelStore
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
ViewModelStore
就是一个Map结构用来存储ViewModel
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
......
}
而Activity是如何实现ViewModelStoreOwner 呢?如下代码:
注意:通过ensureViewModelStore获取ViewModelStore,这里会看到一个nc的变量,这里先不看它,因为Activity第一次进入nc肯定是null。所以第一次就是返回了一个ViewModelStore的实例
public ViewModelStore getViewModelStore() {
.....
ensureViewModelStore();
return mViewModelStore;
}
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
当屏幕旋转Activity重建的时候会调用:onRetainNonConfigurationInstance
这个返回最终会返回一个带有viewModelStore
的nci
的实例
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
//存储viewModelStore
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
那么onRetainNonConfigurationInstance是什么时候调用的呢?
onRetainNonConfigurationInstance 方法通过Binder AMS 进程通信调用的。
看Activity的源码如下:当屏幕旋转进程调用retainNonConfigurationInstances
并且调用了onRetainNonConfigurationInstance
返回的NonConfigurationInstances
实例会存储到共享内存中。
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
......
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}
当Activity旋转完毕后,重建的Activity的生命周期会重走onCreate()等,再次调用getViewModelStore
会走如下的代码逻辑:
//ComponentActivity : getViewModelStore()
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
//Activity : getLastNonConfigurationInstance会拿到共享内存中存储的NonConfigurationInstances对象,而activity就存储了viewModelStore
NonConfigurationInstances mLastNonConfigurationInstances;
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
使得当重建时,ViewModelStore 被保留,而在重建后,恢复了 Activity 对该 ViewModelStore 的持有。
并且,对于重建的情况,还会走 isChangingConfigurations
的判断,如果为 true,就不走 ViewModelStore 的 clear。在ComponentActivity的构造函数中,监听了onDestory的事件,判断是否是屏幕发生旋转,如果不是会将ViewModelStore进行清空
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
以上就是ViewModel的状态保存原理实现。