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中的方法获取或写入数据实现共享。这种方式就类似于对文件的读写,不同的是这个数据仍然是无法长久保存的。

相关推荐
李斯维2 天前
从历史的角度看 Android 软件架构
android·架构·android jetpack
alexhilton3 天前
Android车载OS中的Remote Compose
android·kotlin·android jetpack
alexhilton9 天前
使用Android Archive进行打包
android·kotlin·android jetpack
Junerver12 天前
我写了一个 Compose Multiplatform 组件库,你可能会用到
kotlin·android jetpack
我命由我1234513 天前
Jetpack Room - Room 查询返回列表无需判空、LIKE 关键字
android·java·开发语言·java-ee·android jetpack·android-studio·android runtime
QING61814 天前
Kotlin 日常开发常用语法糖整理 —— 速记
android·kotlin·android jetpack
我命由我1234514 天前
Android 开发问题:EditText 控件的 android:imeOptions=“actionDone“ 属性不生效
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我命由我1234514 天前
Android 开发问题:获取到的 Android ID 发生了变化
android·java·开发语言·java-ee·android studio·android jetpack·android runtime
我命由我1234514 天前
Android 开发问题:Unable to find explicit activity class
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我命由我1234514 天前
Android 开发问题:全局的主题颜色设置,导致 CheckBox 控件在勾选状态下不显示样式
android·java·开发语言·java-ee·intellij-idea·intellij idea·android jetpack