Android Jetpack 组件之 ViewModel(Kotlin)
文章目录
简介
Android 的页面在创建与销毁时,会触发不同的生命周期。当 Activity 重建时,页面上的数据会丢失。为了保存页面的数据,我们以前通常的做法是在 onSaveInstanceState
中,将数据保存到 bundle 中,再在 onCreate
中将 bundle 中的数据取出来。
现在有了 ViewModel,我们就无需再用这种方法保存,因为 ViewModel 会自动感知生命周期,处理数据的保存与恢复。引用一张官网的介绍图:
导入
android {
...
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
...
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation 'androidx.fragment:fragment-ktx:1.2.4'
}
ViewModel 的使用
新建 MyViewModel 类,继承自 ViewModel
class MyViewModel : ViewModel() {
var number = 0
}
修改 MainActivity
布局文件中只有一个 id 为 tv 的 TextView 和一个 id 为 btn 的 Button,故省略布局文件。
class MainActivity : AppCompatActivity() {
private val myViewModel by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv.text = myViewModel.number.toString()
btn.setOnClickListener {
myViewModel.number++
tv.text = myViewModel.number.toString()
}
}
}
运行效果
对比不使用 ViewModel 的情况
如果不使用 ViewModel ,只用一个 number 变量保存数据的话,页面重建时数据将会丢失,运行效果如下:
ViewModel 的局限性
当进程在后台被系统杀死后,ViewModel 里的数据还是会丢失。为了模拟这个情景,我们先到开发者选项中将 Don't keep activities
打开:
这个设置打开的作用是,当我们点击 home 键使程序进入后台时,程序会立刻被系统杀掉。
这时我们再次运行以上使用 ViewModel 的程序,效果如下:
可以看到,当进程被系统回收后,ViewModel 中的数据丢失了。为了解决这个问题,我们需要用到 ViewModelSavedState.
ViewModelSavedState
导入 ViewModelSavedState
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
修改 MyViewModel
导入依赖后,我们就多了一个构造方法,允许 ViewModel 传入一个 SavedStateHandle
类。这个类的内部使用了一个 HashMap 保存数据。
const val NUMBER_KEY = "number_key"
class MyViewModel(private val state: SavedStateHandle) : ViewModel() {
var number: Int
get() {
return state.get<Int>(NUMBER_KEY) ?: 0
}
set(value) {
state[NUMBER_KEY] = value
}
}
可以看到,我们在 number 的 get 方法中,通过 SavedStateHandle
获取 number,如果没有获取到,默认返回 0;在 set 方法中,修改 state 中对应的值。
修改 MainActivity
class MainActivity : AppCompatActivity() {
private val myViewModel by lazy {
ViewModelProvider(this, SavedStateViewModelFactory(application, this)).get(MyViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv.text = myViewModel.number.toString()
btn.setOnClickListener {
myViewModel.number++
tv.text = myViewModel.number.toString()
}
}
}
唯一的修改在于 myViewModel 的初始化,不妨记做固定写法。
再次运行程序,数据就不会丢失了。
需要注意的是,这里的数据也不是永久保存的,当手机重启或者用户手动杀掉进程后,数据仍然会丢失。
如果需要持久化存储,可以使用 SharedPreferences或数据库将其存储起来。
当调用这些存储方法时,往往我们都会用到 Context,所以 Android 给我们提供了一个 AndroidViewModel 类。
AndroidViewModel
AndroidViewModel 类做的事情很简单,就是封装了一个 Application 字段。
源码如下:
public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
@NonNull
public <T extends Application> T getApplication() {
return (T) mApplication;
}
}
使用如下:
class MyViewModel(application: Application) : AndroidViewModel(application) {
fun test() {
Log.d("~~~", getApplication<Application>().resources.getString(R.string.app_name))
}
}
只需继承 AndroidViewModel,我们就可以通过 getApplication<Application>
来获取到 Application 对象。
Activity 中对 ViewModel 的初始化、使用还是和以前一样,完全不用修改。