ViewModel组件解析

957 阅读12分钟

一、为什么使用ViewModel

通常情况下,我们会将UI交互、逻辑处理等业务逻辑也写在Activity/Fragment中,随着业务的迭代会导致页面代码臃肿且难以维护,并且不符合“单一责任”原则。页面应该只负责UI交互内容,相关数据的获取与逻辑处理应该单独存放和处理。

ViewModel是Android设计专门用于存放应用程序页面所需的数据。它将页面所需的数据从页面中剥离出来,页面只需要处理用户交互,以及负责展示数据的工作

ViewModel还有一重大功能就是解决资源配置导致的销毁重建问题。如果资源配置发生变更,我们需要考虑数据的存储与恢复,若不进行存储,则需要重新获取数据。而ViewModel独立于资源配置变化,即使资源配置变化导致Activity页面重建,也不会影响ViewModel的生命周期。

简单来说,ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据,让数据再发生屏幕旋转等配置更改后继续留存,对ViewModel之间起到了桥梁通信的作用。

ViewModel 对象存在的时间范围是获取 ViewModel时传递给ViewModelProviderLifecycleViewModel将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 activity,是在 activity 完成时;而对于 fragment,是在 fragment 分离时

二、基本使用

依赖引入

dependencies {
    def lifecycle_version = "xxx"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    // Lifecycles only (without ViewModel or LiveData)
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

    // Saved state module for ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"

    // Annotation processor
    kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
}

2.1 简单使用

ViewModel类主要负责为界面准备数据,并在配置更改期间自动保留ViewModel对象。

例如:在ViewModel中获取和存储用户信息

定义一个User数据类

data class User(var age:Int,var name:String)

实现viewModel

class UserModel:ViewModel() {
    val mUserLiveData:MutableLiveData<User> = MutableLiveData()
    init {
        //模拟从网络加载用户数据
        mUserLiveData.postValue(User(1,"name1"))
    }
    fun doSomething(){
        val user = mUserLiveData.value
        if (user != null){
            user.age = 15
            user.name = "name15"
            mUserLiveData.value = user
        }
    }
}

在Activity中使用ViewModel,获取User信息

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        text = findViewById(R.id.tv_text)
        
        val userModel: UserModel = ViewModelProvider(this)[UserModel::class.java]
        userModel.mUserLiveData.observe(this){
            text.text = it.toString()
        }

        text.setOnClickListener(){
            userModel.doSomething()
        }
    }
}

点击按钮,user中age变为15,之后旋转屏幕,则TextView显示的age还是15

ViewModel绝不能引用视图、Lifecycle,或可能存储对 Activity 上下文的引用的任何类,否则可能会导致内存泄漏,因为ViewModel中有在Activity销毁之后才会自动销毁。

ViewModel对象存在的时间比视图或LifecycleOwner的特定实例存在的时间长。如果ViewModel需要Application上下文,可以扩展AndroidViewModel类并设置用于接收Application的构造函数。

2.2 Fragment间通信

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的现象。ActivityFragment可以通过共享一个ViewModel解决这一通信问题,因为Fragment是依附在Activity上。

实例化ViewModel时,将该Activity传入ViewModelProvider,会给你一个该Activity创建好的ViewModel,这个Fragment可以方便的访问该ViewModel中的数据,在Activity中修改userModel数据后,该Fragment就能拿到更新后的数据

class SharedViewModel:ViewModel() {
    private val selected:MutableLiveData<String> = MutableLiveData()
    fun select(string: String){
        selected.value = string
    }

    fun getSelected():LiveData<String>{
        return selected
    }
}
class MasterFragment : Fragment() {
    private var model: SharedViewModel? = null
    fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //获取依附的Activity的ViewModel
        model = getActivity()?.let { ViewModelProvider(it)[SharedViewModel::class.java] }
        stringSelector.setOnClickListener { item -> model!!.select(item) }
    }
}
class DetailFragment: Fragment(){
    private var model:SharedViewModel? = null
    fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        //获取依附的Activity的ViewModel
        model = activity?.let { ViewModelProvider(it)[SharedViewModel::class.java] }
        model?.getSelected()?.observe(this,{
            //更新UI
        })
    }
}

前述MasterFragmentDetailFragment都可以拿到ActivityViewModel,拿到了该ViewModel就可以拿到里面的数据,相当于间接通过ViewModel通信

通过ViewModel实现Fragment通信的优势:

  1. Activity 不需要执行任何操作,也不需要对此通信有任何了解。
  2. 除了 SharedViewModel 约定之外,Fragment 不需要相互了解
  3. 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。

三、原理解析

关于ViewModel原理解析,基于2.4.0版本

先了解一下ViewModel的几个核心角色

  • ViewModelProvider

    ViewModel的使用工具类,封装了一系列作用方法,用于创建ViewModel

  • ViewModelStore

    存储多个ViewModel的存储类

  • ViewModelStoreOwner

    ViewModel存储器的拥有者,ActivityFragment都是其实现者

  • SavedStateHandle

    ViewModel的功能扩展,使得开发者无需直接使用 onSaveInstanceState(Bundle) 等方法来完成数据的保存和重建,而只需要在 ViewModel 里来完成即可

  • ......

ViewModel的原理机制,可以参看下图

ViewModel构造过程:

ViewModel构造过程

ViewModelStore树:

ViewModelStore树

ViewModelStore销毁逻辑:

Fragment层中ViewModelStore销毁逻辑

ViewModel的原理,简单总结为

  1. 所有实例化的ViewModel都缓存在ViewModelStore的封装对象中,实质是一个HashMap
  2. ViewModelStore与具体的Controller(Activity/Fragment)绑定,与其俱生俱灭,生命周期一样长
  3. 获取ViewModel的实例过程委托给ViewModelProvider工具类,其包含一个创建ViewModel的工厂类Factory和一个对ViewModelStore的引用
  4. 获取ViewModel时,会先从 ViewModelStore 中获取缓存的 ViewModel,若没有缓存过则用 Facotry 实例化一个新的 ViewModel 并缓存
  5. viewModelStore存储在NonConfigurationInstances类的mLastNonConfigurationInstances中,其是存在 ActivityClientRecord中的一个组件信息,ActivityClientRecord是存在ActivityThreadmActivities中,由于ActivityThread不受Activity重建影响,由此类推,ViewModel不受Activity重建影响
  6. Activity在收到ON_DESTROY事件,如果判断到是由于配置项更改导致了Activity被销毁,那么就不会调用 getViewModelStore().clear()ViewModel内数据得以保留

3.1 ViewModel存储与获取

从前述代码的ViewModelProvider(this)[UserModel::class.java]入手

//创建ViewModelProvider,并将其保留在给定ViewModelStoreOwner的存储区
public open class ViewModelProvider(
    private val store: ViewModelStore,
    private val factory: Factory
){
    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner))
}
internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
if (owner is HasDefaultViewModelProviderFactory)
owner.defaultViewModelProviderFactory else instance

@JvmStatic
public val instance: NewInstanceFactory
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
get() {
    if (sInstance == null) {
        sInstance = NewInstanceFactory()
    }
    return sInstance!!
}

AppCompatActivity的父类ComponentActivityFragment类已经实现了ViewModelStoreOwnerHasDefaultViewModelProviderFactory接口,可以直接从中获取到ViewModelStoreFactory的实例。当然我们也可以传入自己自定义实现的Factory实例。

下面看一下ViewModelStoreFactory的内部实现

3.1.1 Factory

FactoryViewModelProvider的一个内部接口,实现类用于构建ViewModel实例

public interface Factory {
    /**
     * Creates a new instance of the given `Class`.
     */
    public fun <T : ViewModel> create(modelClass: Class<T>): T
}

对于Factory,主要关注其两个实现类,分别是NewInstanceFactoryAndroidViewModelFactory

NewInstanceFactory实现类

NewInstanceFactory是通过反射来初始化构造函数无参数类型的ViewModel

@Suppress("SingletonConstructor")
public open class NewInstanceFactory : Factory {
    @Suppress("DocumentExceptions")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return try {
            //反射实现,传入的ViewModelClass必须包含无参构造函数
            modelClass.newInstance()
        } catch (e: InstantiationException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        } catch (e: IllegalAccessException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        }
    }

    public companion object {
        private var sInstance: NewInstanceFactory? = null

        /**
         * Retrieve a singleton instance of NewInstanceFactory.
         */
        @JvmStatic
        public val instance: NewInstanceFactory
        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        get() {
            if (sInstance == null) {
                sInstance = NewInstanceFactory()
            }
            return sInstance!!
        }
    }
}

AndroidViewModelFactory实现类

AndroidViewModelFactory专门用来实例化ViewModel中带Context的对象。

public open class AndroidViewModelFactory(
    private val application: Application
) : NewInstanceFactory() {
    @Suppress("DocumentExceptions")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
            try {
                modelClass.getConstructor(Application::class.java).newInstance(application)
            } catch (e: NoSuchMethodException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: InstantiationException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: InvocationTargetException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            }
        } else super.create(modelClass)
    }

    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

        /**
         * Retrieve a singleton instance of AndroidViewModelFactory.
		*/
        @JvmStatic
        public fun getInstance(application: Application): AndroidViewModelFactory {
            if (sInstance == null) {
                sInstance = AndroidViewModelFactory(application)
            }
            return sInstance!!
        }
    }
}

AndroidViewModelFactory通过构造方法给ViewModel带入Application,则可以在ViewModel中拿到Context,因为ApplicationAPP全局的,那么不存在内存泄露的问题。

HasDefaultViewModelProviderFactory实现

ComponentActivityFragment都实现了HasDefaultViewModelProviderFactory接口,看一下其与Factory的联系。

ComponentActivity为例

public interface HasDefaultViewModelProviderFactory {
    @NonNull
    ViewModelProvider.Factory getDefaultViewModelProviderFactory();
}
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    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 (mDefaultFactory == null) {
        mDefaultFactory = new SavedStateViewModelFactory(
            getApplication(),
            this,
            getIntent() != null ? getIntent().getExtras() : null);
    }
    return mDefaultFactory;
}
public SavedStateViewModelFactory(@Nullable Application application,
                                  @NonNull SavedStateRegistryOwner owner,
                                  @Nullable Bundle defaultArgs) {
    mSavedStateRegistry = owner.getSavedStateRegistry();
    mLifecycle = owner.getLifecycle();
    mDefaultArgs = defaultArgs;
    mApplication = application;
    mFactory = application != null
        ? ViewModelProvider.AndroidViewModelFactory.getInstance(application)
        : ViewModelProvider.NewInstanceFactory.getInstance();
}

HasDefaultViewModelProviderFactoryComponentActivity中最终也是根据Application来判断是通过NewInstanceFactoryAndroidViewModelFactory来创建ViewModel实例。

如果要使用带参数的ViewModel实例,需要我们自定义FactoryFactory的自定义在文末再进行介绍。

3.1.2 ViewModelStore

Factory是用来创建ViewModel,而ViewModelStore则是用来存储ViewModel

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    //ViewModel作为Value,进行存储
    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。如果ViewModelStore拥有者(Activity/Fragment)销毁后不会重建,需调用此方法
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModelStore作用:将ViewModel作为Value存储到HashMap中。

ActivityFragment都通过实现了ViewModelStoreOwner来获取ViewModelStore,这里以Activity为例

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}
@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.");
    }
    ensureViewModelStore();
    return mViewModelStore;
}
void ensureViewModelStore() {
    if (mViewModelStore == null) {
        //如果存储器是空,就先尝试恢复最近一次配置变更时保存下来的数据
        NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        //如果lastNonConfigurationInstance不存在,就new一个
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

getViewModelStore()的主要内容:

  1. 如果ViewModelStore为空,先尝试从NonConfigurationInstance从获取 ViewModelStore实例
  2. 如果NonConfigurationInstance不存在,就new一个ViewModelStore

NonConfigurationInstances

NonConfigurationInstances类是ComponentActivity的一个静态内部类,用来存储viewModelStore,其实例不会随着配置改变而消失或改变。

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

ViewModel对象不会随着资源配置变化导致Activity/Fragment的销毁重建而改变,其奥秘就在这里。

ActivityDestroy之前会调用retainNonConfigurationInstances()方法,该方法中会执行NonConfigurationInstances存储操作。

//Activity.java
NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    .......
   
    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;
}

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}

NonConfigurationInstances mLastNonConfigurationInstances;
//ComponentActivity.java
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
    // Maintain backward compatibility.
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    ......

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;	//存储了ViewModelStore
    return nci;
}

retainNonConfigurationInstances()ActivityviewModelStore对象存入到NonConfigurationInstances对象中

为什么资源配置变化不会影响到NonConfigurationInstances实例呢

看一下getLastNonConfigurationInstance()方法

public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
        ? mLastNonConfigurationInstances.activity : null;
}

mLastNonConfigurationInstances是怎么来的?看一下attach方法

final void attach(Context context, ActivityThread aThread,
                 NonConfigurationInstances lastNonConfigurationInstances,
                 ......
                 ){
    ......
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    ......
}

Activityattach方法会给mLastNonConfigurationInstances赋值,为Activity关联上下文环境,在ActivityThreadperformLaunchActivity方法中调用。

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,
                    r.assistToken, r.shareableActivityToken);
    ......
}

lastNonConfigurationInstances是存在 ActivityClientRecord中的一个组件信息,而 ActivityClientRecord存在于ActivityThreadmActivities

//ActivityThread.java
/**
 * Maps from activity token to local record of running activities in this process.
 */
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

Activity销毁前会调用retainNonConfigurationInstances()方法,而该方法是在performDestroyActivity()被调用

/** Core implementation of activity destroy call. */
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
                            int configChanges, boolean getNonConfigInstance, String reason) {
    r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();	//Activity信息存储
    ......
}

ActivityThread 中的 ActivityClientRecord 是不受 activity 重建影响,则其lastNonConfigurationInstances也不受影响,相应的NonConfigurationInstancesviewModelStore不受影响,则其中存储的ViewModel自然也不受影响

3.1.3 get

回到ViewModel最初的入口ViewModelProvider(this)[UserModel::class.java]

ViewModelProvider(this)获取了Factory实例,接下来要调用get()方法,这个方法需要传入Class对象,ViewModelProvider 需要拿到 Class 才能完成反射操作。

get()主要是通过 modelClass 来自动生成一个字符串 Key,并将参数转发给另外一个 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")
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    //从Map中获取ViewModel缓存
    var viewModel = store[key]
    //判断该viewModel对象是否可以转成该类
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)?.onRequery(viewModel)
        return viewModel as T
    } else {
        @Suppress("ControlFlowWithEmptyBody")
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    //无缓存,重新通过mFactory构建
    viewModel = if (factory is KeyedFactory) {
        factory.create(key, modelClass)
    } else {
        factory.create(modelClass)
    }
    //将ViewModel进行缓存
    store.put(key, viewModel)
    return viewModel
}

通过keyViewModelStore里取ViewModel实例,如果取不到或者类型不符,则通过mFactory.create()方法来反射初始化ViewModel,并在返回初始化结果前将它存到 mViewModelStore 中,从而完成ViewModel的初始化流程

3.1.4 小结

ViewModel存储和获取部分内容简单总结为

  1. 所有实例化的ViewModel都缓存在ViewModelStore的封装对象中,其实质是一个HashMap
  2. ViewModelStore与具体的Controller(Activity/Fragment)绑定,与其生命周期一样长
  3. 获取ViewModel的实例过程委托给ViewModelProvider工具类,其包含一个创建ViewModel的工厂类Factory和一个对ViewModelStore的引用
  4. 获取ViewModel时,会先从 ViewModelStore 中获取缓存的 ViewModel,若没有缓存过则用 Facotry 实例化一个新的 ViewModel 并缓存
  5. viewModelStore存储在NonConfigurationInstances类的mLastNonConfigurationInstances中,其是存在 ActivityClientRecord中的一个组件信息,ActivityClientRecord是存在ActivityThreadmActivities中,由于ActivityThread不受Activity重建影响,由此类推,ViewModel不受Activity重建影响

3.2 ViewModel回收

要知道 ViewModel 是在何时回收的,那么就只要看 ViewModelStore 是在什么时候清空 HashMap 就可以。

ViewModel实例对象存储在ViewModelStore,则只要确认ViewModelStoreHashMap清理时机,即可确认其回收时机。

ComponentActivity 中调用了 ViewModelStoreclear() 方法

public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
    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();
                }
            }
        }
    });
    ......
}

Activity在收到ON_DESTROY事件,如果判断是由于资源配置变更导致的页面销毁,不会调用ViewModelStoreclear()方法;如果是正常退出或被系统杀死,则就会调用该clear()方法,清空所有缓存的ViewModel实例

四、自定义ViewModel

ViewModelProvider提供了两个Factory接口实现类

  • NewInstanceFactory:通过反射初始化包含无参构造函数的 ViewModel
  • AndroidViewModelFactory:通过反射初始化包含参数仅有一个且为 Application 类型的构造函数的 ViewModel

如果需要通过其他类型的构造函数来初始化ViewModel,需要自己实现ViewModelProvider.factory接口完成初始化逻辑

class MyViewModel(val age: Int) : ViewModel() {
    val nameLiveData = MutableLiveData<String>()
}
class MainActivity : AppCompatActivity() {
    //创建myViewModelA
    private val myViewModelA by lazy {
        ViewModelProvider(this, object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return MyViewModel(10) as T
            }
        }).get(
            MyViewModel::class.java
        ).apply {
            nameLiveData.observe(this@MainActivity, {
            })
        }
    }

    //创建myViewModelB
    private val myViewModelB by lazy {
        ViewModelProvider(this, object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return MyViewModel(20) as T
            }
        }).get(
            MyViewModel::class.java
        ).apply {
            nameLiveData.observe(this@MainActivity, {
            })
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.e("myViewModelA", myViewModelA.toString() + " age: " + myViewModelA.age)
        Log.e("myViewModelB", myViewModelB.toString() + " age: " + myViewModelB.age)
    }
}
/**打印结果**/
E/myViewModelA: github.leavesc.demo.MyViewModel@e24ac80 age: 10
E/myViewModelB: github.leavesc.demo.MyViewModel@e24ac80 age: 10

myViewModelAmyViewModelB 虽然入参不同,但其对应同一个内存地址,即最先初始化的ViewModel实例被缓存下来重复使用。这是因为二者默认对应的Key是一样的,所以初始化 myViewModelB时之间复用之前的缓存。

@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
}

如果希望 myViewModelAmyViewModelB 对应不同的实例对象,那么就需要在初始化的时候主动为它们指定不同的 Key,这样它们就可以一起被存到 ViewModelStoreHashMap

private val myViewModelA by lazy {
    ViewModelProvider(this, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return MyViewModel(10) as T
        }
    }).get(
        "keyA", MyViewModel::class.java
    ).apply {
        nameLiveData.observe(this@MainActivity, {
        })
    }
}

private val myViewModelB by lazy {
    ViewModelProvider(this, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return MyViewModel(20) as T
        }
    }).get(
        "keyB", MyViewModel::class.java
    ).apply {
        nameLiveData.observe(this@MainActivity, {

        })
    }
}
E/myViewModelA: github.leavesc.demo.MyViewModel@e24ac80 age: 10
E/myViewModelB: github.leavesc.demo.MyViewModel@9abd6fe age: 20

五、常见问题

5.1 初始化陷阱

这里说的初始化陷阱,在自定义ViewModel部分提过,即ViewModel的缓存复用机制可能导致的使用问题。

class AViewModel() : ViewModel() {

    override fun onCleared() {
        super.onCleared()
        Log.e("AViewModel", "onCleared")
    }
}
class BViewMode : ViewModel() {

    override fun onCleared() {
        super.onCleared()
        Log.e("BViewMode", "onCleared")
    }
}
class MainActivity : AppCompatActivity() {

    private val aViewModel by lazy {
        ViewModelProvider(this).get(
            "myKey", AViewModel::class.java
        )
    }

    private val bViewModel by lazy {
        ViewModelProvider(this).get(
            "myKey", BViewMode::class.java
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.e("aViewModel", aViewModel.toString())
        Log.e("bViewModel", bViewModel.toString())
        Log.e("MainActivity", "onCreate")
    }
}
/***打印结果***/
E/aViewModel: github.leavesc.demo.AViewModel@3c93503
E/AViewModel: onCleared
E/bViewModel: github.leavesc.demo.BViewMode@e24ac80
E/MainActivity: onCreate

上述代码打印结果的原因是 aViewModelbViewModel 使用了同个key,但是又不是同一个ViewModel类型,导致初始化bViewModel 时,会将HashMap中的 aViewModel覆盖并回收旧值。

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();
        }
    }
}

ViewModel使用注意点

对于不同类型的 ViewModel 实例,在初始化的时候不能指定相同的 Key