Android Jetpack — ViewModel
ViewModel
是为了更好的以生命周期的方式管理界面相关的数据。
以一个简单的计数 demo 来演示之间的区别。
图1.gif上图中,是以平常的方式实现的计数器,当我们旋转屏幕而没有其他处理的时候,计数器的数据丢失了。这是因为屏幕旋转时,我们的activity
被销毁重建了,而存放在activity
的数据自然也是没有了。
class MainActivity : AppCompatActivity() {
private lateinit var mBtnAdd: Button
private lateinit var mTvNum: TextView
private var mNum = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
}
private fun init() {
mBtnAdd = findViewById(R.id.btn_add)
mTvNum = findViewById(R.id.tv_num)
mTvNum.text = mNum.toString()
mBtnAdd.setOnClickListener {
mNum++
mTvNum.text = mNum.toString()
}
}
}
这时,我们采用ViewModel
来实现,效果如图:
可见,旋转屏幕后,数据还被保存了下来。
那ViewModel
究竟怎么使用?以及是什么原理呢?
1. ViewModel的使用
1.1 依赖
ViewModel
的依赖,可以查看官方文档,导入最新的版本。
现在示例使用的版本:
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
1.2 创建一个ViewModel类
这个ViewModel
类的作用主要是用于保存与View
相关的数据。
在此次的demo中,就是要把计数的mNum
存到ViewModel
中。
*** 需要注意的是ViewModel
类中不应该持有Activity
、Fragment
、view
的引用,具体原因后面会讲解释***
如果确实需要,应该使用applicationcontext
,或者使用含有上下文的AndroidViewModel
。
class NumberViewModel : ViewModel() {
var num = 0
}
1.3 View中关联ViewModel
class MainActivity2 : AppCompatActivity() {
private lateinit var mBtnAdd: Button
private lateinit var mTvNum: TextView
private lateinit var mViewModel: NumberViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_2)
init()
}
private fun init() {
mBtnAdd = findViewById(R.id.btn_add)
mTvNum = findViewById(R.id.tv_num)
//获取ViewModel
mViewModel = ViewModelProvider(this).get(NumberViewModel::class.java)
//使用ViewModel中的数据
mTvNum.text = mViewModel.num.toString()
mBtnAdd.setOnClickListener {
mViewModel.num = mViewModel.num + 1
mTvNum.text = mViewModel.num.toString()
}
}
}
ViewModelProvider(this).get(NumberViewModel::class.java)
//2.2.0-alpha02 以前的版本是通过下面的方式,新版本已弃用
//ViewModelProviders.of(this).get(NumberViewModel::class.java)
这里获取到当前Activity
对应的NumberViewModel
(第一次是创建->保存->返回),然后就可以直接使用了。
这里的this
可以 FragmentActivity
、AppCompatActivity
或 Fragment
。
2. ViewModel的内部实现
一步步跟进ViewModel
的内部实现。
首先是通过ViewModelProvider(this)
构造方法,创建一个ViewModelProvider
,并在其构造方法中,获取到存储ViewModel
的ViewModelStore
对象。
/**
* Creates {@code ViewModelProvider}. This will create {@code ViewModels}
* and retain them in a store of the given {@code ViewModelStoreOwner}.
* <p>
* This method will use the
* {@link HasDefaultViewModelProviderFactory#getDefaultViewModelProviderFactory() default factory}
* if the owner implements {@link HasDefaultViewModelProviderFactory}. Otherwise, a
* {@link NewInstanceFactory} will be used.
*/
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
owner
就是我们传入的this
(FragmentActivity
、AppCompatActivity
、Fragment
)。
我们看看XXXActivity
中的getViewModelStore
方法。
/**
* Returns the {@link ViewModelStore} associated with this activity
* <p>
* Overriding this method is no longer supported and this method will be made
* <code>final</code> in a future version of ComponentActivity.
*
* @return a {@code ViewModelStore}
* @throws IllegalStateException if called before the Activity is attached to the Application
* instance i.e., before onCreate()
*/
@NonNull
@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.");
}
if (mViewModelStore == null) {
//如果当前的mViewModelStore为空,会先向nc中取mViewModelStore,这里边存储的就是上一次Activity对应的实例的mViewModelStore
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
可见,这里的关键是NonConfigurationInstances
。在设备旋转的时候,当前Activity
被销毁了,mViewModelStore
等数据会被封装到NonConfigurationInstances
中存储出来,创建了新的对象会重新传入该NonConfigurationInstances
。
然后是存储ViewModel
的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();
}
}
可见ViewModelStore
还是比较简单的,就是用HashMap
来存储的数据。
ViewModel
的生成和获取相关, 是在ViewModelProvider
的get
方法中。
/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this {@code ViewModelProvider}.
* <p>
* The created ViewModel is associated with the given scope and will be retained
* as long as the scope is alive (e.g. if it is an activity, until it is
* finished or process is killed).
*
* @param modelClass The class of the ViewModel to create an instance of it if it is not
* present.
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
//获取我们传入的NumberViewModel简称(就这样叫吧),作为key的一部分
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//从mViewModelStore中取出viewModel
ViewModel viewModel = mViewModelStore.get(key);
//判断viewModel是否是modelClass类的实例
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
//如果是则直接返回已存在的viewModel
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
//否则通过工厂来新生成一个viewModel实例
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//把新生成的viewModel实例存入mViewModelStore中
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
3. ViewModel的生命周期
引用官网的一张图片
图3.pngonCreate
在Activity
的生命周期内可能会多次调用(例如,本例中的旋转应用程序时),但ViewModel
会在整个过程中保留下来。
这张图也解释了为什么ViewModel
中不能持有Activity
、Fragment
、view
的引用。因为Activity
在重建后是一个新的对象,如果ViewModel
中持有旧对象的引用,这个旧对象可能就等不到释放,造成泄漏。