Jetpack之ViewModel

2021-12-13  本文已影响0人  0246eafe46bd

ViewModel

ViewModel 的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样可以在一定程度上减少Activity中的逻辑

ViewModel的生命周期和Activity不同,它可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁。因此,将与界面相关的变量存放在ViewModel当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失

viewmodel-lifecycle.png

ViewModel的基本用法

添加依赖

implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

创建Activity

比较好的编程规范是给每一个Activity和Fragment都创建一个对应的ViewModel,因此这里我们就先创建MyActivity,之后再为其创建ViewModel。实现的功能是点击按钮对界面上的数据加1

activity_my.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".viewmodel_study.ViewModelActivity">

    <Button
        android:id="@+id/plus_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/num_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/plus_btn" />
</androidx.constraintlayout.widget.ConstraintLayout>

MyActivity

class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
    }
}

创建 ViewModel

新建一个对应的MyViewModel类,并让它继承自ViewModel,在ViewModel中加入一个counter变量用于计数

class MyViewModel : ViewModel() {
    //加入一个counter变量用于计数
    var counter = 0
}

实现计数器的逻辑

修改MyActivity

class MyActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
        // 不可以直接去创建ViewModel的实例,一定要通过ViewModelProvider来获取ViewModel的实例
        viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
        // 按钮点击监听
        plus_btn.setOnClickListener {
            // 使用viewModel修改数据
            viewModel.counter++
            refreshCounter()
        }
        refreshCounter()
    }

    /**
     * 获取viewModel的数据刷新界面
     */
    private fun refreshCounter() {
        num_text.text = viewModel.counter.toString()
    }
}

绝对不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider来获取ViewModel的实例,因为ViewModel有其独立的生命周期,并且其生命周期要长于Activity。如果我们在onCreate()方法中直接去创建ViewModel的实例,那么每次onCreate()方法执行的时候,ViewModel都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据了

运行后旋转一下模拟器的屏幕,就会发现Activity虽然被重新创建了,但是计数器的数据却没有丢失

向ViewModel传递参数

如果需要向ViewModel传递参数,可以借助ViewModelProvider.Factory

添加功能:退出程序后又重新打开的情况下,数据仍然不会丢失

给MyViewModel的构造函数添加countReserved参数

给MyViewModel的构造函数添加一个countReserved参数,这个参数用于记录之前保存的计数值,并在初始化的时候赋值给counter变量

class MyViewModel(countReserved: Int) : ViewModel() {
    // counter变量用于计数
    var counter = countReserved
}

新建一个MyViewModelFactory类

新建一个MyViewModelFactory类,并让它实现ViewModelProvider.Factory接口

class MyViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MyViewModel(countReserved) as T
    }
}

MyViewModelFactory 的构造函数中也接收了一个countReserved参数。另外ViewModelProvider.Factory接口要求我们必须实现create()方法,因此这里在create()方法中我们创建了MainViewModel的实例,并将countReserved参数传了进去。为什么这里就可以创建MainViewModel的实例了呢?因为create()方法的执行时机和Activity的生命周期无关,所以不会产生之前提到的问题

修改MyActivity中的代码

class MyActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel
    private lateinit var preferences: SharedPreferences
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
        // 获取退出app前保存的数据
        preferences = getPreferences(Context.MODE_PRIVATE)
        val countReserved = preferences.getInt("count_reserved", 0)

        // 不可以直接去创建ViewModel的实例,一定要通过ViewModelProvider来获取ViewModel的实例
        viewModel =
            ViewModelProvider(this, MyViewModelFactory(countReserved)).get(MyViewModel::class.java)
        ......
    }

    /**
     * 获取viewModel的数据刷新界面
     */
    private fun refreshCounter() {
        num_text.text = viewModel.counter.toString()
    }

    override fun onDestroy() {
        super.onDestroy()
        preferences.edit().putInt("count_reserved", viewModel.counter).apply()
    }
}

在ViewModelProvider中,额外传入了一个MyViewModelFactory参数,将读取到的计数值传给了MyViewModelFactory的构造函数,这样就完成了向ViewModel传递数据

注意

不能把Activity的实例传给ViewModel,因为ViewModel的生命周期是长于Activity的,如果把Activity的实例传给ViewModel,就很有可能会因为Activity无法释放而造成内存泄漏

上一篇 下一篇

猜你喜欢

热点阅读