一. 什么是ViewModel
官方对ViewModel的定义:
- 类职责:负责为界面准备数据(意味着一切处理数据逻辑的业务代码,应该写在ViewModel中)
- 在配置更改期间会自动保留ViewModel对象:因此可以作为跨页面(Fragment)通讯的基石
二. ViewModel有什么优点
- Activity配置更改重建时(比如屏幕旋转)保留数据
- UI组件(Activity与Fragment、Fragment与Fragment)间实现数据共享
对于第一条不用VM的情况下只能通过onSaveInstanceState保存数据,当activity重建后再通过onCreate或onRestoreInstanceState方法的bundle中取出,但如果数据量较大,数据的序列化和反序列化将产生一定的性能开销。
对于第二条如果不用VM,各个UI组件都要持有共享数据的引用,这会带来两个麻烦:①. 如果新增了共享数据,各个UI组件需要再次声明并初始化新增的共享数据;②. 某个UI组件对共享数据修改,无法直接通知其他UI组件,需手动实现观察者模式。而VM结合LiveData就可以很轻松的实现这一点。
知道了它的优化,下面我们来看看如果使用它
三. 如何使用
首先写个类,继承 ViewModel
class UserViewModel : ViewModel() {
private var users: MutableLiveData? = null
fun getUsers(): LiveData? {
if (users == null) {
users = MutableLiveData()
users?.value = "hello world"
}
return users
}
}
然后后Activity里面使用
class ViewModelActivity : AppCompatActivity() {
lateinit var viewmodel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_model)
setTitle("ViewModel的使用")
//通过ViewModelProvider获取实例
viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
viewModel.getUsers()?.observe(this, Observer {
Log.d("ViewModel", it)
})
}
}
如果引入了依赖库
implementation "androidx.fragment:fragment-ktx:1.2.4"
那么上述实例化ViewModel可以通过by关键字方便实现
private val viewModel by viewModels()
// Activity中如果共享的ViewModel
private val viewModel by activityViewModels()
activityViewModels
是什么呢?如果Activity中有好几个Fragment,那么这几个Fragment通过activityViewModels
方法拿到的ViewModel
都是同一样。这样就实现了数据的共享
当然viewModels
和activityViewModels
也是通过ViewModelProvider(this).get
方法创建的。所以我们看源码,也是从ViewModelProvider(this).get
方法看是如果生成对象的。通过源码就可以清楚的知道为什么它会有那两个优点了
四、调用源码分析
- 构造方法
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
ViewModelProvider
的构造方法传入的是一个ViewModelStoreOwner
,获取的是owner.getViewModelStore()
方法,getViewModelStore
返回的是一个ViewModelStore
对象。而ViewModelStore
对象就比较简单,里面就主要有个Map
存储ViewModel
。
看到这里可以猜测,我们获取的ViewModel
存储应该就是在这个ViewModelStore
里面,类似于缓存一样。
ViewModelProvider(this)
方法传的是当前Activity对象,那么肯定是哪个父类继承了ViewModelStoreOwner
接口。往上找发现FragmentActivity实现了这个接口
在FragmentActivity里面有个全局变量mViewModelStore
public class FragmentActivity extends ComponentActivity implements
ViewModelStoreOwner,
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompat.RequestPermissionsRequestCodeValidator {
...
private ViewModelStore mViewModelStore;
...
@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) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
...
}
FragmentActivity里面实现了getViewModelStore
方法,这里是个关键点,可以看出来,如果mViewModelStore
为null,则去NonConfigurationInstances
里面拿,那这个getLastNonConfigurationInstance
从哪里拿的?这里我们暂且不跟,先看它是如何创建ViewModel的,如果前面获取都为null,最后是直接new了一个出来。
继续看ViewModelProvider#get
方法
public T get(@NonNull Class modelClass) {
String canonicalName = modelClass.getCanonicalName();
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
通过类名和前缀DEFAULT_KEY 生成一个字符串key,继续调用重载方法,从前面mViewModelStore
里面拿出来一个ViewModel
,如果不为空,则直接返回,可见mViewModelStore
确实就是缓存用的,如果为空。则通过mFactory
创建一个。并且放入mViewModelStore
。
public T get(@NonNull String key, @NonNull Class modelClass) {
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.
}
}
//通过factory创建viewModel
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//存入mViewModelStore
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
这个代码也好理解,就是从mViewModelStore里面拿对象,拿不到就让factory去生成一个。由于我们没有传入factory,这里的factory就是默认的NewInstanceFactory。而NewInstanceFactory逻辑也简单,也就是直接反射生成一个对象。
分析到这我们发现,ViewModelProvider.get方法其实逻辑就是反射生成一个ViewMode,并存储到当前Activity的mViewModelStore中,
那么问题来了,既然旋转屏幕,Activity会重新创建,那么它里面的变量mViewModelStore怎么还能保持是同一个?
答案就在刚刚的FragmentActivity#getViewModelStore
方法中,继续看这个方法,这里的mViewModelStore
并不是重新new,而是从一个NonConfigurationInstances
对象中去拿,而这个getLastNonConfigurationInstance
方法那就是这个问题的关键所在了。
public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
我们继续看这个
getLastNonConfigurationInstance
方法
按ctrl键点进去,进了Activity.java里面了
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
我们关心的是这个对象是在哪里赋值的,在attach
方法里面发现了赋值的地方
final void attach(...) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
....
mLastNonConfigurationInstances = lastNonConfigurationInstances;
}
那么attach方法里面的这个lastNonConfigurationInstances
又是从哪里来的呢?
在ActivityThread
里面的performLaunchActivity
方法,创建时会调用这个方法绑定一些重要的数据到Activity,
现在我们知道lastNonConfigurationInstances
是存在ActivityClientRecord
中的。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
...
return activity;
}
那么r.lastNonConfigurationInstances
又是在哪里赋值的呢?
同样的
ActivityThread#performDestroyActivity
方法中我们看到了赋值操作。如果发生了配置变化,会调用activity的retainNonConfigurationInstances
方法,存入ActivityClientRecord
中
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
...
if (getNonConfigInstance) {
try {
//如果发生了配置改变,数据存起来
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to retain activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
}
...
return r;
}
似乎离真相越来越近了,我们继续看retainNonConfigurationInstances
方法
//Activity#retainNonConfigurationInstances
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}
里面回调了onRetainNonConfigurationInstance
方法,而FragmentActivity复写了这个方法,把viewModelStore
存入了NonConfigurationInstances
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
总结
事情的起因是我们通过
ViewModelProvider
去获取存放在mViewModelStore
中的ViewModel
。然后就在发现ViewModelProvider
的构造方法要先去取ViewModelStore
。
1. 如果mViewModelStore
不为空,就当场返回
2. 如果mViewModelStore
为空,就去通过getLastNonConfigurationInstance()
取NonConfigurationInstances
,再从NonConfigurationInstances
中取。
2.1NonConfigurationInstances
如果不为空,就直接返回nc.viewModelStore
2.2NonConfigurationInstances
如果为空,再判断一遍mViewModelStore
也为空【上面已经刚判断过一次】,就new一个mViewModelStore
出来并返回
那么,
getLastNonConfigurationInstance()
返回的NonConfigurationInstances
怎么就能取出来mViewModelStore
了,啥时候放进去的?
答:是Activity#attach方法时赋值的
那么,attach方法中拿来给
mLastNonConfigurationInstances
赋值的lastNonConfigurationInstances
又是哪里来的?
答:是ActivityClientRecord中保存的,在执行ActivityThread#performLaunchActivity
方法时传入的ActivityClientRecord
,然后方法内调用activity.attach
方法,传入r.lastNonConfigurationInstances
。
那么,这个
r.lastNonConfigurationInstances
又是什么时候赋值的?
答:执行performDestroyActivity
的时候将r.activity.retainNonConfigurationInstances()
赋值给了r.lastNonConfigurationInstances
。
那么,这个
retainNonConfigurationInstances
方法内部做了什么?
答:它调用了onRetainNonConfigurationInstance
,而FragmentActivity复写了这个方法,把viewModelStore
存入了NonConfigurationInstances
。
相当于是当Activity销毁【执行
performDestroyActivity
】时,
①会把ViewModelStore
存储在-->NonConfigurationInstances
②然后把NonConfigurationInstances
存储在-->ActivityClientRecord
然后重启Activity【执行performLaunchActivity】时,会调用attach
方法,在其中把ActivityClientRecord
的NonConfigurationInstances
给Activity