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

相关推荐
x02414 天前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节
alexhilton17 天前
深入理解观察者模式
android·kotlin·android jetpack
Wgllss17 天前
花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
android·性能优化·android jetpack
上官阳阳20 天前
使用Compose创造有趣的动画:使用Compose共享元素
android·android jetpack
沐言人生24 天前
Android10 Framework—Init进程-15.属性变化控制Service
android·android studio·android jetpack
IAM四十二1 个月前
Android Jetpack Core
android·android studio·android jetpack
王能1 个月前
Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)
android·ios·kotlin·web·android jetpack·kmp·kmm
alexhilton1 个月前
让Activity更加优雅地跳转
android·kotlin·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-11.客户端操作属性
android·android studio·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack