这次回来重新写Android,发现现在都用MVVM模式了,想想也是,以前用MVC,随着业务扩展,Activity代码量越来越大,改着改着就牵一发而动全身了。当然,最麻烦的还是随着屏幕的旋转的界面重新初始化处理。所以这也是ViewModel深得我心的一个原因。
ViewModel独立于配置变化,所以屏幕旋转所导致的Activity重建并不会影响ViewModel的生命周期,大概就是下面这个图的样子。
通过查看ViewModel源码可以看到,ViewModel是一个抽象类,里面有一个onCleared()方法。注释上写着“当不再使用此ViewModel时将调用此方法,并将销毁该方法。当ViewModel观察到某些数据并且需要清除此订阅以防止此ViewModel泄漏时,它非常有用。”所以我们可以在这个方法中执行一些资源释放相关的操作。由上图也可以看到,由于屏幕旋转而导致的Activity重建,并不会调用这个方法。
也正因为如此,ViewModel的最重要的作用就是将视图与数据进行分离,并独立于Acitiviy得到重建。
在页面中,我们使用ViewModelProvider类来实例化ViewModel
//获取ViewModel的实例,将countReserved传递给MainViewModel的构造函数
viewModel = ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
来看一下源码:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
接收的第一个参数是ViewModelStoreOwner对象,对应我们调用时候的this,指代当前的Activity。之所以能这么用,是因为我们的Activity继承自AppCompatActivity,而查看源码发现,AppCompatActivity又继承自FragmentActivity,在androidx依赖包中,FragmentActivity默认实现了ViewModelStoreOwner接口。
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
........
}
public class FragmentActivity extends ComponentActivity implements
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompat.RequestPermissionsRequestCodeValidator {
......
class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
ViewModelStoreOwner,
OnBackPressedDispatcherOwner {
public HostCallbacks() {
super(FragmentActivity.this /*fragmentActivity*/);
}
@NonNull
@Override
public ViewModelStore getViewModelStore() {
return FragmentActivity.this.getViewModelStore();
}
......
}
......
}
接口方法getViewModelStore()所定义的返回值类型为ViewModelStore。
查看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();
}
}
可以看出ViewModel实际上是以HashMap<String, ViewModel>的形式被缓存起来了。ViewModel与页面直接没有直接的关联,通过ViewModelProvider进行关联。当页面需要ViewModel时,会向ViewModelProvider索要,ViewModelProvider则会检查该ViewModel是否已经存在于缓存中,如果存在就直接返回,否则会实例化一个。因此,Activity由于配置变化导致的销毁重建并不会影响ViewModel,这也是ViewModel是独立于页面存在原因。
所以,需要注意的是:
- 不要向ViewModel中传入任何类型的Context或带有Context引用的对象,这可能会导致页面无法被销毁,从而引发内存泄漏。
如果一定要在ViewModel中使用Context,可以使用ViewModel的子类AndroidViewModel,因为它接收Application作为Context,这意味着,它的生命周期和Application是一样的,这就不算是一个内存泄漏了。
除了Activity,androidx依赖包中的Fragment也默认实现了ViewModelStoreOwner接口,因此,也可以在Fragment中正常使用ViewModel。