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

相关推荐
alexhilton11 小时前
模块化Jetpack Compose架构
android·kotlin·android jetpack
hai_android13 小时前
InputStage 责任链核心执行流程
android·android studio·android jetpack
传奇开心果编程2 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程2 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程3 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
天空之城--5 天前
Android一周动态:Android 18首次官宣、Compose Material3 1.4转正(5趋势+5资讯)
android·人工智能·flutter·架构·android jetpack
传奇开心果编程6 天前
【现代声明式UI学与练】第1课 从命令式UI到声明式UI
学习·flutter·ui·swiftui·react·android jetpack
传奇开心果编程9 天前
【用案例学Material 3 Expressive】第1课:从一封邮件开始,感受安卓设计新语言的表现力
android·学习·ui·kotlin·android jetpack
传奇开心果编程9 天前
【Jetpack Compose进阶学与练】第14课:系列收尾复习总结;Compose项目常见坑点汇总;学习路线与后续学习方向
android·学习·ui·kotlin·android jetpack
hai_android10 天前
子 View 被压小时,如何向父容器"告状"?——MEASURED_STATE_TOO_SMALL 向上传递机制
android·kotlin·android jetpack