温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Android Jetpack组件之ViewModel怎么使用

发布时间:2023-05-06 15:47:35 来源:亿速云 阅读:132 作者:iii 栏目:开发技术

今天小编给大家分享一下Android Jetpack组件之ViewModel怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

依赖版本

// 注意这里的 appcompat、activity-ktx、fragment-ktx
// 高版本的自动引入了 viewmodel-savedstate 实战中很少用到的功能
// 篇幅原因 就不再本文中分析 viewmodel-savedstate 扩展组件了
implementation 'androidx.appcompat:appcompat:1.0.0'

def fragment_version = "1.1.0"
def activity_version = "1.0.0"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"

def lifecycle_version = "2.5.1"

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

基础使用

定义

class MainViewModel: ViewModel(){ ... }
// or 
class MainViewModel(application: Application): AndroidViewModel(application){
    val data: String = ""
    
    fun requestData(){
        data = "xxx"
    }
}

在 MainVieModel 中可以定义 UI 界面中需要的数据(对象、LiveData、Flow 等等)和方法,在 Activity 真正销毁前 ViewModel 中的数据不会丢失。

Activity 中获取

val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// or
// 引入 activity-ktx 库可以这样初始化 ViewModel
val vm by viewModels<MainViewModel>()

// 通过 vm 可以调用其中的方法、获取其中的数据
vm.requestData()
Log.e(TAG, vm.data)

Fragment 中获取

val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// or 
// 获取和 Activity 共享的 ViewModel 也就是同一个 ViewModel 对象
val vm = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)

引入 fragment-ktx 可以这样初始化

val vm = viewModels<MainViewModel>()
// or 效果同上
val vm = activityViewModels<MainViewModel>()

前置知识

ViewModel 的使用非常简单,也很容易理解,就是一个生命周期长于 Activity 的对象,区别在于不会造成内存泄漏。ViewModel 不是魔法,站在开发者的角度在 ViewModel 没有问世之前横竖屏切换需要保存状态数据的需求通常都是通过 onSaveInstanceState、onRestoreInstanceState 来实现。

onSaveInstanceState、onRestoreInstanceState

关于这两个方法这里就简单概述一下:onSaveInstanceState 用于在 Activity 横竖屏切换(意外销毁)前保存数据,而 onRestoreInstanceState 是用于 Activity 横竖屏切换(重建)后获取保存的数据;

onSaveInstanceState 调用流程

由于是在 Activity 销毁前触发,那么直接来 ActivityThread 中找到 performPauseActivity 方法:

ActivityThread.java

private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason, PendingTransactionActions pendingActions) {
    // ...
    if (shouldSaveState) {
        callActivityOnSaveInstanceState(r);
    }
    // ...
}

private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
    // ...
    // 这里通过 ActivityClientRecord 获取到 activity
    // state 是 Bundle 对象,后面要保存的数据就放在 state 中
    mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
    // ...
}

这里有 ActivityThread 调用到了 Instrumentation 中,继续看源码:

Instrumentation.java

public void callActivityOnSaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
    activity.performSaveInstanceState(outState);
}

根据传入的 activity 调用其 performSaveInstanceState 方法:

Activity.java

final void performSaveInstanceState(@NonNull Bundle outState) {
    onSaveInstanceState(outState);
}

总结一下,onSaveInstanceState 中我们将数据存储在 Bundle 对象中,而这个 Bundle 对象是存储在 ActivityClientRecord 中。

onRestoreInstanceState 调用流程

看完了 onSaveInstanceState 的调用流程,那么 onRestoreInstanceState 的流程就来简单说说,由于在 onStart 后发生回调,所以直接去看 ActivityThread 中的源码:

ActivityThread.java

public void handleStartActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, ActivityOptions activityOptions) {
    // ...
    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
    // ...
}

可以看出这里从 ActivityClientRecord 中取出了 activity 和 state 进行传毒,后面就和 onSaveInstanceState 调用流程一样了,源码比较简单就不贴了。

onRetainCustomNonConfigurationInstance、getLastCustomNonConfigurationInstance

除了 onSaveInstanceState 和 onRestoreInstanceState,在 Activity 中还有一组方法可以实现类似的功能,就是 onRetainCustomNonConfigurationInstance 和 getLastCustomNonConfigurationInstance,前者即保存数据,后者即获取保存的数据;

简单使用

override fun onRetainCustomNonConfigurationInstance(): Any? {
    val data = SaveStateData()
    return data
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // 获取保存的数据
    val data = getLastCustomNonConfigurationInstance() as SaveStateData
}

和 onSaveInstanceState 使用的区别在于 onSaveInstanceState 只能在其参数中的 Bundle 对象中写入数据,而 onRetainCustomNonConfigurationInstance 返回的类型是 Any(Java Object)不限制数据类型。老样子看一下这组方法的源码调用流程。

onRetainCustomNonConfigurationInstance

onRetainCustomNonConfigurationInstance 是在 ComponentActivity 中定义的,默认实现返回 null,其在 onRetainNonConfigurationInstance 方法中被调用:

ComponentActivity.java

public Object onRetainCustomNonConfigurationInstance() {
    // ComponentActivity 中默认返回 null
    return null;
}

public final Object onRetainNonConfigurationInstance() {
    // 保存在了 custom 变量中
    Object custom = onRetainCustomNonConfigurationInstance();
    // 这里已经出现 ViewModel 相关的源码了,这里先按下不表
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }
    // 新建 NonConfigurationInstances 对象
    NonConfigurationInstances nci = new NonConfigurationInstances();
    // custom 赋值给了 NonConfigurationInstances 对象
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

从 ComponentActivity 的这部分源码中可以看出保存的数据最终放在了 NonConfigurationInstances 对象的 custom 属性中;接着找 onRetainNonConfigurationInstance 的定义,在 Activity 中:

Activity.java

public Object onRetainNonConfigurationInstance() {
    // 默认返回 null
    return null;
}

NonConfigurationInstances retainNonConfigurationInstances() {
    // ComponentActivity 中返回的 NonConfigurationInstances 对象
    Object activity = onRetainNonConfigurationInstance();
    // ...
    // 注意 这里有新建另一个 NonConfigurationInstances 对象
    NonConfigurationInstances nci = new NonConfigurationInstances();
    // ComponentActivity 中返回的 NonConfigurationInstances 对象
    // 存储到了新的 NonConfigurationInstances 中的 activity 属性中
    nci.activity = activity;
    // ...
    return nci;
}

在 Activity 类中相当于做了一层套娃,又新建了一个 NonConfigurationInstances 对象,将 ComponentActivity 中返回的 NonConfigurationInstances 对象存了进去;

其实源码看到这里就可以了,不过本着刨根问底的原则,我们接着再看一下 NonConfigurationInstances 到底存在了哪里?在 ActivityThread.java 中找到了调用 retainNonConfigurationInstances 的地方:

ActivityThread.java

void performDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    // ...
    // 这个 r 是参数中的 ActivityClientRecord
    r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
}

和 onSaveInstanceState 一样存储在了 ActivityClientRecord 中,只不过换了一个属性罢了。

getLastCustomNonConfigurationInstance

看完了存储的流程,简单来看看取数据的流程。既然存的时候套娃了一下 NonConfigurationInstances,那取数据的时候肯定也需要套娃:

ComponentActivity.java

public Object getLastCustomNonConfigurationInstance() {
    // 通过 getLastNonConfigurationInstance 获取 NonConfigurationInstances
    NonConfigurationInstances nc = (NonConfigurationInstances)
            getLastNonConfigurationInstance();
    // 返回 custom
    return nc != null ? nc.custom : null;
}

那么在 Activity 中肯定还需要取一次 ActivityClientRecord 中的 NonConfigurationInstances:

Activity.java

NonConfigurationInstances mLastNonConfigurationInstances;

public Object getLastNonConfigurationInstance() {
    // 返回其 activity 字段
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

// mLastNonConfigurationInstances 赋值在 attach 方法中
final void attach(Context context, /*参数太多 省略了*/ NonConfigurationInstances lastNonConfigurationInstances) {
    // ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    // ...
}

可以看出在 Activity attach 方法中就已经拿到了套娃后的 NonConfigurationInstances 对象,我们都知道 Activity attach 方法是在 ActivityThread 的 performLaunchActivity 中调用,看一下源码:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // ...
    // 参数太多 省略了
    // 可以看到是从 ActivityClientRecord 中取出传入的
    activity.attach(appContext, r.lastNonConfigurationInstancesn);
    // ...
}

小节总结

两种方式都是将数据保存到了 ActivityClientRecord 中,不同的是前者限制了 Bundle 类型,后者不限制类型(ViewModel 采用的就是后者这组方法实现),不过后者已经在源码中被标记了删除,并不影响使用,标记删除是为了让开发者们利用 ViewModel 来接管这种需求。下面我们就正式进入 ViewModel 源码。

源码分析

前置知识有点长,不过也几乎把 ViewModel 的原理说透了,ViewModel 的保存、恢复是利用了系统提供的方法,不过还有些细节还需要在源码中探索,比如:如何实现 Activity/Fragment 共享 ViewModel?接下来就来深入 ViewModel 源码。

创建

先来以 Activity 中创建 ViewModel 的这段代码入手:

val vm by viewModels<MainViewModel>()

查看 viewModels 源码:

// 这是一个 ComponentActivity 的扩展方法
@MainThread // 在主线程中使用
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    // 从命名也可以看出是一个工厂模式,默认是 null
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    // 默认 factoryProducer 为 null
    // 返回的是 AndroidViewModelFactory
    val factoryPromise = factoryProducer ?: {
        val application = application ?: throw IllegalArgumentException(
            "ViewModel can be accessed only when Activity is attached"
        )
        AndroidViewModelFactory.getInstance(application)
    }
    
    // 返回了一个 ViewModelLazy 对象,将 viewModelStore、factoryProducer 传入
    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

到这里先暂停看一下 AndroidViewModelFactory 是如何初始化的,以及 viewModelStore 是什么东东:

ViewModelProvider.kt

private var sInstance: AndroidViewModelFactory? = null

@JvmStatic
public fun getInstance(application: Application): AndroidViewModelFactory {
    if (sInstance == null) {
        sInstance = AndroidViewModelFactory(application)
    }
    return sInstance!!
}

是一个单例模式,直接对 AndroidViewModelFactory 进行实例化,再来看看 mViewModelStore

ComponentActivity.java

// 都是定义在 ComponentActivity 中的变量,默认 null
private ViewModelStore mViewModelStore;

public ViewModelStore getViewModelStore() {
    // ...
    if (mViewModelStore == null) { // 第一次启动 activity 为 null
        // 获取保存的数据
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
         // 优先从保存的数据中获取
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        // 默认返回 ViewModelStore
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

ViewModelStore 内部仅仅是管理一个 Map<String, ViewModel>,用于缓存、清理创建的 ViewModel。

回过头接着看扩展方法 viewModels 返回的 ViewModelLazy:

public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
    private val viewModelClass: KClass<VM>, // ViewModel 的 class
    private val storeProducer: () -> ViewModelStore, // 默认是 ViewModelStore
    private val factoryProducer: () -> ViewModelProvider.Factory, // 这里就是 mDefaultFactory
    private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty } // 
) : Lazy<VM> { // 注意这里返回的 Lazy,延迟初始化
    private var cached: VM? = null

    override val value: VM
        get() { // 由于返回的是 Lazy,也就是当使用 ViewModel 时才会调用 get
            val viewModel = cached
            return if (viewModel == null) { // 第一次调用是 null,进入 if
                val factory = factoryProducer() // mDefaultFactory
                val store = storeProducer() // ViewModelStore
                ViewModelProvider( // 生成 ViewModelProvider 对象
                    store,
                    factory,
                    extrasProducer()
                ).get(viewModelClass.java).also { // 调用其 get 方法获取 ViewModel
                    cached = it  // 保存到 cached 变量
                } 
            } else {
                viewModel
            }
        }
    
    override fun isInitialized(): Boolean = cached != null
}

这里又出现了一个陌生的对象 CreationExtras,其内部也是一个 map,可以理解为一个键值对存储对象,只不过他的 Key 是一个特殊类型。

接着查看 ViewModelProvider 的 get 方法是如何创建 ViewModel 的:

// 存储ViewModel的key的前缀
internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

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 传入
    // 当前key即为:androidx.lifecycle.ViewModelProvider.DefaultKey$com.xxx.MainViewModel
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    val viewModel = store[key] // 优先从 ViewModelStroe 中获取缓存
    if (modelClass.isInstance(viewModel)) { // 如果类型相同 直接返回
        // 这里我们的 factory 是 AndroidViewModelFactory 所以不会走这行代码
        (factory as? OnRequeryFactory)?.onRequery(viewModel)
        return viewModel as T
    }
    // ...
    // 这里的 defaultCreationExtras 是上一步骤中的 CreationExtras,默认值为 CreationExtras.Empty
    // MutableCreationExtras 包装一层就是将 defaultCreationExtras 中所有的键值对都copy一份
    val extras = MutableCreationExtras(defaultCreationExtras)
    // 将当前 ViewModel 的 key 存储进去
    extras[VIEW_MODEL_KEY] = key
    
    return try {
        // 优先调用双参数方法
        factory.create(modelClass, extras)
    } catch (e: AbstractMethodError) {
        // 调用双参数方法发生异常再调用单参数方法
        factory.create(modelClass)
    }.also { 
        // 获取到 ViewModel 后存储到 viewModelStore 中
        // 再提一嘴 viewModelStore 是在 ComponentActivity 中定义 
        store.put(key, it) 
    }
}

终于到了创建 ViewModel 的部分了,直接去看 AndroidViewModelFactory 的 create 方法:

ViewModelProvider.kt

override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
    // application 不为 null 调用单参数方法
    // 在新建 AndroidViewModelFactory 已经传入了 application,一般情况不为 null
    return if (application != null) { 
        create(modelClass)
    } else { 
        // application 如果为 null,则会从传入的 extras 中尝试获取
        val application = extras[APPLICATION_KEY]
        if (application != null) {
            // 这个 create 也是双参数,但不是递归,第二个参数是 application,源码贴在下面
            create(modelClass, application)
        } else {
            // 如果 application 仍然为 null,且 ViewModel 类型为 AndroidViewModel 则抛异常
            if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                throw IllegalArgumentException(...)
            }
            // 类型不是 AndroidViewModel 则根据 class 创建
            // 注意这里调用的 super.create 是父类方法
            // 父类方法直接根据 modelClass.newInstance() 创建,就一行就不贴源码了
            super.create(modelClass)
        }
    }
}

override fun <T : ViewModel> create(modelClass: Class<T>): T {
    return if (application == null) { // application 为 null 直接抛异常
        throw UnsupportedOperationException(...)
    } else {
        // 调用下面的双参数方法
        create(modelClass, application)
    }
}

private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
    // 如果是 AndroidViewModel 类型则获取带 application 的构造参数创建
    return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
        modelClass.getConstructor(Application::class.java).newInstance(app)
    } else {
        // 直接调用父类 create 方法通过 modelClass.newInstance() 创建
        super.create(modelClass)
    }
}

至此 Activity 中的 ViewModel 创建过程源码就全部分析完了,总结一下:Activity 中的 ViewModel 创建都是通过单例工厂 AndroidViewModelFactory 的 create 方法中反射创建,在调用 create 创建前会生成字符串 key,创建完成后会将 key 和 vm 对象存储到 ViewModelStore 中,后续获取将优先从 ViewModelStore 缓存中获取。

ViewModelStore 是定义在 ComponentActivity 中的,ViewModel 生命周期 “长于” Activity 的原理跟这个 ViewModelStore 脱不了干系。

恢复

前面小节提过,ViewModel 的恢复利用的是 onRetainNonConfigurationInstance 方法,ViewModelStore 又是定义在 ComponentActivity 中,那么直接去看 ComponentActivity 这部分的源码:

ComponentActivity.java

public final Object onRetainNonConfigurationInstance() {
    // 留给开发者使用的字段
    Object custom = onRetainCustomNonConfigurationInstance();
    
    // 获取当前 Activity 的 mViewModelStore
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) { 
        // 如果为 null 则尝试获取上一次保存的数据
        NonConfigurationInstances nc =  (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // 获取上一次存储的 viewModelStore
            viewModelStore = nc.viewModelStore;
        }
    }
    // ...
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom; // 开发者用的字段
    nci.viewModelStore = viewModelStore; // 保存 viewModelStore 的字段
    return nci;
}

再来看一看 ViewModelStore 的获取方法:

public ViewModelStore getViewModelStore() {
    // ...
    if (mViewModelStore == null) {
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
         // 优先从保存的数据中获取 viewModelStore
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        // 获取不到才会新建
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

Activity 获取 mViewModelStore 时优先从 getLastNonConfigurationInstance 获取到 NonConfigurationInstances 对象,再从其中获取 viewModelStore,这样在当前 Activity 作用域中创建过的 ViewModel 都存储在 ViewModelStore 中,当需要再次使用时走 ViewModel 创建流程会直接从 ViewModelStore 中返回。

以上就是“Android Jetpack组件之ViewModel怎么使用”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI