Jetpack系列(二) -- ViewModel

ViewModel

前言

选读书籍--《Android Jetpack开发 原理解析与应用实战》------ 著: 黄林晴

本篇主要根据选读书籍来学习ViewModel组件,而并非学习MVVM框架

时间: 23/09/12

AndroidStudio版本: Giraffe 2022.3.1 JDK:17 开发语言: Kotlin

Gradle版本: 8.0 Gradle plugin Version: 8.1.1

ViewModel管理数据

旋转屏幕导致数据重新赋值

在实际开发过程中,总会有需要手机切换横竖屏的需求,但是在不设置configChanges的情况下,横竖屏切换默认都会导致Activity界面重新绘制,包括重新执行onCreate()等周期方法。

那么这个时候,屏幕中保存的数据就会被刷新回初始状态。

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    lateinit var binding: ActivityMainBinding
​
    private var showInt = 5
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.show.text = showInt.toString()
        Log.d(TAG, "show number, now is $showInt")
        binding.add.setOnClickListener {
            showInt++
            binding.show.text = showInt.toString()
            Log.d(TAG, "add number, now is $showInt")
        }
    }
​
}
Log显示

一般情况下,我们会使用到onCreate()方法中的Bundle来存储、获取界面中的数据,这种方法这里暂不做详细描述。

还有一种方式就是使用ViewModel来存放数据。

使用ViewModel

  • 引入依赖

    scss 复制代码
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
  • 新建一个MainViewModel类继承ViewModel

    kotlin 复制代码
    class MainViewModel : ViewModel() {
    ​
        var showInt = 5
    }
  • 在MainActivity中初始化MainViewModel,并替换showInt

    kotlin 复制代码
    class MainActivity : AppCompatActivity() {
        private val TAG = "MainActivity"
        lateinit var binding: ActivityMainBinding
        private lateinit var mainViewModel: MainViewModel
    ​
        private var showInt = 5
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    ​
            mainViewModel = ViewModelProvider(this)[MainViewModel::class.java]
            binding.show.text = mainViewModel.showInt.toString()
            Log.d(TAG, "show number, now is ${mainViewModel.showInt}")
            binding.add.setOnClickListener {
                mainViewModel.showInt++
                binding.show.text = mainViewModel.showInt.toString()
                Log.d(TAG, "add number, now is ${mainViewModel.showInt}")
            }
        }
    ​
    }
Log显示

原理

既然每次横竖屏切换都会执行一遍onCreate,那就说明每次都重新初始化了一遍ViewModel,那为什么ViewModel中的值不会发生变化呢?

查看初始化操作代码的源码------ViewModelProvider中的get()方法

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

很容易理解哈,我们从上往下看

  • 先通过key从store中拿出来一个viewModel,这就可以看出来这个store是存放ViewModel的。
  • 拿到这个viewModel之后,判断它是不是就是modelClass对应的那个ViewModel,如果是直接返回这个viewModel
  • 如果不符合上述情况,就走到下面通过factory,即工厂模式来创建一个新的ViewModel,并在最后添加到store中,然后返回这个ViewModel。

注意:从上述源码可以看出,ViewModel的生命周期会比Activity长很多,因此如果ViewModel直接引用View(例如:Context) 的话会导致内存泄漏

ViewModel其它应用

通过上面的实践和分析,我们知道ViewModel中的信息可以得到暂时的保存,那么我们可以通过这个特性,来优化代码。

例如我们可以在一个Activity,多个fragment之间实现 fragment之间的"共享"数据。实际上就是在Activity中初始化ViewModel,在多个fragment中去调用同一个ViewModel,通过这个ViewModel中的方法获取或写入数据实现共享。这种方式就类似于对文件的读写,不同的是这个数据仍然是无法长久保存的。

相关推荐
l软件定制开发工作室1 天前
Jetpack Architecture系列教程之(一)——Jetpack介绍
android jetpack
alexhilton5 天前
选择Retrofit还是Ktor:给Android开发者的指南
android·kotlin·android jetpack
用户67958126582095 天前
compositionLocalOf和staticCompositionLocalOf,你都用对了吗
android jetpack
方之长8 天前
我写了个App,上架 Google Play 一年,下载不到 10 次,于是决定把它开源了
android·github·android jetpack
我命由我123458 天前
Android Studio - Android Studio 查看项目的 Android SDK 版本(4 种方式)
android·java·ide·java-ee·android studio·android jetpack·android runtime
工程师老罗17 天前
我用Ai学Android Jetpack Compose之CircularProgressIndicator
android·android jetpack
工程师老罗17 天前
我用Ai学Android Jetpack Compose之Icon
android·android jetpack
工程师老罗18 天前
我用Ai学Android Jetpack Compose之Row
android·android jetpack
砖厂小工18 天前
AI 也能"看懂"图片: Android AI 搜图的奥秘
openai·android jetpack
普通网友1 个月前
Android-Jetpack架构组件(一)带你了解Android-Jetpack
jvm·架构·android jetpack