joy keeps flowin'

Jetpack系列之ViewModel

xx
目次

ViewModel和架构MVVM中的VM(ViewModel)同名,但并不是一个东西,架构中的ViewModel是和View双向绑定的,这里的ViewModel只能做到利用LiveData更新。

为什么用ViewModel呢?一是把网络请求和数据库读取的逻辑从Activity中放到了ViweModel,分担了一部分Activity的压力;另一个原因是可以在Activity或Fragment重建时保存数据,不会丢失。今天探讨的时第二个问题。

用法 #

一般的创建写法是val viewModel :MyViewModel by viewModels()这样,或者直接的写法应该是val viewModel: MyViewModel = ViewModelProvider(viewModelStore,defaultViewModelProviderFactory).get(MyViewModel::class.java)这样才对。

viewModels()需要添加了activity-ktx的依赖,顺着代码往下翻实际上也是用了第二种,不同的是被Lazy包了一下。

ViewModelProvider的构造方法需要三个参数分别是ViewModelStore,Factory和CreationExtras。

ViewModelStore中有mutableMapOf<String, ViewModel>()类型的属性,实际作用也是以kv的形式存放ViewModel。

Factory更直接,一个工厂模式,决定怎么创建ViewModel类。

CreationExtras中又是一个map:

1
internal val map: MutableMap<Key<*>, Any?> = mutableMapOf()

保存额外的参数。

原理 #

保证ViewModel不被重建的奥秘藏在get方法中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    /**
     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
     * an activity), associated with this `ViewModelProvider`.
     *
     *
     * The created ViewModel is associated with the given scope and will be retained
     * as long as the scope is alive (e.g. if it is an activity, until it is
     * finished or process is killed).
     *
     * @param modelClass The class of the ViewModel to create an instance of it if it is not
     * present.
     * @return A ViewModel that is an instance of the given type `T`.
     * @throws IllegalArgumentException if the given [modelClass] is local or anonymous class.
     */
    @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)
    }

    /**
     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
     * an activity), associated with this `ViewModelProvider`.
     *
     * The created ViewModel is associated with the given scope and will be retained
     * as long as the scope is alive (e.g. if it is an activity, until it is
     * finished or process is killed).
     *
     * @param key        The key to use to identify the ViewModel.
     * @param modelClass The class of the ViewModel to create an instance of it if it is not
     * present.
     * @return A ViewModel that is an instance of the given type `T`.
     */
    @Suppress("UNCHECKED_CAST")
    @MainThread
    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
        val 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.
            }
        }
        val extras = MutableCreationExtras(defaultCreationExtras)
        extras[VIEW_MODEL_KEY] = key
        // AGP has some desugaring issues associated with compileOnly dependencies so we need to
        // fall back to the other create method to keep from crashing.
        return try {
            factory.create(modelClass, extras)
        } catch (e: AbstractMethodError) {
            factory.create(modelClass)
        }.also { store.put(key, it) }
    }

get时先尝试从store中获取,store也就是刚刚说的ViewModelStore。拿不到时才会用Factory创建一个新的。

在ComponentActivity中取到的viewModelStore又是从一个NonConfigurationInstances类实例中拿到的,实例并不是Activity内部创建的,是在Activity#attach时传入并保存下来的(你不信我也没办法,赋值的代码只有attach有)。也就是说外部一定有对其赋值的代码。

恰好知道Activity#attach方法在ActivityThread中用到。顺藤摸瓜,又找到是在Activity销毁前保存下来的。接着往上摸,找到handleRelaunchActivity的头上,确定在需要重新launch的Activity中才会传入NonConfigurationInstances,performLaunchActivity时传入null。

知道了从哪进来的,从哪出去的就好说了。Activity重建时,会用retainNonConfigurationInstances拿到,其中一部分保存的是Activity的状态,也就是activity字段中。

保存的activity名字叫activity,类型是Object的。实际的是NonConfigurationInstances,里面包含了viewModelStore。

总结 #

获取ViewModel时如果是Activity重建的场景,会在销毁前将ViewModelStore保存下来,并将ViewModelStore传给Activity,ViewModelStore保存了ViewModel实例。这样Activity重建后仍然有之前的ViewModel。

标签:
Categories: