Jetpack之ViewModel解析

360 阅读5分钟

1. ViewModel的简介和使用

1.1 简介

  • ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

1.2 生命周期

ViewModel 的生命周期与其作用域直接关联。ViewModel 会一直保留在内存中,直到其作用域 ViewModelStoreOwner 消失。以下上下文中可能会发生这种情况:

  • 对于 activity,是在 activity 完成时。
  • 对于 fragment,是在 fragment 分离时。
  • 对于 Navigation 条目,是在 Navigation 条目从返回堆栈中移除时。

这使得 ViewModels 成为了存储在配置更改后仍然存在的数据的绝佳解决方案。 image.png

1.3 简单使用

  • ViewModel是Android Jetpack为开发者提供的开发MVVM结构的组件,作为ViewModel层级通常是与LiveData/Flow等观察组件协同工作的,通常步骤为:
  1. 继承ViewModel自定义MyViewModel
  2. 在MyViewModel中编写获取UI数据的逻辑
  3. 使用LiveData将获取到的UI数据抛出
  4. 在Activity/Fragment中使用ViewModelProvider获取MyViewModel实例
  5. 观察MyViewModel中的LiveData数据,进行对应的UI更新
class UserViewModel : ViewModel() {

    private val _userLiveData = MutableLiveData<User>()
    val userLiveData: LiveData<User> = _userLiveData

    fun login() {
        viewModelScope.launch {
            // 模拟请求耗时
            delay(2000)
            _userLiveData.value = User(0, "BTPJ")
        }
    }
}
class UserActivity : AppCompatActivity() {

    private lateinit var mUserViewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        // 构建ViewModelProvider实例
        val viewModelProvider = ViewModelProvider(this)
        // 再利用viewModelProvider对象获取ViewModel实例
        mUserViewModel = viewModelProvider.get(UserViewModel::class.java)
        // 监听ViewModel中的LiveData更新
        mUserViewModel.userLiveData.observe(this) {
            findViewById<TextView>(R.id.tv_userName).text = it.name
        }

        findViewById<Button>(R.id.btn_login).setOnClickListener {
            mUserViewModel.login()
        }
    }
}

2. 源码分析

2.1 ViewModel的存储与获取

  • 先看下我们继承的ViewModel类是个啥
public abstract class ViewModel {
    ...
    /** 是否被销毁 */
    private volatile boolean mCleared = false;

    /** viewModel销毁前会调用的方法 */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }

    @MainThread
    final void clear() {
        mCleared = true;
        ...
        onCleared();
    }
    ...
}
  • ViewModel就是一个普通的抽象类,内部没啥逻辑,有个clear()方法会在ViewModel将被清除时调用,调用时会回调onCleared()

  • 接着从ViewModel实例的创建开始,先是调用ViewModelProvider(this)构造一个ViewModelProvider对象

public open class ViewModelProvider(
    private val store: ViewModelStore,
    private val factory: Factory
) {

    /** Factory接口的实现负责实例化ViewModels */
    public interface Factory {
    
        /** 根据ViewModel的class类创建ViewModel实例 */
        public fun <T : ViewModel> create(modelClass: Class<T>): T
    }
    ...

     /** 使用默认的Factory创建ViewModelProvider对象 */
    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner))

    /** 使用传入的Factory创建ViewModelProvider对象 */
    public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
        owner.viewModelStore,
        factory
    )
    
    ...
}
  • 其中有几个重要的类,大致可以从命名上得知各自的作用
  • ViewModelProvider:ViewModel提供者
  • ViewModelStore:存储ViewModel的容器
  • ViewModelStoreOwner:存储ViewModel容器的持有者
  • Factory:创建ViewModel实例的工厂
/** ViewModel的容器类 */
public class ViewModelStore {
    /** 利用HashMap存储ViewModel */
    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());
    }

    /** 清除容器中的ViewModel实例 */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            // 这里会调用viewModel的clear()
            vm.clear();
        }
        mMap.clear();
    }
}
  • ViewModelStore的代码很简单,就是利用HashMap容器来存储ViewModel,清除容器前会调用viewModel.clear()
/** 持有ViewModelStore容器的接口 */
public interface ViewModelStoreOwner {
    /** 通过该方法实现持有ViewModelStore */
    @NonNull
    ViewModelStore getViewModelStore();
}
  • ViewModelStoreOwner是个接口,实现类通过重写getViewModelStore()拿到当前作用域的ViewModelStore,在Activity/Fragment中获取ViewModel实例时,从ViewModelProvider(owner)中传递的是本身的Activity/Fragment对象,猜测Activity/Fragment一定是ViewModelStoreOwner的实现类,这个后面再验证
  • 先看下viewModelProvider.get(...)方法
@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")
    // 这里的key就是ViewModelStore中的Map的用于存 ViewModel的 Key
    // 默认为androidx.lifecycle.ViewModelProvider.DefaultKey:$canonicalName
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    // 从viewModelStore中根据key获取Map中存储的ViewModel实例
    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构建
        factory.create(modelClass)
    }
    // 构建后需要将viewModel存进容器
    store.put(key, viewModel)
    return viewModel
}

...
internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
...
  • 逻辑比较简单:先尝试从ViewModelStore获取ViewModel实例,key是"androidx.lifecycle.ViewModelProvider.DefaultKey:xxxViewModel",如果没有获取到,就使用Factory创建,然后存入ViewModelStore

2.2 ViewModelStore的存储与获取

  • 刚刚知道ViewModel实例是存储到ViewModelStore容器内,由ViewModelProvider提供出来的,那么接下来看下ViewModelStore是如何被存储和获取的,上面也分析到能获取到ViewModelStore获取实例的类均拥有ViewModelStoreOwner接口的实现,这里主要看看Activity中的实现
  • 具体实现ViewModelStoreOwner的Activity是ComponentActivity,是我们常用的AppCompatActivity的父类
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner,
        ActivityResultRegistryOwner,
        ActivityResultCaller {
...

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        // activity还没关联Application,即不能在onCreate之前去获取viewModel
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        // 确保mViewModelStore有值
        ensureViewModelStore();
        return mViewModelStore;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
           // 从lastNonConfigurationInstance从获取
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // 将ViewModelStore实例存进当前的NonConfigurationInstances类
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                // 没获取到就new一个
                mViewModelStore = new ViewModelStore();
            }
        }
    }
...
}
  • 先尝试从NonConfigurationInstance中获取ViewModelStore实例,如果NonConfigurationInstance中不存在,就new一个mViewModelStore。 并且还注意到,在onRetainNonConfigurationInstance()方法中 会把mViewModelStore赋值给NonConfigurationInstances:
// ComponentActivity.java
/** 在Activity因配置改变 而正要销毁时,且新Activity会立即创建,那么系统就会调用此方法 */
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // getViewModelStore()没被调用过
        // 从之前的配置mLastNonConfigurationInstances查找是否存在
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }
    
    // 不存在就new一个,并将viewModelStore赋值给他
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}
  • 可见,当配置改变时(即onRetainNonConfigurationInstance()回调时),系统把viewModelStore存在了NonConfigurationInstances中,在onRetainNonConfigurationInstance中会对viewModelStore进行保存,在getLastNonConfigurationInstance中会对viewModelStore进行恢复
  • 总结:onRetainNonConfigurationinstance在Activity因配置改变而正要销毁时,且新Activity会立即创建,那么系统就会调用此方法。此方法中把包裹着ViewModelstore的NonConfigurationinstances返回给ActivityThread中的ActivityClientRecord保存,在Activity重建的attach回调的时候会重新拿到这个NonConfigurationinstances

3. ViewModel对比onSaveInstanceState

  • 由于ViewModel是利用了onRetainNonConfigurationinstance这个方法,所以这个就变相是比较 onRetainNonConfigurationinstance和onSaveInstanceState
  • 存储数据的限制
    • ViewModel,可以存复杂数据,大小限制就是App的可用内存。
    • onSaveInstanceState只能存可序列化和反序列化的对象,且大小有限制(一般Bundle限制大小1M)
  • 存储方式
    • onSaveInstanceState()数据最终存储到ActivityManagerService的ActivityRecord中了,也就是存到系统进程中去了。
    • onRetainNonConfigurationInstance()(即ViewModel) 数据是存储到ActivityClientRecord中,也就是存到应用本身的进程中了
  • 生命周期
    • onSaveInstanceState:存到系统进程中,所以App被杀之后还是能恢复的。
    • onRetainNonConfigurationInstance(ViewModel):存到本身进程中,App被杀是没法恢复的
  • 使用场景
    • ViewModel适用于保存比较复杂的数据,而onSaveInstanceState适用于保存一些简单的数据。同时,ViewModel还具有生命周期感知和数据共享等特点,使得它更加适合保存和管理Activity和Fragment的数据