viewmodel:一个简单的例子
介绍
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续存在(出自官方文档)。举个例子,如果系统销毁或重新创建界面控制器,则存储在其中的任何临时性界面相关数据都会丢失。例如,应用的某个 Activity 中可能包含用户列表。因配置更改而重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的Bundle恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。viewmodel可以完成onSaveInstanceState()的功能,且更强大,他提供了更多的可能性与扩展性
一个简单的例子
使用ViewModel大概分为三个步骤
- 通过创建继承自ViewModel的类,将数据与UI控制器分离(Activity,Fragment)
- 在UI控制器中关联ViewModel
- 在UI控制器中使用ViewModel
步骤1 通过创建继承自ViewModel的类,将数据与UI控制器分离(Activity,Fragment)
首先你需要添加androidx软件包库中的ViewModel依赖
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
或通过官方文档获取最新版本。
我们通常需要为每个ui控制器都创建一个viewmodel,这个viewmodel的作用就是保存和屏幕关联的所有数据,并提供对应的getter/setter方法,下面是一个继承自viewmodel的类
class MainViewModel : ViewModel() {
var counterB: Int = 0
}
这里演示最简单的使用场景,就不提供get/set了
步骤2 在UI控制器中关联ViewModel
在UI控制器中建立两者的关联
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ViewModelProvider(this).get(MainViewModel::class.java)
}
步骤3 在UI控制器中使用ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
var counterA = 0
val text1 = findViewById<TextView>(R.id.text1)
val text2 = findViewById<TextView>(R.id.text2)
val button = findViewById<Button>(R.id.button)
//在销毁创建后赋值一遍
text2.text = viewModel.counterB.toString()
text1.text = counterA.toString()
button.setOnClickListener {
counterA++
viewModel.counterB++
text1.text = counterA.toString()
text2.text = viewModel.counterB.toString()
}
}
这里只提供了一个最简单的场景,可以看到即使程序因为旋转而销毁重新创建但是在viewModel中的数据并不受影响
viewModel的生命周期
下图说明了Activity经历屏幕旋转后所处的各种生命周期状态,这些状态同样适用于Fragment,通常在onCreate()方法时请求ViewModel,ViewModel存在的时间范围是从首次请求ViewModel直到Activity完成并销毁,viewmodel不要持有ui控制器的引用,或持有Context引用的任何类,如果需要在viewmodel中使用Context,如获取系统服务,可以使用Application Context,理想情况下,viewModel应该对ui控制器一无所知,这提高了可测试性、泄露安全性和模块化性,一般推荐通过观察者模式和UI控制器通信,如LiveData,这也是目前较为通用的一种方式。
viewModel在同一个UI控制器的onCreate()方法中因异常销毁而多次被调用时,所创建的ViewModel是同一个,这就是保存数据的原因
aaa@qq.com
aaa@qq.com
aaa@qq.com
aaa@qq.com
aaa@qq.com
viewModel只保存瞬态数据而非持久化存储
一旦关联的ui控制器完成或进程销毁,则ViewModel和所有包含的数据都会被回收,
viewModel是否可以替代onSaveInstanceState?
不是替代,但他们相关。
之前在网上看到有人说有onSaveInstanceState我为啥要用viewmodel?,只有深入了解二者的差异你才能更好的选择。
onSaveInstanceState在默认的情况下就会保存视图上的一些瞬时信息,如EditText的输入内容,以便在onRestoreInstanceState或onCreate还原,很关键的一个信息是onSaveInstanceState的调用时机是在活动即将销毁前,由于其运行在主线程,大量数据带来的序列化与反序列化所造成的消耗可能会造成丢帧或视觉卡顿,以及进入后台时调用,这种情况下其实可能并不需要保存数据
同时ViewModel可以替代使用Fragment setRetainInstance(true)来保留大量数据例如图像,或复杂对象。使用ViewModel你可以在ViewModel中请求网络数据并进行临时存储,当配置更改时不至于重新请求网络数据。
总结来说:
- ViewModel的保存在配置更改之上,因此在配置更改时无需重新查询外部数据(网络或数据库)
- 不在UI管理器中管理数据,这是良好的设计
- onSaveInstanceState()用于保存少量瞬态数据,ViewModel可以保存大量数据
- ViewModel可以委派外部数据加载,并且在加载完成后临时保存
- onSaveInstanceState()在配置更改期间以及活动进入后台时被调用,当系统内存不足时进程被杀死也会被调用。
- ViewModel 在系统发起的进程终止过程中会被销毁。因此,您应将 ViewModel 对象与 onSaveInstanceState()(或其他一些磁盘持久性功能)结合使用,并将标识符存储在 savedInstanceState 中,以帮助视图模型在系统终止后重新加载数据。
我应该怎么选择?
- ViewModel:在内存中存储显示关联界面控制器所需的所有数据。
- onSaveInstanceState():存储当系统停止后又重新创建界面控制器时轻松重新加载 Activity 状态所需的少量数据。这里指的是将复杂的对象保留在本地存储空间中,并将这些对象的唯一 ID 存储在 onSaveInstanceState() 中,而不是存储复杂的对象。
Google推荐的使用方法是结合使用,所以如果有人问你二者取其一你怎么回答,现在清楚了吗。
ViewModel+LiveData-与生命周期联动的更新
一个简单的例子
class MainViewModel() : ViewModel() {
// var counterB: Int = 0
val data = mutableListOf("关羽", "张飞", "黄忠", "马超", "赵云")
private val lists: MutableLiveData<List<String>>? by lazy {
MutableLiveData<List<String>>().apply {
value = data
}
}
fun getList(): MutableLiveData<List<String>> {
return lists!!
}
}
没有从外部获取数据,模拟了一个简单的赋值过程。
liveData.observe(this, Observer {
//update UI
})
介绍:
LiveData 是一种可观察的数据存储器类,和UI控制器的生命周期关联,可以确保仅更新处于活跃状态的观察者。
只有当观察者的生命周期处于 STARTED 或 RESUMED 状态LiveData才会调用onChanged,全部状态如下:
/**
* Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
* any more events. For instance, for an {@link android.app.Activity}, this state is reached
* <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
*/
DESTROYED,
/**
* Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
* the state when it is constructed but has not received
* {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
*/
INITIALIZED,
/**
* Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached in two cases:
* <ul>
* <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
* <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
* </ul>
*/
CREATED,
/**
* Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached in two cases:
* <ul>
* <li>after {@link android.app.Activity#onStart() onStart} call;
* <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
* </ul>
*/
STARTED,
/**
* Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached after {@link android.app.Activity#onResume() onResume} is called.
*/
RESUMED;
/**
* Compares if this State is greater or equal to the given {@code state}.
*
* @param state State to compare with
* @return true if this State is greater or equal to the given {@code state}
*/
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
在源码中验证:
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
通过传入STARTED判断自然顺序只有满足STARTED/RESUMED,才会通知观察者。虽然没有立即通知,但是LiveData记录下了数据,当状态变化为STARTED/RESUMED时依然会通知到观察者。
LiveData仅在数据发生变化时才会通知观察者,且只发送给活跃状态的观察者。
postValue,如果你需要在子线程通知观察者你应该使用他,如下
class MainViewModel() : ViewModel() {
// var counterB: Int = 0
val data = mutableListOf("关羽", "张飞", "黄忠", "马超", "赵云")
private val lists: MutableLiveData<List<String>> by lazy {
MutableLiveData<List<String>>().apply {
Timer().schedule(timerTask {
lists.postValue(data)
},2000)
}
}
fun getList(): MutableLiveData<List<String>> {
return lists
}
}
使用LiveData有如下好处:
- 不会发生内存泄露
- 观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
- 不会因 Activity 停止而导致崩溃
- 如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。
- 不再需要手动处理生命周期
- 界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
- 数据始终保持最新状态
- 如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
- 适当的配置更改
- 如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据
就写这些吧,突然降温给我整感冒了,大家保护好自己!
上一篇: 情意绵绵,莫问花开几时休
下一篇: 秋