1.ViewModel
ViewModel是Jetpack的一部分。 ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。
ViewModel出现的背景:
①职责分离
Android开发中,在页面较为简单的情况下,通常会将UI交互、数据获取等相关的业务逻辑全部写在页面中。但是在页面功能复杂的情况下,这样做就是不合适的。因为这样不符合“单一功能原则”。页面只应该负责处理用户与UI控件的交互,应该将UI与数据相关的业务逻辑隔离开。
为了能够更好地将职能划分,Android提供了ViewModel类,专门用于存放应用程序页面所需的数据。
②数据保留不丢失
在Android中如果Activity、Fragment销毁或者重建,存储在其中的数据会丢失。对于简单的数据,Activity可以使用onSaveInstanceState()方法来从onCreate()中恢复数据,但这个方法只适合可以序列化再反序列化的少量数据,而不适合较大的数据。
③防止异步操作时内存泄露
UI界面经常需要异步操作,比如网络请求等,当界面销毁时往往需要手动维护异步取消的动作,这样显得特别繁琐,并且把所有代码都写在界面中,会变得特别臃肿。
于是就需要将视图数据与界面分离,让层次清晰且高效。ViewModel作为视图数据和界面的桥梁,用来存储与UI相关的数据,它通过lifecycle感知的方式存储和管理UI相关数据。它可以维护自己的生命周期,不需要手动操作,这无疑大大降低开发难度。
ViewModel的生命周期:
这张图是在没有任何设置的情况下,旋转屏幕时Activity的生命周期变化和ViewModel的生命周期。可以看到Activity重建的时候,ViewModel中的数据是不会被清理的。即ViewModel类使得数据在配置更改(如屏幕旋转)时保活。
从这张图可以看出:
①ViewModel的生命周期比创建它的Activity、Fragment的生命周期都要长,即ViewModel中的数据会一直存活在Activity/Fragment中。因此ViewModel不能持有Context的对象,否则会出现内存泄露。
也就是说ViewModel一直保留在内存中,直到它的作用域永久消失:在activity的情况下,当它finishes时;而在fragment的情况下,当它被detached时。
②Activity在生命周期中可能会触发多次onCreate(),而ViewModel只会在第一次onCreate()时创建,然后直到最后Activity销毁。
ViewModel的应用:
屏幕旋转后,使用户操作数据仍然存在。
2.ViewModel的使用
①写一个类继承自ViewModel
class MyViewModel : ViewModel() {
private var number:Int = 0
fun getNumber(): Int {
return number
}
fun setNumber() {
number++
}
override fun onCleared() {
super.onCleared()
//viewModel销毁时调用,可以做一些释放资源的操作,防止内存泄露
}
}
②使用自定义的ViewModel
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myViewModel = ViewModelProvider( this).get(MyViewModel::class.java)
btn_add.setOnClickListener {
myViewModel.setNumber()
tv_text.setText("" + myViewModel.getNumber())
}
}
}
点击Button可以使TextView上的数字一直增加,而且旋转屏幕时数据也会保持,不会丢失(如果不使用ViewModel,旋转屏幕时TextView上的数据会变为0)。
ViewModel是一个抽象类,只有一个onCleared()方法。当ViewModel不再被需要,即与之相关的Activity/Fragment被销毁时,该方法会被系统调用。因此可以在该方法中执行一些资源释放的相关操作,防止造成内存泄露。注意: 当屏幕旋转而导致的Activity重建,并不会调用该方法。
注意:
①如果Activity重新创建,它接收的ViewModel实例与第一个Activity创建的实例相同。
②当Activity完成(不能简单理解为destroy)时,框架会调用ViewModel的onCleared()方法,以便它可以清理资源。
③ViewModel的生命周期比视图的生命周期长,所以ViewModel绝不能持有视图、Lifecycle或Activity的上下文等引用,否则就会造成内存泄漏。
3.源码分析
①ViewModel的实例缓存在哪里
ViewModel的创建方法是
val myViewModel = ViewModelProvider( this).get(MyViewModel::class.java)
创建ViewModelProvider对象并传入this参数,然后通过ViewModelProvider的get方法,传入MyViewModel的class类型,就得到了 myViewModel实例。
看一下ViewModelProvider的构造方法:
public ViewModelProvider( ViewModelStoreOwner owner) {
// 获取owner对象的ViewModelStore对象
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance());
}
ViewModelProvider构造方法的参数类型是 ViewModelStoreOwner。而使用时传入的是this,这是因为Activity实现了ViewModelStoreOwner接口。
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner ... {
private ViewModelStore mViewModelStore;
// 重写了ViewModelStoreOwner接口的唯一的方法getViewModelStore()
@Override
public ViewModelStore getViewModelStore( ) {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}
}
再看刚刚的ViewModelProvider构造方法里调用了this(ViewModelStore, Factory),将 ComponentActivity#getViewModelStore返回的ViewModelStore实例传了进去,并缓存到ViewModelProvider中。
public ViewModelProvider(ViewModelStore store, Factory factory) {
mFactory = factory;
// 缓存ViewModelStore对象
mViewModelStore = store;
}
接着看ViewModelProvider#get方法做了什么。
@MainThread
public <T extends ViewModel> T get( 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的CanonicalName, 然后调用了另一个get方法。
@MainThread
public <T extends ViewModel> T get(String key, Class<T> modelClass) {
//从mViewModelStore缓存中尝试获取
ViewModel viewModel = mViewModelStore.get(key);
// 命中缓存
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
// 返回缓存的 ViewModel 对象
return (T) viewModel;
}
// 使用工厂模式创建 ViewModel 实例
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create( modelClass);
}
// 将创建的ViewModel实例放进mViewModelStore缓存中
mViewModelStore.put(key, viewModel);
// 返回新创建的 ViewModel 实例
return (T) viewModel;
}
通过ViewModelProvider的构造方法可以知道mViewModelStore其实是Activity里的mViewModelStore对象,它在 ComponentActivity中被声明。 看到了put方法,不难猜它内部用了Map结构。
public class ViewModelStore {
//内部有一个 HashMap
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();
}
}
// 通过key获取ViewModel对象
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
到这里,正常情况下ViewModel的创建流程看完了,简单总结:ViewModel对象存在于ComponentActivity的mViewModelStore对象中。
②为什么Activity旋转屏幕后ViewModel可以恢复数据
在ViewModelProvider的构造方法中,获取ViewModelStore对象时,实际调用了 MainActivity#getViewModelStore(),而 getViewModelStore()实现在MainActivity的父类 ComponentActivity中。
ComponentActivity.java:
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}
在返回mViewModelStore对象之前调用了 ensureViewModelStore()方法。
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();
}
}
}
当mViewModelStore == null调用了getLastNonConfigurationInstance()获取 NonConfigurationInstances对象nc,当nc != null时将mViewModelStore赋值为 nc.viewModelStore,最终viewModelStore == null时,才会创建ViewModelStore实例。
不难发现,之前创建的viewModelStore对象被缓存在NonConfigurationInstances中。
// 它是 ComponentActivity 的静态内部类
static final class NonConfigurationInstances {
Object custom;
// 果然在这儿
ViewModelStore viewModelStore;
}
NonConfigurationInstances对象是通过 getLastNonConfigurationInstance()来获取的。
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null ?mLastNonConfigurationInstances.activity : null;
}
注意:
①onRetainNonConfigurationInstance方法和getLastNonConfigurationInstance是成对出现的,跟onSaveInstanceState机制类似,只不过它是仅用作处理配置更改的优化。
②返回的是onRetainNonConfigurationInstance 返回的对象。
看看onRetainNonConfigurationInstance 方法
//保留所有适当的非配置状态
@Override
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
// 若 viewModelStore 为空,则尝试从 getLastNonConfigurationInstance() 中获取
if (viewModelStore == null) {
NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
//依然为空,说明没有需要缓存的,则返回null
if (viewModelStore == null && custom == null) {
return null;
}
// 创建 NonConfigurationInstances 对象,并赋值viewModelStore
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
到这儿大概明白了,Activity在因配置更改而销毁重建过程中会先调用onRetainNonConfigurationInstance保存viewModelStore实例。 在重建后可以通过getLastNonConfigurationInstance方法获取之前的viewModelStore实例。因此Activity旋转屏幕后ViewModel可以恢复数据。
③什么时候ViewModel#onCleared()会被调用
public abstract class ViewModel {
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException( value);
}
}
}
onCleared();
}
}
onCleared()方法被clear()调用了。 刚才看 ViewModelStore源码时好像是调用了clear() ,回顾一下:
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());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
在ViewModelStore的clear()中,遍历mMap并调用ViewModel对象的clear(),再看ViewModelStore的clear()什么时候被调用的:
// ComponentActivity的构造方法
public ComponentActivity() {
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged( LifecycleOwner source,Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
mContextAwareHelper.clearAvailabl eContext();
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
...
}
观察当前activity生命周期,当Lifecycle.Event == Lifecycle.Event.ON_DESTROY,并且 isChangingConfigurations()返回false时才会调用ViewModelStore#clear 。
// Activity#isChangingConfigurations()
public boolean isChangingConfigurations() {
return mChangingConfigurations;
}
isChangingConfigurations用来检测当前的Activity是否因为Configuration的改变被销毁了, 配置改变返回true,非配置改变返回false。
总结:在activity销毁时,判断如果是非配置改变导致的销毁,getViewModelStore().clear()才会被调用。
4.ViewModel使用注意
①Fragment间共享数据
因为ViewModel只会在Activity存活时会创建一次,因此在同一个Activity中可以在多个Fragment中共享ViewModel中数据。
public class FragmentA extends Fragment{
ViewModelProviders.of(getActivity()).get( MyViewModel.class).getDatas().observe(this, new Observer<User>() {
@Override
public void onChanged(User user) {
//获取Activity中数据变化
}
});
}
public class FragmentB extends Fragment{
ViewModelProviders.of(getActivity()).get( MyViewModel.class).getDatas().observe(this, new Observer<User>() {
@Override
public void onChanged(User user) {
}
});
}
//在Activity中更新数据
ViewModelProviders.of(this).get(MyViewModel.class). updateUser();
②使用ViewModel的时候,要注意ViewModel不能够持有View、Lifecycle、Acitivity引用,而且不能够包含任何包含前面内容的类。因为ViewModel的生命周期比它们长,这样很有可能会造成内存泄漏。
③ViewModel中使用Context
如果ViewModel需要Applicaiton的Context(为了获取系统服务),可以使用AndroidViewModel。
普通的ViewModel生命周期都很短,随着Activity 销毁而销毁。如果要创建一个长生命周期的ViewModel可以使用AndroidViewModel。
AndroidViewModel 持有了一个Application,所以它的生命周期会很长。具体使用如下:
class MyViewModel(application: Application) : AndroidViewModel(application) {
override fun onCleared() {
super.onCleared()
//viewModel销毁时调用,可以做一些释放资源的操作
}
}
可以在AndroidViewModel中存储一些全局数据。