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

相关推荐
未来猫咪花18 小时前
LiveData "数据倒灌":一个流行的错误概念
android·android jetpack
alexhilton2 天前
借助RemoteCompose开发动态化页面
android·kotlin·android jetpack
QING6183 天前
Jetpack Compose Brush API 简单使用实战 —— 新手指南
android·kotlin·android jetpack
QING6183 天前
Jetpack Compose Brush API 详解 —— 新手指南
android·kotlin·android jetpack
QING6184 天前
Jetpack Compose 中 Flow 收集详解 —— 新手指南
android·kotlin·android jetpack
ljt27249606615 天前
Compose笔记(五十七)--snapshotFlow
android·笔记·android jetpack
QING6185 天前
kotlin 协程: GlobalScope 和 Application Scope 选择和使用 —— 新手指南
android·kotlin·android jetpack
QING6185 天前
Kotlin 协程中Job和SupervisorJob —— 新手指南
android·kotlin·android jetpack
天花板之恋5 天前
Compose中的协程:rememberCoroutineScope 和 LaunchedEffect
android jetpack
我命由我123455 天前
Android 开发问题:布局文件中的文本,在预览时有显示出来,但是,在应用中没有显示出来
android·java·java-ee·android studio·android jetpack·android-studio·android runtime