一、概念
1.1 恢复页面状态
Activity/Fragment 通常会在以下三种情况被销毁,对于后两种情况我们会希望重建时能恢复之前的页面状态。
当前界面永久离开 | 用户跳转至其它界面或直接关闭Activity(调用finish()、点击返回按钮) |
配置发生更改 | 屏幕旋转、系统语言等操作会使 Activity 需要立即重建。 |
被系统杀死 | 应用在后台因为内存不足被系统杀死,用户返回应用后,Activity 会被重建。 |
二、创建 ViewModel
通过属性委托 ViewModels()
通过 ViewModelProvider.get()
调用 get() 获取 ViewModel 实例时,会先用 “包名+类名” 作为 key 到 ViewModelStore 中查找,已经创建过就直接返回(因此同一作用域下重复获取的 ViewModel 为同一实例),没有就进而调用 Factory 的 create() 反射创建一个新的并保存到 ViewModelStore 中。
ViewModelProvider | public constructor( 创建无参 ViewModel 使用。 |
public constructor( owner: ViewModelStoreOwner, factory: Factory ) : this(owner.viewModelStore, factory, defaultCreationExtras(owner)) 创建有参 ViewModel 使用。 | |
constructor( 上面两个都是调用的这个,传过来的 defaultCreationExtras(owner) 是从 Activity/Fragment 中获取的(预设 key 都是从这里注入的,如ComponentActivity 的实现中提供了 Application 和 Intent)。 | |
get() | public open operator fun <T : ViewModel> get(modelClass: Class<T>): T public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T 形参 modelClass:需要创建的 ViewModel 的 class(如 MainActivity::class.java)。 形参 key:不指定就是“包名+类名”。 |
Activity {
val viewModel = ViewModelProvider(this).get(DemoViewModel::class.java)
}
2.1 ViewModelStoreOwner
在获取 ViewModel 对象时需要传入一个作用域 ViewModelStoreOwner,即保存 ViewModel 实例的地方,可以是 Activity/Fragment/Navigation 它们都有实现该接口。
例如 ComponentActivity 会通过 Lifecycle 观察生命周期 onDestory() 并进行判断,只有在非配置更改的情况下(!isChangeingConfigurations())才会调用 clear() 清除缓存的 ViewModel 列表。这使得 ViewModels 不会因为配置更改而销毁,成为了存储在配置更改后仍然存在的数据的绝佳解决方案,这也是为什么 ViewModel 不适用在后台因为内存不足被系统杀死时的情况(使用 SavedStateHandle 解决)。
- 由于 ViewModel 的生命周期是大于 Activity/Fragment 的, 对于数据的初始化不要放在 UI 生命周期中随着配置更改重建而反复请求,而是放在 ViewModel 自己的 init{ } 代码块中。
2.1.1 指定作用域
指定为最近的作用域(即获取实例时外层直接包裹它的 )。
val viewModel = ViewModelProvider(this).get(DemoViewModel::class.java)
val viewModel: DemoViewModel by viewModels()
指定为任意作用域。
val viewModel: DemoViewModel by viewModels(
ownerProducer = { requireParentFragment() }
)
//更便捷的在Fragment中将作用域限定为宿主Activity
val viewModel: SharedViewModel by activityViewModels()
指定为 Navigation 图。
val viewModel: DemoViewModel by viewModels(
{ findNavController().getBackStackEntry(R.id.nav_graph) }
)
//更便捷的指定
val viewModel: DemoViewModel by navGraphViewModels(R.id.nav_graph)
//使用 Hilt
val viewModel: SharedViewModel by hiltNavGraphViewModels(R.id.nav_graph)
2.1.2 同一作用域下重复获取的 ViewModel 为同一实例
同一 Activity 下两个 Fragment 间的通信。
class DemoActivity {
class FragmentA : Fragment() {
val viewModel: DemoViewModel by activityViewModels()
}
class FragmentB : Fragment() {
val viewModel: DemoViewModel by activityViewModels()
}
}
2.2 ViewModelProvider.Factory
默认 Factory 使用反射创建实例,所以 ViewModel 的构造函数不能有参数 。如需创建带参 ViewModel 需要自定义 Factory。
create() | public fun <T : ViewModel> create(modelClass: Class<T>): T public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T CreationExtras 是在 Lifecycle v2.5.0 引入的,替代 Factory 为创建 ViewModel 提供所需参数,使得 Factory 无需再持有状态,一个无状态的 Factory 能被更好的复用。(此前为了构建不同参数类型的 ViewModel 而存在各种特殊的 Factory 子类,如提供 Application 的 AndroidViewModelFactory、提供 SavedStateHandle 的AbstractSavedStateViewModelFactory) |
CreationExtras | APPLICATION_KEY:提供当前 Application context。 |
VIEW_MODEL_KEY:获取的 String 为在 ViewModelProvider.get() 中传入的 key 参数。可以基于 key 区分多个 ViewModel 实例。 | |
DEFAULT_ARGS_KEY:获取 Activity 中的 Intent 进而获取 Bundle。createSavedStateHandle 所需的 Bundle。 | |
SAVED_STATE_REGISTRY_OWNER_KEY:提供创建 createSavedStateHandle 所需的 SavedStateRegistryOwner。 | |
VIEW_MODEL_STORE_OWNER_KEY:createSavedStateHandle 所需的 ViewModelStoreOwner。 |
2.2.1 普通写法
class DemoRepository
class DemoViewModel(private val repository: DemoRepository) : ViewModel()
class DemoFactory(
private val repository: DemoRepository
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return DemoViewModel(repository) as T
}
}
Activity {
val viewModel1 = DemoFactory(DemoRepository()).create(DemoViewModel::class.java)
val viewModel2 = viewModels<DemoViewModel> {
DemoFactory(DemoRepository())
}
}
2.2.2 DSL 写法
class DemoRepository
class DemoViewModel(private val repository: DemoRepository) : ViewModel()
private val factory = viewModelFactory {
initializer {
DemoViewModel(DemoRepository())
}
}
Activity {
val viewModel = factory.create(DemoViewModel::class.java)
}
2.2.3 通用版写法
定义通用工厂
class DemoRepository
class DemoViewModel(repository: DemoRepository) : ViewModel()
private val demoRepository = object : CreationExtras.Key<DemoRepository>{}
@Suppress("UNCHECKED_CAST")
val ViewModelFactory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
//通过 key 获取 Application
val APP = checkNotNull(extras[APPLICATION_KEY]) as APP
//通过 key 获取 SavedStateHandle
val savedStateHandle = extras.createSavedStateHandle()
//通过 key 获取 Activity 中 Intent 的 Bundle 数据
val bundle = extras[DEFAULT_ARGS_KEY]
//根据不同的 class 类名返回对应的 ViewModel 实例
return when (modelClass) {
DemoViewModel::class.java -> {
//通过 key 获取自定义的参数来创建带参 ViewModel
extras[demoRepository]?.let {
DemoViewModel(it)
}
}
else -> {
throw IllegalArgumentException("Unknown class $modelClass")
}
} as T
}
}
Activity 中使用
Activity {
//通过重写getDefaultViewModelCreationExtras()
//来自定义 ViewModelProvider 构造中的 defaultCreationExtras
//往里面传入提供给自定义 ViewModel 创建时需要的参数
override val defaultViewModelCreationExtras: CreationExtras
get() {
super.defaultViewModelCreationExtras
return MutableCreationExtras(super.defaultViewModelCreationExtras).apply {
set(demoRepository, DemoRepository())
}
}
val viewModel = ViewModelFactory.create(DemoViewModel::class.java)
}
Compose 中使用
@Composable
fun Demo() {
val owner = LocalViewModelStoreOwner.current
val defaultExtras = (owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras ?: CreationExtras.Empty
val extras = MutableCreationExtras(defaultExtras).apply {
set(demoRepository, DemoRepository())
}
val factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return extras[demoRepository]?.let {
DemoViewModel(it)
}as T
}
}
val viewModel = factory.create(DemoViewModel::class.java, extras)
}
四、SavedStateHandle
在自定义类 ViewModel 的构造中可传入一个 SavedStateHandle 类型参数,和 Bundle 一样以键值对形式存储数据,可以在 APP 处于后台时进程被杀死的情况下幸存下来。
- 简化了状态的保存/还原操作,底层还是由 Activity 中 onSaveInstanceState() 、onCreate() 触发时机。
contains(key: String) | 检查是否存在给定键的值。 |
remove(key: String) | 移除给定键的值。 |
keys() | 返回 SavedStateHandle 中包含的所有键。 |
class DemoViewModel(
private val state: SavedStateHandle
) : ViewModel() {
//普通使用
var name: String
get() = state["name"] ?: ""
set(value) { state["name"] = value }
//编译器会提示推荐使用上面的方式存取
var age: Int
get() = state.get("age") ?: 0
set(value) { state.set("age", value) }
//LiveData
var addressLiveData: LiveData<String> = state.getLiveData("address", "")
//StateFlow
var phoneStateFlow: StateFlow<Long> = state.getStateFlow("phoneNum", 11122223333L)
//Compose
var coinMutableState: Long by state.saveable { mutableStateOf(123L) }
}
class DemoActivity : ComponentActivity(){
val viewModel: DemoViewModel by viewModels()
fun test() {
viewModel.name
viewModel.addressLiveData.observe(this) {}
}
}
五、使用 Hilt
Hilt 避免了 Factory 的使用,在写法上最为简单。
二、使用
ViewModel 生命周期长于 Activity/Fragment,如果直接 new 出来就失去了意义。
2.1 创建ViewModel
//无参
class MainViewModel : ViewModel() {}
//有参
class MainViewModel(mainRepository: MainRepository) : ViewModel() {}
class MainViewModelFactory(private val mainRepository: MainRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel(mainRepository) as T
}
}
2.2 View中使用
不推荐
//写法一:延迟初始化(因为不能在onCreate之前调用)
lateinit var viewmodel: MainViewModel
onCreate{
ViewModelProvider(this)[MainViewModel::class.java] //无参
MainViewModelFactory(MainRepository()).create(MainViewModel::class.java) //有参
}
//写法二:懒加载(可以一次性都写在类属性位置)
val viewmodel by lazy {
ViewModelProvider(this)[MainViewModel::class.java] //无参
MainViewModelFactory(MainRepository()).create(MainViewModel::class.java) //有参
}
推荐(不能用的话需要导包)
implementation "androidx.activity:activity-ktx:1.6.1"
val viewModel by viewModels<MainViewModel>() //无参
val viewModel by viewModels<MainViewModel> { MainViewModelFactory(MainRepository()) } //有参
2.3 Compose中使用
- 仅用于最顶层屏幕级组合函数(离 Activity/Fragment 中 setContent() 最近的那个)。
- 遵循唯一可信数据源,ViewModel将状态传递给子组合项,子组合项将事件上抛给父组合项,不要直接将 ViewModel 向下传递给子组合项。
@Composable
fun DemoScreen(
viewModel: DemoViewModel = viewModel(),
viewmodel2: DemoViewModel2 = viewModel(factory = DemoViewModelFactory(DemoRepository()))
) {
Demo(dataState = viewModel.dataState) {
viewModel.buttonClicked()
}
}
@Composable
fun Demo(
dataState: String,
onClick: () -> Unit,
) {
Button(onClick = onClick) { Text(text = dataState) }
}
class DemoViewModel : ViewModel() {
var dataState by mutableStateOf("")
private set
fun buttonClicked() { }
}