【Android】ViewModel 用法及源码解析
本文讲解 ViewModel 用法,以及 ViewModel 源码解析。
官方文档:https://developer.android.google.cn/topic/libraries/architecture/viewmodel
一句话介绍 ViewModel :以注重生命周期的方式存储和管理界面相关的数据,让数据可在发生屏幕旋转等配置更改后继续留存。
注:本文使用 Kotlin 编写。
导入 Lifecycle 库:implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
一、用法
1. 自定义布局,一个 TextView ,两个 Button
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/app_name" />
<Button
android:id="@+id/btn_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Plus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/btn_minus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Minus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2. 自定义 ViewModel 类
class MainViewModel : ViewModel() {
var count = 0
}
3. 在 Activity 里获取 ViewModel 实例
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get<MainViewModel>(MainViewModel::class.java)
tv_content.text = mainViewModel.count.toString()
// 加
btn_plus.setOnClickListener {
mainViewModel.count++
tv_content.text = mainViewModel.count.toString()
}
// 减
btn_minus.setOnClickListener {
mainViewModel.count--
tv_content.text = mainViewModel.count.toString()
}
}
}
4. 执行程序,点击 Button ,并旋转屏幕,发现数值不会被重置,也就是 ViewModel 没有被重建。
二、源码解析
1. ViewModel 源码
public abstract class ViewModel {
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
onCleared();
}
@SuppressWarnings("unchecked")
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T getTag(String key) {
if (mBagOfTags == null) {
return null;
}
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
ViewModel 是一个抽象类,通过查看源码发现 ViewModel 就是一个数据容器,把 Activity 或 Fragment 里面的数据直接放到 ViewModel 里面即可,它就是一个对象,让对象把这些数据存起来,不根据生命周期进行销毁,就是 ViewModel 它的功能。onCleared 这个函数官方对它的解释是 “This method will be called when this ViewModel is no longer used and will be destroyed.“ ,也就是说在 Activity 执行到 onDestroy 的时候会回调这个 onCleared 函数。在 onCleared 这个函数里可以执行数据清除的操作或者对观察者做解注册、解观察等。
引用一下官方文档的图:
Q:ViewModel 是如何实现在生命周期发生变化时或重建时 ViewModel 没有改变?
A:从 mainViewModel = ViewModelProvider(this).get<MainViewModel>(MainViewModel::class.java) 这段代码入手,
ViewModelProvider 的 get 函数:
@NonNull
@MainThread 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 。
通过 modelClass 可以获取到 ViewModel 的类名,用 DEFAULT_KEY + ":" + 类名 去 get 这个 class 。
public < T extends ViewModel > T get(@NonNull String key, @NonNull Class < T > modelClass) {
// 用 key 来 get 到 ViewModel
ViewModel viewModel = mViewModelStore.get(key);
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.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory)(mFactory)).create(key, modelClass);
} else {
// 如果 key 存的不是 modelClass ,就要重建一个 ViewModel
viewModel = (mFactory).create(modelClass);
}
// 通过 Map 把 key 对应的 ViewModel 保存起来
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
通过 ViewModelStore 的 get 和 put ,可以知道到 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());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
发现 ViewModelStore 是一个 Map 。通过 key-value 形式把 ViewModel get 出来,也可以看出 ViewModelStore 不跟生命周期进行绑定。
ViewModelStore 这个实例也是在 ViewModelProvider 里。通过源码:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
发现在 ViewModelProvider 实例化的过程中,owner 是 其实是我们传进来的 Activity 。owner 去 get 这个 store 。
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
所以说 Activity 肯定是实现了 ViewModelStore 这个类。
查看 AppCompatActivity 的父类 ComponentActivity :
public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {}
发现 ComponentActivity 实现了 ViewModelStoreOwner 这个接口。
查看 ViewModelStoreOwner 的 getViewModelStore 在 ComponentActivity 是如何实现的:
public ViewModelStore getViewModelStore() {
...
if (mViewModelStore == null) {
// 跟 Activity 屏幕方向有关
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// 获取 ViewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
// 实例出来一个 ViewModelStore
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
捋顺一下流程:在首次执行或重建 onCreate 的时候,都会调用这个 getViewModelStore() 函数。第一次的时候都为空,会 new 出来一个。第二次的时候直接 return 。
再来看 getLastNonConfigurationInstance() ,它是 Activity 源码里面的一个函数。
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity: null;
}
static final class NonConfigurationInstances {
Object activity;
HashMap < String,
Object > children;
FragmentManagerNonConfig fragments;
ArrayMap < String,
LoaderManager > loaders;
VoiceInteractor voiceInteractor;
}
@UnsupportedAppUsage
NonConfigurationInstances mLastNonConfigurationInstances;
其实是一个容器,但是他不跟随 Actiivty 生命周期变化而变化。在系统层面,重建的过程中还会 get 到,而不是被序列化的那种存储。
所以说 ViewModelStore 一直没有变,在重建的过程中获取到的 store 还是上一个 store ,获取的 ViewModel 还是上一个 ViewModel 。
这样 ViewModel 就实现了不跟 Activity 生命周期进行销毁重建这些流程了。
在继续看父类这个 Activity 源码:
public final Object onRetainNonConfigurationInstance() {
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
在状态或配置发生改变的时候会把 viewModelStore 存起来。然后下次在用的时候会获取到。
总结:在 MainActivity 里面,通过 ViewModelProvider ,传递 owner 参数,owner 获取到的 ViewModelStore 。然后通过 ViewModelStore 里面获取上一个 ViewModel 对象。
注:ViewModelProvider 在 get 的时候传了一个 key
private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";
这个 key 拼接的是类的对象,这个 ViewModelStore 存在于 MainActiivty 中,所以说我们每次获取的是当前 Activity ,如果说在创建 Activity 的话,在获取的 ViewModel 就不是上一个 Activity ,而是下一个 Activity 的。因为新的 Activity 里获取的还是空的,会在创建一个。所以说 ViewModel 不是全局的。
希望本文对你有帮助,感谢大家支持!!!
本文地址:https://blog.csdn.net/cnwutianhao/article/details/107727413