JetPack使用记录之viewModels函数

2,935 阅读3分钟

前面文章说过,viewModels是fragment的扩展程序里的,作用就是获取ViewModel实例,主要有2个作用,一个是懒加载获取单例,一个是在Activity或者Fragment生命周期内获取实例,那么如何实现的呢,直接看源码。

使用

比如我在登录页,在Activity中需要获取登录的ViewModel:

  • 直接使用viewModels函数,参数传递一个工厂。
private val loginViewModel : LoginViewModel by viewModels {
    InjectorUtils.provideLoginViewModelFactory(this)
}

  • 提供一个ViewModel工厂。
/**
 * 提供登录页逻辑的ViewModelFactory
 * */
fun provideLoginViewModelFactory(context: Context): LoginViewModelFactory{
    return LoginViewModelFactory(getLoginRepository())
}
  • 根据该ViewModel所需要的Repository来创建一个ViewModel。
class LoginViewModelFactory(private val loginRepository: LoginRepository)
    : ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return LoginViewModel(loginRepository) as T
    }

}

源码分析

1、先看viewModels源码以及解析:

@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
  • 首先类的注释:
Returns a [Lazy] delegate to access the ComponentActivity's ViewModel, if [factoryProducer]
 is specified then [ViewModelProvider.Factory] returned by it will be used
 to create [ViewModel] first time.

返回一个Lazy的实例来访问这个Activity的ViewModel,如果factoryProducer即工厂生产者被指明,那么然后ViewModelProvider.Factory会被返回,同时会先使用这个实例来创建ViewModel实例。

  • 再看调用时机:
This property can be accessed only after the Activity is attached to the Application,
 and access prior to that will result in IllegalArgumentException.

当Activity是attached时,这个属性是可以访问的,提前访问会报异常。

2、配合前面所说的注释,我们这里需要一个ViewModelProvider.Factory实例,那么这里也直接使用NewInstanceFactory来完成,直接看代码:

class LoginViewModelFactory(private val loginRepository: LoginRepository)
    : ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return LoginViewModel(loginRepository) as T
    }

}

这里直接返回了LoginViewModel实例。

3、懒加载和单例的实现,在1中我们可以发现调用了一个3参数的方法,我们来看一下代码:

class ViewModelLazy<VM : ViewModel> (
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
    private var cached: VM? = null

    override val value: VM
        get() {
            val viewModel = cached
            return if (viewModel == null) {
                val factory = factoryProducer()
                val store = storeProducer()
                ViewModelProvider(store, factory).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

    override fun isInitialized() = cached != null
}
  • 这里注释有句非常重要的话:
Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
 an activity), associated with this `ViewModelProvider`.

返回一个现有的ViewModel实例或者创建一个实例,在一定范围内(fragment或者activity),这个就表明这个实例我不是随便创建的,是和生命周期有关的。

  • 这里实现延迟初始化的方式还是比较值的学习的,以后有需要可以借鉴。
  • 当viewModel实例为空时,最终调用ViewModelProvider方法来获取。

4、ViewModelProvider代码解析:

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

这里会创建一个ViewModelProvider实例,有2个参数,第一个factory就是用来创建ViewModel实例的工厂实例,第二个store是保存ViewModel实例的集合,这里就要注意了,来看一下这个store保存的范围,毕竟它不能保存一个ViewModel太久,比如我Activity都被销毁了,这个store肯定不能再保存这个Activity相关的ViewModel了,

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

不出意外,这个store是和activity相关联的,它就是一个HashMap:

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

其中有个clear方法,按照期望肯定是在activity销毁的时候调用,来看一下Activity的代码:

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

也确实如此。

5、从ViewModelProvider中get出需要的ViewModel,按照之前的逻辑,一个ViewModelProvider对应着一个factory和一个Activity共有的store,所以肯定也是按照这种顺序来获取:

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;
}
  • 这里也是会先从store中取出,如果没有的话再通过factory来创建,然后插入到HashMap中。
  • 这里还会有个新的需求,比如我在Activity A的A处获取到一个ViewModel实例,但是在B处也获取时,需要进行修改一下,这时可以使用OnRequeryFactory来完成。

总结

获取ViewModel实例通过这个方法不仅可以实现单例懒加载,内部还实现了HashMap来达到在一定scope保存实例的作用,可以防止内存占用。