ViewModel 具备宿主生命后期感知能力的数据存储组件,使用 ViewModel 保存的数据,在页面因配置变更
导致页面销毁重建之后依然也是存在的,其中配置变更主要是指横竖屏切换、分辨率调整、权限变更、系统字体样式变更。ViewModel 的优势:
- 页面更改数据不丢失
当设备因配置更改导致 Activity/Fragment 重建,ViewModel 中的数据并不会因此而丢失,配合 LiveData 可以在页面重建后立马能收到最新保存的数据用以重新渲染页面。
- 生命周期感应
在 ViewModel 中难免会做一些网络请求或数据的处理,可以复写 onCleared() 方法,终止清理一些操作,释放内存。该方法在宿主 onDestroy 时被调用。
- 数据共享
对于单 Activity 对 Fragment 的页面,可以使用 ViewModel 实现页面之间的数据共享,实际上不同的 Activity也可以实现数据共享。
1. ViewModel基本使用
(1)首先,在module的build.gradle中添加依赖;
//通常情况下,只需要添加appcompat就可以了
api 'androidx.appcompat:appcompat:1.1.0'
//如果想单独使用,可引入下面的依赖
api 'androidx.lifecycler:lifecycle-viewmodel:2.0.0'
(2)其次,实现一个与当前Activity/Fragment关联的ViewModel;
通常一个Activity对应一个ViewModel,Activity可以由多个Fragment组成,因此多个Fragment共享这个ViewModel对象存储的数据(注:实际上是LiveData存储的数据
)。当然,多个Activity也可以共享同一个ViewModel,但是需要在Application中特殊配置;
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
(3)最后,实例化一个ViewModel对象。
在ViewModel中,我们结合LiveData即可实现一个MVVM架构开发模式,其中,ViewModel将充当VM层,负责从M层获取数据并通知V层自动更新UI(LiveData实现
)。
// 获取Activity对应的ViewModel
// 该ViewModel的生命周期与Activity一致
val viewModel = ViewModelProvider(activity).get(SharedViewModel::class.java)
// 在Fragment中获取Activity的ViewModel
// 那么Activity与Fragment共享该ViewModel对象
val viewModel = ViewModelProvider(requestActivity()).get(SharedViewModel::class.java)
// 创建一个Fragment的ViewModel对象
// 该ViewModel的生命周期与Fragment一致
val viewModel = ViewModelProvider(fragment).get(SharedViewModel::class.java)
当然,我们也可以Kotlin的特性更容易的获取ViewModel对象,但需要注意的是,这需要在项目中分别依赖activity-ktx
和fragment-ktx
库。
// Activity及其两个Fragment获得得将是同一个SharedViewModel对象
// 即共享数据
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val model: SharedViewModel by viewModels()
model.selected().observe(this, Observer<Item>{ item ->
})
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
2. ViewModel源码解析
2.1 ViewModel的生命周期
2.2 ViewModel实现原理
ViewModel框架UML类图如下:
具体说明如下:
- ViewModelProvider
ViewModelProvider
是提供创建ViewModel对象的入口,它的构造方法需要传入一个ViewModelStoreOwner对象和一个Factory对象,其内部将通过工厂模式
完成对ViewModel实例的创建。源码如下:
public class ViewModelProvider {
private static final String DEFAULT_KEY =
"androidx.lifecycle.ViewModelProvider.DefaultKey";
...
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 具体工厂创建ViewModel实例
}
}
- ViewModelStoreOwner
ViewModelStoreOwner
是一个接口,通过实现该接口,用于表明自己是一个ViewModelStore
的拥有者(owner
)。Activity/Fragment均继承了这个接口,在Activity/Fragment中,可以通过getViewModelStore()
方法获取与其关联的ViewModelStore对象。源码如下:
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
- ViewModelStore
ViewModelStore
是一个ViewModel实例存储仓库,它内部维护了一个HashMap集合,实现存储多个ViewModel实例。在Activity/Fragment中,可以通过调用其getViewModelStore()
方法来获取与之关联的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());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
- Factory
在ViewModel框架中,是通过工厂模式实现对ViewModel对象的创建,其中,Factory就是所有具体工厂类的父接口,它提供了一个通用的方法create
来创建一个ViewModel对象,但具体的实现是在具体工厂类中,如KeyedFactory、SavedStateViewModelFactory 、NewInstanceFactory、AndroidViewModelFactory。源码如下:
public interface Factory {
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
2.2.1 创建ViewModel对象过程
首先,创建一个ViewModelProvider对象,其构建方法中需要传入一个ViewModelStoreOwner
和指定创建ViewModel实例的工厂Factory
,这个工厂对象可选,对于Activity和Fragment来说,默认调用ViewModelStoreOwner
的getDefaultViewModelProviderFactory()方法获取,因为它们均实现了HasDefaultViewModelProviderFactory接口。代码如下:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
// owner即Activity或Fragment
// 它们均继承了ViewModelStoreOwner和HasDefaultViewModelProviderFactory
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
// ViewModelStore: ViewModel仓库,存储所有ViewModel对象
// factory:创建ViewModel的具体工厂
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
其次,通过工厂模式
实例化一个ViewModel对象,并返回。具体过程如下:
- a. 检查ViewModelStore仓库中是否缓存了要创建的ViewModel对象;
- b. 如果没有指定工厂,就使用SavedStateViewModelFactory具体工厂来创建ViewModel对象;
- c. 将创建的ViewModel对象缓存到ViewModelStore仓库中。
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 1. 首先,检查是否存在modelClass的缓存
// (1)尝试从ViewModel仓库中获取key对应的ViewModel对象
// 其中,mViewModelStore = owner.getViewModelStore()
ViewModel viewModel = mViewModelStore.get(key);
// (2) 核实viewModel是否确实为modelClass的一个实例
// 如果是,直接return即可
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
// 2. 否则,判断mFactory是哪一个工厂实例
// 并调用对应的create方法创建ViewModel对象
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
// 3. 将创建的ViewModel对象缓存到ViewModel仓库中
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
根据ViewModelProvider的构造方法可知,假如我们在Activity或Fragment中创建ViewModel对象的话,mFactory将是它们的getDefaultViewModelProviderFactory()
方法返回的具体工厂对象,即均为SavedStateViewModelFactory。代码如下:
//.../androidx/activity/ComponentActivity.java
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner
{
...
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.");
}
// 创建SavedStateViewModelFactory对象
if (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
getApplication(),
this,
getIntent() != null ? getIntent().getExtras() : null);
}
return mDefaultFactory;
}
}
// .../androidx/fragment/app/Fragment.java
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner
{
...
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
// 创建SavedStateViewModelFactory对象
if (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
requireActivity().getApplication(),
this,
getArguments());
}
return mDefaultFactory;
}
}
接着,我们来看一下SavedStateViewModelFactory#create
是如何构建ViewModel对象。具体过程如下:
- a. 检查要创建的ViewModel是否包含参数
savedStateHandle
的构造方法,分实现AndroidViewModel与否; - b. 如果不存在(a)描述的构造方法,则使用AndroidViewModelFactory具体工厂创建ViewModel对象;
- c. 否则,使用(a)返回的构造方法反射实例化一个ViewModel对象。
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
// 1. 首先,查找是否存在modelClass的构造方法
// 根据modelClass是否继承于AndroidViewModel分两种情况
// 如果是,查找含有(application, savedStateHandle)参数的构造方法;
// 否则,查找含有(savedStateHandle)参数的构造方法;
boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
Constructor<T> constructor;
if (isAndroidViewModel) {
constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
} else {
constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
}
// doesn't need SavedStateHandle
// 2. 其次,如果没有找到
// 说明要创建的ViewModel不包含对应的构造方法
// 此时也说明,这里不需要SavedStateHandle
if (constructor == null) {
return mFactory.create(modelClass);
}
// 3. 找到对应的构造方法
// 处理需要SavedStateHandle的情况(本文暂时不考虑)
SavedStateHandleController controller = SavedStateHandleController.create(
mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
try {
T viewmodel;
if (isAndroidViewModel) {
viewmodel = constructor.newInstance(mApplication, controller.getHandle());
} else {
viewmodel = constructor.newInstance(controller.getHandle());
}
viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
return viewmodel;
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to access " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("An exception happened in constructor of "
+ modelClass, e.getCause());
}
}
由SavedStateViewModelFactory
的构造方法可知,上述代码的mFactory
即位AndroidViewModelFactory
的一个实例。代码如下:
public SavedStateViewModelFactory(@NonNull Application application,
@NonNull SavedStateRegistryOwner owner,
@Nullable Bundle defaultArgs) {
mSavedStateRegistry = owner.getSavedStateRegistry();
mLifecycle = owner.getLifecycle();
mDefaultArgs = defaultArgs;
mApplication = application;
// 单利模式创建AndroidViewModelFactory
mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
AndroidViewModelFactory继承于NewInstanceFactory,它们均是Factory的具体工厂实现类,需要注意的是,如果我们自定义的ViewModel继承于AndroidViewModel,那么就使用AndroidViewModelFactory具体工程来实例化该对象,否则,使用NewInstanceFactory具体工程来实例化。代码如下:
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private static AndroidViewModelFactory sInstance;
@NonNull
public static AndroidViewModelFactory getInstance(@NonNull Application application) {
if (sInstance == null) {
sInstance = new AndroidViewModelFactory(application);
}
return sInstance;
}
private Application mApplication;
public AndroidViewModelFactory(@NonNull Application application) {
mApplication = application;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
// 1. 如果modelClass是AndroidViewModel的子类
// 调用其指定的构造方法来实例化一个对象
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
try {
return modelClass.getConstructor(Application.class).newInstance(mApplication);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
// 2. 否则,调用父类NewInstanceFactory的create方法
// 该方法会使用默认的构造方法实例化一个对象
// modelClass.newInstance()
return super.create(modelClass);
}
}
public static class NewInstanceFactory implements Factory {
private static NewInstanceFactory sInstance;
@NonNull
static NewInstanceFactory getInstance() {
if (sInstance == null) {
sInstance = new NewInstanceFactory();
}
return sInstance;
}
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
// 使用默认构造方法
// 实例化一个对象
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
2.2.2 ViewModel复用原因分析
由ViewModelProvider$get
方法可知,当ViewModel被实例化完毕后,将会被存储到ViewModel的仓库ViewModelStore中(注:Activity可以关联多个ViewModel对象
),所有Activity与其关联的ViewModel对象都会缓存到这个ViewModelStore的Map集合中。在Activity获取ViewModelStore对象如下:
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.");
}
// 1. 判断ViewModelStore是否存在,没有存在则创建一个
if (mViewModelStore == null) {
//(1) 查看NonConfigurationInstances中是否有缓存
// 如果有就返回这个缓存
// static final class NonConfigurationInstances {
// Object custom;
// ViewModelStore viewModelStore;
// }
// 注:NonConfigurationInstances为ComponentActivity的静态内部类
// 被final修饰,即不可被继承
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
//(2) 否则,直接new一个
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
// 2. 否则,直接返回已经存在的ViewModelStore
return mViewModelStore;
}
由上述代码可知,getViewModelStore()方法中会首先从NonConfigurationInstances
来获取ViewModelStore
实例对象。那么,ViewModelStore何时被存储到NonConfigurationInstances
?答案是:onRetainNonConfigurationInstance
。因系统原因页面被回收时,会触发该方法,所以 viewModelStore 对象此时会被存储在NonConfigurationInstance 中。在页面恢复重建时,会再次把这个 NonConfigurationInstance 对象传递到新的Activity 中实现对象复用。onRetainNonConfigurationInstance()
源码如下:
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// 如果NonConfigurationInstance保存了viewModelStore,把它取出来
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
//把viewModelStore放到NonConfigurationInstances中并返回
nci.viewModelStore = viewModelStore;
//把viewModelStore放到NonConfigurationInstances中并返回
return nci;
}
2.2.3 ViewModel#onClear()被调用过程
ViewModel的onClear()
方法是在Activity被销毁被回调,即通过监听Activity的Lifecycle实现。当生命周期为onDestory时,会去调用ViewModelStore的clear()方法,在该方法中会遍历集合中的ViewModel对象,并依次调用其onClear()
方法,最后,再将集合清空。相关源码如下:
// .../androidx/activity/ComponentActivity.class
public ComponentActivity() {
Lifecycle lifecycle = getLifecycle();
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
}
// .../androidx/lifecycle/ViewModelStore.class
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
// .../androidx/lifecycle/ViewModel.class
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
强调一点:ViewModel只是针对于页配置变更
时,ViewModel能够实现复用,对于因内存不足或者因为电量不足导致页面被回收情况,是无法实现ViewModel复用的,此时就需要ViewModel配合SavedState
实现,它将承担起ViewModel与onSaveIntanceState之间通信的桥梁,其中,onSaveIntanceState在非配置变更导致页面被回收时被触发。最后,总结一下ViewModel与onSaveIntanceState方法区别:
- onSaveIntanceState
onSaveIntanceState只能存储轻量级的 key-value 键值对数据,非配置变更导致的页面被回收时才会触发,此时数据存储在 ActivityRecord 中;
- ViewModel
ViewModel可以存放任意 Object 数据,因配置变更导致的页面被回收才有效。此时存在ActivityThread#ActivityClientRecord 中。