Android Jetpack组件架构:ViewModel的原理

Android Jetpack组件架构:ViewModel的原理

导言

本篇文章是关于介绍ViewModel的,由于ViewModel的使用还是挺简单的,这里就不再介绍其的基本应用,我们主要来分析ViewModel的原理。

ViewModel的生命周期

众所周知,一般使用ViewModel是用来解决两个问题的,第一个就是关于设备配置发生改变时Activity先前状态的保存,在ViewModel出来之前我们一般会使用saveInstanceState这个Bundle来进行状态的保存,但是这样做能存储的数据是有限的,结构也不够明确,ViewModel作为一个生命周期大于Activity的组件就可以帮我们实现状态的存储,下面是ViewModel生命周期与Activity对比的图:

可以看到直到Activity被完全Destory时ViewModel中的数据才会被清除。

我们使用ViewModel的第二个原因就是用来实现MVVM架构,可以通过DataBinding组件和ViewModel组件以及LiveData组件一起实现MVVM架构,这样可以减轻Activity的职责,避免Activity过于臃肿。

获得ViewModel的提供者

我们从ViewModel的创建开始分析其原理,这里用我们上一篇文章的例子:

kotlin 复制代码
mViewModel = ViewModelProvider(this).get(SimpViewModel::class.java)

这里首先会通过ViewModelProvider的构造方法获得一个ViewModelProvider的实例,其构造方法如下所示:

kotlin 复制代码
public constructor(
    owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

constructor(
    private val store: ViewModelStore,
    private val factory: Factory,
    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
)

可以看到我们调用的是第一个构造方法,最终会调用到第二个构造方法中,也就是主构造方法中,这个构造来说就是指定了三个成员变量,分别是ViewModelStore,FactoryCreationExtras三个类型的参数,我们先来分别介绍一下这三个类型。

ViewModelStore -- ViewModel的拥有者

着整个类比较小,但是也是有注释的,我们先来看看注释:

这段注释中比较重要的信息就是ViewModel是真正用来存储ViewModel的类并且在configuration changes就是配置发生改变时新的实例和旧的实例中的信息是一致的。只有当持有者不再会被recreated时里面的数据才会通过clear清除。

接下来我们来看该类的源码:

java 复制代码
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();
    }
}

可以看到具体是用一个哈希表来存储viewModel的实例的,里面唯一比较大的方法就是put方法,里面做的处理就是将替换出来的旧的viewModel实例给清理掉。

Factory -- ViewModel的创建工厂

这个Factory是一个定义在ViewModelProvider的内部接口,它的主要职责是用来初始化ViewModel,说实话就是一个工厂。我们来看其定义:

java 复制代码
    public interface Factory {
        public fun <T : ViewModel> create(modelClass: Class<T>): T {
            throw UnsupportedOperationException(
                "Factory.create(String) is unsupported.  This Factory requires " +
                    "`CreationExtras` to be passed into `create` method."
            )
        }

        public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
            create(modelClass)

        companion object {
            @JvmStatic
            fun from(vararg initializers: ViewModelInitializer<*>): Factory =
                InitializerViewModelFactory(*initializers)
        }
    }

不过这里是一个抽象的,我们来找一个具体的,也就是defaultFactory方法获取的工厂:

kotlin 复制代码
public companion object {
    internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
        if (owner is HasDefaultViewModelProviderFactory)
            owner.defaultViewModelProviderFactory else instance
     	......       
}

这个方法的逻辑就是判断ViewModel的持有者是不是有默认的工厂方法,如果有的话就获取持有者的默认工厂,否则返回的是自身的instance实例,至于这个instance实例是在NewInstanceFactory这个实现了Factory接口的工厂类中定义的伴生变量,具体逻辑是:

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

可以看到instance是一个静态的单例,他具体指向的还是这个NewInstanceFactory类,至于它是如何初始化/创建ViewModel的实例的我们可以看一眼它的create方法:

kotlin 复制代码
override fun <T : ViewModel> create(modelClass: Class<T>): T {
    return try {
        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)
    }
}

这个传入的modelClass就是我们传入的.class文件:

kotlin 复制代码
ViewModelProvider(this).get(SimpViewModel::class.java)

所以可以看到,这个默认情况下的工厂就是直接用反射生成了对应ViewModel的实例。

CreationExtras -- 构建ViewModel时的额外参数

首先我们来看一看注释的内容:

简单来说它就是为工厂生成实例的时候提供额外参数的,这些参数使用一个Map来存储的了,不过默认情况下我们并不需要实现额外的工厂,所以这个类型我们先略过。

获得ViewModel的实例

前面我们已经知道了通过构造ViewModelProvider我们可以获得其Factory了,接下来继续向下看:

kotlin 复制代码
ViewModelProvider(this).get(SimpViewModel::class.java)

我们看一看get方法做了什么:

kotlin 复制代码
    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)
    }

    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
        return try {
            factory.create(modelClass, extras)
        } catch (e: AbstractMethodError) {
            factory.create(modelClass)
        }.also { store.put(key, it) }
    }

可以看到第一个方法会调用第二个方法,其中第一个方法向第二个方法传入的的第一个String类型的参数是通过DEFAULT_KEY和我们传入的类的类名拼接而成的。然后跳转到第二个方法之中去,首先会尝试从ViewModleStroe中获取对应Key对应的ViewModel,但是一般第一次创建时应该会为null,所以之后跳转的应该是最后return块中的factory.create方法之中,这个方法我们在之前Factory的介绍中提到过了,具体就是通过反射实例化ViewModel的,并且最后将其放入到ViewModelStore对象之中去。

至于多次获取同一个ViewModel实例是会跳转到:

java 复制代码
 if (modelClass.isInstance(viewModel)) {
            (factory as? OnRequeryFactory)?.onRequery(viewModel)
            return viewModel as T
        } 

这一段中去,这里onRequery默认是无实现的,也就是说并不会对viewModel做任何的处理。

ViewModelStore在哪里被创建

既然ViewModel是被存储在ViewModelStore之中的,那ViewModelStore究竟是在哪里被创建出来的呢?我们可以在ComponentActivity之中找到答案:

kotlin 复制代码
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(); //创建一个新的ViewModelStore
        }
    }
}

可以看到这里在获得ViewModelStore时主要是通过一个NonConfigurationInstances ,而该参数是一个静态的对象,也就是说,它是一个单例,这样就保证了Activity在整个生命周期之中只有一个ViewModelStore实例,从而实现配置改变时也可以恢复数据的作用。

Activity的默认工厂

在看ComponentActivity的源码时,发现了原来Activity也是有默认工厂的,它的具体实现如下:

kotlin 复制代码
    constructor(application: Application?, owner: SavedStateRegistryOwner, defaultArgs: Bundle?) {
        savedStateRegistry = owner.savedStateRegistry
        lifecycle = owner.lifecycle
        this.defaultArgs = defaultArgs
        this.application = application
        factory = if (application != null) getInstance(application)
            else ViewModelProvider.AndroidViewModelFactory()
    }

这个方法最终设置到的工厂类都是一个名为AndroidViewModelFactory的工厂类:

kotlin 复制代码
    public open class AndroidViewModelFactory
    private constructor(
        private val application: Application?,
        // parameter to avoid clash between constructors with nullable and non-nullable
        // Application
        @Suppress("UNUSED_PARAMETER") unused: Int,
    ) : NewInstanceFactory() {
    
        @Suppress("SingletonConstructor")
        public constructor() : this(null, 0)

        @Suppress("SingletonConstructor")
        public constructor(application: Application) : this(application, 0)

        @Suppress("DocumentExceptions")
        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
            return if (application != null) {
                create(modelClass)
            } else {
                val application = extras[APPLICATION_KEY]
                if (application != null) {
                    create(modelClass, application)
                } else {
                    // For AndroidViewModels, CreationExtras must have an application set
                    if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                        throw IllegalArgumentException(
                            "CreationExtras must have an application by `APPLICATION_KEY`"
                        )
                    }
                    super.create(modelClass)
                }
            }
        }

        @Suppress("DocumentExceptions")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return if (application == null) {
                throw UnsupportedOperationException(
                    "AndroidViewModelFactory constructed " +
                        "with empty constructor works only with " +
                        "create(modelClass: Class<T>, extras: CreationExtras)."
                )
            } else {
                create(modelClass, application)
            }
        }

        @Suppress("DocumentExceptions")
        private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
            return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {//如果传入的Class类型实现的接口和AndroidViewModel::class一致,及说明也有生命周期,调用构造犯法并且传入application对象
                try {
                    modelClass.getConstructor(Application::class.java).newInstance(app)
                } 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
            
            @JvmStatic
            public fun getInstance(application: Application): AndroidViewModelFactory {
                if (sInstance == null) {
                    sInstance = AndroidViewModelFactory(application)
                }
                return sInstance!!
            }
            
            private object ApplicationKeyImpl : Key<Application>
            
            @JvmField
            val APPLICATION_KEY: Key<Application> = ApplicationKeyImpl
        }
    }

具体通过getInstance方法就跳转到了这里,可以发现似乎工厂类都是一个单例的模式,这个工厂的特殊之处就是他的create方法涉及到了Application对象的传入,比如说这里的newInstance方法:

kotlin 复制代码
modelClass.getConstructor(Application::class.java).newInstance(app)

public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (serializationClass == null) {
        return newInstance0(initargs);
    } else {
        return (T) newInstanceFromSerialization(serializationCtor, serializationClass);
    }
}

也就是说整个被传入Application的生命周期内都只有一个实例,这样由于创建的实例在生命周期范围内的单例性和ViewModelStore的单例性,整个ViewModel就可以实现在整个Activity的生命周期内(发生意外,比如说配置改变时)数据不变更的作用。

相关推荐
weixin_482565531 小时前
Android IC读写器安卓小程序 3
android·小程序
峰子20121 小时前
Go语言实现守护进程的挑战
开发语言·后端·面试·架构·golang·go
hvinsion2 小时前
Python PDF批量加密工具
android·python·pdf
m0_748230442 小时前
【MySQL】数据库开发技术:内外连接与表的索引穿透深度解析
android·mysql·数据库开发
绝无仅有2 小时前
gozero项目日志Prometheus的配置与实战
后端·面试·架构
marui19822 小时前
hadoop sql 执行log
android·ide·android studio
绝无仅有3 小时前
gozero项目日志收集与配置实战
后端·面试·架构
uhakadotcom3 小时前
2025年,最新的AI发展趋势是什么?
后端·面试·架构
liangmou21214 小时前
解释小部分分WPI函数(由贪吃蛇游戏拓展)
android·游戏·c#
你听得到114 小时前
《Flutter性能优化全攻略:从首屏渲染到性能监测,附案例代码详解》
android·flutter