「Jetpack-ViewModel」
一、概览
ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据,并且即使在屏幕发声旋转等配置更改后依然可以继续保留数据。
ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment. It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).
ViewModel作为Android Jetpack的一部分,其目的就是为UI(一般是Activity或Fragment)提供数据以展示,将UI与数据隔离开,也是数据驱动模型的基础。早期在onSaveInstanceState()中只能通过序列化与反序列化的方式保存少量的数据。对大量数据的展示不适用,其次大多数数据都是异步请求的,界面重建会导致重新创建对象,造成资源浪费;另外对资源的管理如资源释放也需要做大量且细致的工作避免造成内存的泄露。ViewModel很好的解决了这些矛盾。
- ViewModel负责为UI提供与管理数据—数据驱动
- 对于屏幕旋转或其他配置信息改变时恢复数据—使用原始数据避免重新创建对象浪费资源
- 管理界面相关数据并注重生命周期—避免潜在的内存泄漏
二、生命周期
ViewModel绝对不能引用视图,Lifecycle 或可能存储对 Activity 上下文的引用的任何类。
ViewModel是不能够持有对UI的引用的,究其原因-ViewModel的生命周期是要比UI的生命周期长的。当生命周期长的对象被生命周期短的对象(UI)持有,GC时是无法回收的,存在潜在的内存泄漏风险。官方也明确指出了这一点。看看ViewModel的生命周期:
只有当Activity完全Finished时ViewModel才会被清除,而之前提到的即Activity因配置或者旋转屏幕时的数据恢复ViewModel是不会重新创建实例的。这显然是ViewModel的生命周期更长。
三、使用
引入相关库
val lifecycle_version = "2.5.0-alpha01"
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
ViewModel使用比较简单,自定的业务CustomViewModel需要继承抽象类ViewModel,大致分为几个部分:
- 实现CustomViewModel继承ViewModel。
- 通过MutableLiveData将具体的业务数据VO包裹如用户列表MutableLiveData<List<UserVO>> userVOS。
- 根据具体业务通过Repositories请求数据更新LiveData并将数据抛出。
- 在UI实例化CustomViewModel,并观察数据以更新UI。
1.示例代码
以获取用户列表为例,那么就会有几个基本类,Activity、UserViewModel、UserRepository。
- UserRepository
class UserRepository {
suspend fun fetchUserVOS(param: String) {
//todo
}
}
- UserViewModel
class UserViewModel : ViewModel() {
private val userVOS: MutableLiveData<List<UserVO>> by lazy {
MutableLiveData<List<UserVO>>().also {
loadUsers()
}
}
fun getUserVOList(): LiveData<List<UserVo>> {
return userVOS;
}
private fun loadUsers() {
UserRepository.fetchUserVOS("xx")
}
}
- Activity
class Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val model: UserViewModel by viewModels()
model.getUsers().observe(this, Observer<List<User>>{ users ->
//todo
})
}
}
代码实现比较简单,通过对数据LiveData的监听,当UserViewModel中持有的LiveData发生变动时就会反应到UI界面上。数据驱动的形式解耦了数据类与UI类的之间的关联,即UI只关心对数据的展示操作,而获取数据的逻辑被移到了UserViewModel之中。其次UserViewModel是关注生命周期的,因此UI也不需要在处理资源的释放问题。
2.数据共享
Activity中多个Fragment数据共享的情况在实际开发过程比较常见,如点击列表条目,并展示详情并且在同一屏展示。ViewModel同样适合这种场景,但仅仅需要注意的是,在实例化共享ViewModel时传入的Owner为Activity,即可以达到通信的目的。以点击列表条目展示详情为例:
两个Fragment同时依附于Activity,并共享同一个SharedViewModel.
- XML文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="16dp"
tools:context=".Activity">
<fragment
android:id="@+id/fragment1"
android:name=".ListFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragment2"
android:name=".DetailFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
- 共享ViewModel-SharedViewModel
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
- 列表ListFragment
class ListFragment : 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
}
}
}
- 详情DetailFragment
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
})
}
}
当两个Fragment分别获取ViewModelProvider时,得到的就是同一个SharedViewModel对象,但前提是实例化时传入的owner为同一个宿主Activity。那么就看看具体源码实现细节,看看到底内部是如何实现存储和获取的。
四、实现细节
着重需要看有几个关键类,ViewModel、提供ViewModel实例的ViewModelProvider、存储ViewModel实例的存储类ViewModelStore,Activity类中对ViewModel相关的具体实现。
1.ViewModel
ViewModel是一个抽象类,用户自定义业务的CustomViewModel仅仅需要继承ViewModel即可,内部包含clear清除方法,一般不需要开发者实现自己的逻辑。当需要被清理时由系统本身处理释放资源。
public abstract class ViewModel {
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
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();
}
//....
}
2.存储类ViewModelStore
用于保存ViewModel对象的实例,看一段源码中的注释是怎样对其定义的:
Class to store ViewModels.
An instance of ViewModelStore must be retained through configuration changes: if an owner of this ViewModelStore is destroyed and recreated due to configuration changes, new instance of an owner should still have the same old instance of ViewModelStore.
注释中清楚的解释了,UI界面如Activity如果因为配置原因发生销毁后重建,ViewModelStore如果重建应该如旧的实例保持一致。这里也可以看出一些细节,如果ViewModelStore与旧的保持一致,那么ViewModel势必也会一致,那么Activity因旋转而创建获得到的ViewModel实例是旧实例。源码实现很简单:
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();
}
}
- 内部用一个Map用于保存众多的ViewModel对象;
- 但是存储的Key在这里还不是很明确,对于新添加的ViewModel对象,会判断是否存在旧对象,如果有则清除并更新。
3.提供类ViewModelProvider
public open class ViewModelProvider(
private val store: ViewModelStore,
private val factory: Factory
){
public interface Factory {
public fun <T : ViewModel> create(modelClass: Class<T>): T
}
public companion object {
internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
if (owner is HasDefaultViewModelProviderFactory)
owner.defaultViewModelProviderFactory else instance
internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
private var sInstance: AndroidViewModelFactory? = null
@JvmStatic
public fun getInstance(application: Application): AndroidViewModelFactory {
if (sInstance == null) {
sInstance = AndroidViewModelFactory(application)
}
return sInstance!!
}
}
@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
var viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
viewModel = if (factory is KeyedFactory) {
factory.create(key, modelClass)
} else {
factory.create(modelClass)
}
store.put(key, viewModel)
return viewModel
}
}
ViewModelProvider的构造函数比较简单,看关键参数ViewModelStore,之前提到ViewModelStore是用来存储ViewModel对象的类。这个很好理解,当ViewModel被ViewModelProvider创建并被存储到ViewModelStore当中,而这个key在这里可以明确的是DEFAULT_KEY+包名+类名。
DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
而实例化ViewModelProvider传入的上下文参数就相当于ViewModelStore的持有者也即是ViewModelStoreOwner,这是一个接口:
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
而它的实现类为Activity与Fragment,这也是为什么实例化时可以直接传入this。简单总结一下流程:
存储ViewModel对象以DEFAULT_KEY+包名+类名为key,如果ViewModelStore已存在实例则直接返回。
如果不存在ViewModel的实例,则通过反射来创建,并存储到ViewModelStore之中。
ViewModelProvider实例化时传入的上下文可以是Activity或者Fragment,而他们都实现了ViewModelStoreOwner接口,通过Owner获取自身的ViewModelStore(存储ViewModel实例),到这里可以解释,两个不同Fragment依附于同一个Activity时获得的ViewModel对象是同一个。因此做到了数据共享的目的,当然前提是使用的是Activity作为上下文对象。
4.ViewModelStoreOwner的实现类
之前提到的ViewModelStoreOwner是一个接口,而其实现类有AppCompatActivity、ComponentActivity、Fragment等,以ComponentActivity为例具体分析实现细节,解决当Activity在发生屏幕旋转的情况下ViewModel是如何恢复数据并ViewModel本身不会被重新创建实例。
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
在ComponentActivity中对getViewModelStore()
的具体实现:
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.");
}
ensureViewModelStore();
return mViewModelStore;
}
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
对于获取ViewModelStore的流程,源码比较清晰,ensureViewModelStore
方法中主要有几个步骤:
- 首先会尝试从
getLastNonConfigurationInstance
中去获取对应实例,而这个方法是其顶层父类Activity中的。 - ComponentActivity中定义了一个静态的内部类NonConfigurationInstances,如果通过
getLastNonConfigurationInstance
获取到结果,即NonConfigurationInstances不为空,那么直接取出viewModelStore对象并更新成员变量mViewModelStore。如果为空,那么直接实例化一个新的viewModelStore对象。
对成员变量mViewModelStore进一步分析,主要关注对其赋值取值的一些操作。跟踪其引用,可以找到方法onRetainNonConfigurationInstance:
//Retain all appropriate non-config state. You can NOT override this yourself! Use a //androidx.lifecycle.ViewModel if you want to retain your own non config state.
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
//Maintain backward compatibility.
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;
}
- 结合注释,方法主要作用-保留所有状态(且与系统配置不相关),可以理解为系统状态配置、旋转等不会影响到这个状态的恢复与保存。
- 代码中同样调用了顶层父类
getLastNonConfigurationInstance
方法,获取NonConfigurationInstances对viewModelStore进行恢复。 - 其次这里的逻辑实现其实是对Activity中
onRetainNonConfigurationInstance
的重写:
//Activity
//Called by the system, as part of destroying an activity due to a configuration change, //when it is known that a new instance will immediately be created for the new //configuration.
public Object onRetainNonConfigurationInstance() {
return null;
}
- 对于Activity中方法
getLastNonConfigurationInstance
,观察发现返回的是Activity实例本身,通过强转为NonConfigurationInstances,这个比较疑惑的点,来一步步分析具体是怎样操作的:
//Activity
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
//...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
//...
return nci;
}
//Check to see whether this activity is in the process of being destroyed in order to be //recreated with a new configuration. This is often used in onStop to determine whether the //state needs to be cleaned up or will be passed on to the next instance of the activity via //onRetainNonConfigurationInstance().
public boolean isChangingConfigurations() {
return mChangingConfigurations;
}
Activity中同样存在一个静态内部类NonConfigurationInstances,只是与ComponentActivity实现是不同的,注意
Object activity = onRetainNonConfigurationInstance();
,而这个具体的实现恰恰是在ComponentActivity之中。几处注释也说明了onRetainNonConfigurationInstance
的调用时机:1.当UI因配置改变,旋转屏幕导致重新创建时,此方法会被系统调用,恢复相关配置传递到新的Activity实例上。
2.当UI被用户正常销毁即非配置或旋转屏幕的情况下,保存的状态信息会被清除,此时需要借助mChangingConfigurations标志位。
3.当UI因配置而导致的重新创建不会引起ViewModel的重新创建,系统内部根据相关的标志位来处理,而具体的细节涉及到Activity的启动流程。Activity、 ActivityThread、ActivityClientRecord
- ViewModel何时才会被真正的清除呢,回到ComponentActivity之中,在构造函数中:
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
当不是由于配置原因或旋转造成的UI重建,也即用户“主动销毁”界面时ViewModel才会被真正清理。