一、ViewModel简介
ViewModel的出现主要有以下几个方面的考虑:
(1)在编写应用程序的时候,如果在Activity和Fragment中写入过多的逻辑,Activity会变得非常臃肿,不利于代码的维护,也违背了软件的分层思想。根据MVVM模型,可以将业务逻辑从View层拆分出来,Activity和Fragment只负责界面渲染相关、响应用户事件、进行权限申请等工作,将业务逻辑交给ViewModel来操作。
(2)在原来的应用写法中,常常会在Activity中去进行网络访问等异步调用,所以Activity需要根据自己的生命周期去管理这些调用,在必要的时候释放一些异步调用,做一些清理工作,以防止潜在的内存泄露。并且还要考虑当Activity发生configration变化时这些资源的重建等,非常麻烦,这里我们可以使用ViewModel来专门管理这些操作。
(3)在没有ViewModel时Fragment之间的通信,通常比较麻烦,需要通过Activity来进行管理,不可避免的会产生相互持有引用的情况,或者使用EventBus这样的第三方框架来通讯,这里使用ViewModel+LiveData的方式,可以更方便的进行Fragment之间的数据交换。
(4)另外,ViewModel的生命周期要比Activity长,所以方便在有Activity重建的场景下保存数据。借用一张官网的图,下图对比了Activity在屏幕旋转时的生命周期和ViewModel的生命周期。
这里需要特别注意的一点是,ViewModel中不要有Activity的引用,因为ViewModel生命周期比Activity生命周期要长,销毁Activity时可能因为ViewModel的引用导致Activity实例无法正确释放而发生内存泄露。
二、ViewModel使用
2.1 在Activity中使用ViewModel
首先我们要创建一个自己的ViewModel类,继承自ViewModel抽象类。
class UserViewModel : ViewModel() {
private var repository: ApiRepository = ApiRepositoryImpl()
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
getUsersFromNet()
}
}
private fun getUsersFromNet() {
Thread(Runnable {
TimeUnit.MILLISECONDS.sleep(3000)
users.postValue(repository.getUsersFromNet())
}).start()
}
fun getUsers(): LiveData<List<User>> {
return users
}
override fun onCleared() {
super.onCleared()
}
}
在这里,ApiRepository是数据请求接口。然后我们定义了一个MutableLiveData类型,关于LiveData以后再详细讲,LiveData数据发生变化时会通过回调通知订阅者,订阅者则可以根据自己的情况更新UI。
这里需要注意的是onCleared()方法,这个方法在ViewModel销毁是会被系统调用,在这里可以做一些清理工作,比如Bitmap回收、网络连接释放等。
然后我们需要初始化ViewModel对象。因为现在ViewModelProviders
工具类已经被弃用,所以这里我们使用ViewModelProvider
类来初始化ViewModel类。
var model = ViewModelProvider(this).get(UserViewModel::class.java)
或
var model = ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(UserViewModel::class.java)
上面两种初始化的方法唯一不同是,下面的方法多加了一个Factory参数。
然后添加观察者
model.getUsers().observe(this, Observer<List<User>> { users ->
Log.d("=====", users.toString())
})
2.2 Fragment之间交换数据
两个Fragment之间交换数据是常见的一种应用场景,首先我们建立公共的ViewModel类。
class BottomNavViewModel: ViewModel() {
val text = MutableLiveData<Int>().apply {
value = 1
}
}
这个类,只维护一个Int类型的LiveData。
然后创建数据生成的Fragment。
class HomeFragment : Fragment() {
private lateinit var navViewModel: BottomNavViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
navViewModel =
ViewModelProvider(requireActivity()).get(BottomNavViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
textView.setOnClickListener {
navViewModel.text.value = navViewModel.text.value!!.inc()
}
return root
}
}
在数据生成类中,点击textView,增加BottomNavViewModel中text变量的值。然后创建接收数据的Fragment。
class DashboardFragment : Fragment() {
private lateinit var navViewModel: BottomNavViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
navViewModel =
ViewModelProvider(requireActivity()).get(BottomNavViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_dashboard, container, false)
val textView: TextView = root.findViewById(R.id.text_dashboard)
navViewModel.text.observe(requireActivity(), Observer {
textView.text = it.toString()
})
return root
}
}
当BottomNavViewModel中text值发生变化时更新DashboardFragment上textView的值。
三、ViewModel源码解析
首先我们从ViewModel初始化的这句代码开始看。
var model = ViewModelProvider(this).get(UserViewModel::class.java)
这句代码分两步进行,先创建了一个ViewModelProvider
对象,然后调用get
方法。
3.1、ViewModelProvider类创建
我们点开看ViewModelProvider
类这个只有一个参数的构造方法。
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
在这个方法里传入需要传入一个ViewModelStoreOwner
对象,通过其getViewModelStore
方法获取ViewModelStore
对象。而上面我们在Activity中是传入的一个Activity对象,于是我们找到Activity的继承链:AppCompatActivity
–>FragmentActivity
–>ComponentActivity
,这里的ComponentActivity
就实现在了ViewModelStoreOwner
接口。
ViewModelProvider
类有三个构造方法,最终都会执行这个构造方法:
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
在这个构造方法中需要传入一个ViewModelStore
对象,一个Factory
对象。
这个ViewModelStore
对象就是我们用来在Configuration变化时存储ViewModel的地方。
3.1.1、Factory
Factory
对象则是用来创建ViewModel实例用的。我们常常会遇到的Factory有三个:
一个是当我们使用只有一个参数的构造函数时,系统默认的HasDefaultViewModelProviderFactory
对象,这是一个接口,是由ComponentActivity
实现的,由于它的实现机制过于复杂,这里我也没完全看懂,所以就不写了。
二是AndroidViewModelFactory
这个Factory接受一个Application
对象,它返回的ViewModel的生命周期与Application
相同。
三是NewInstanceFactory
这个Fractory返回一个普通的ViewModel对象。
三个Factory的代码较多,这里我就不贴出来了,有兴趣的同学可以自己去看。
3.1.2、ViewModelStore
3.1.2.1 ViewModelStore创建过程
在说到ViewModelProvider
类的创建时我们提到,只有一个参数的构造方法需要传ViewModelStoreOwner
对象,而这个对象就是ComponentActivity
,然后我们去看ComponentActivity
中是如何实现这个方法的。
@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;
}
这里的mViewModelStore
就是我们的ViewModelStore
实例对象。代码中首先判断mViewModelStore是否为空,若为空,则使用getLastNonConfigurationInstance
方法从之前存储的数据中去获取NonConfigurationInstances
,而我们的ViewModelStore
对象就存储在其中;如果之前没有存储过mViewModelStore
对象,则新创建一个,新创建的mViewModelStore
对象,在Activity销毁前系统会调用onRetainNonConfigurationInstance
方法来保存数据。
这里需要注意的就是getLastNonConfigurationInstance
和onRetainNonConfigurationInstance
两个方法是对应的,一个保存一个获取,都由系统来调用,后者在销毁Activity前调用,前者在Activity创建后都可调用。
3.1.2.2 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类,这里需要注意的有两点。
一个是put
方法,put
方法在往HashMap中放入一个ViewModel后,如果之前有相同key的ViewModel,则需要被清理。
二个是clear
方法,clear
方法遍历HashMap中的ViewModel,然后调用每个ViewModel的clear方法来进行清理操作。那么它是如何被调用的呢?我们可以在ComponentActivity
的构造方法中找到相关的代码,当Activity被destroy并且不是处于configuration正在变化的状态时调用clear
方法
public ComponentActivity() {
//省略
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();
}
}
}
});
//省略
}
3.2、ViewModelProvider.get()
我们再来看初始化语句的第二步get
方法。
var model = ViewModelProvider(this).get(UserViewModel::class.java)
该方法传入一个ViewModel的Class对象。
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);
}
该方法调用下面的方法
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> 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.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
这个方法中涉及到OnRequeryFactory
和KeyedFactory
,与上面的HasDefaultViewModelProviderFactory
相关,就不详述了,理由跟上面一样。
我们简单理解这个方法,就是从mViewModelStroe对象中通过键获取我们的ViewModel对象。
四、总结
(1)ViewModel中不要持有Activity的引用。
(2)ViewModel对象存储在ViewModelStroe
中,ViewModelStroe
通过getLastNonConfigurationInstance
和onRetainNonConfigurationInstance
两个方法由系统来存储。
(3)当Activity不是因为configuration变化而销毁时,ViewModel才会被回收。
(4)关于应用方面的总结看第一段简介。