Jetpack - ViewModel

文章详细介绍了Android开发中ViewModel的使用,包括其在Activity/Fragment重建时保持状态的能力,如何创建和获取ViewModel实例,特别是ViewModelStoreOwner的作用,以及如何通过Factory创建带有参数的ViewModel。文章还提到了SavedStateHandle在处理配置更改时保存和恢复状态的功能,并简单提及了依赖注入库Hilt在简化ViewModel创建方面的帮助。
摘要由CSDN通过智能技术生成

一、概念

1.1 恢复页面状态

Activity/Fragment 通常会在以下三种情况被销毁,对于后两种情况我们会希望重建时能恢复之前的页面状态。

当前界面永久离开用户跳转至其它界面或直接关闭Activity(调用finish()、点击返回按钮)
配置发生更改屏幕旋转、系统语言等操作会使 Activity 需要立即重建。
被系统杀死应用在后台因为内存不足被系统杀死,用户返回应用后,Activity 会被重建。

二、创建 ViewModel

通过属性委托 ViewModels()

通过 ViewModelProvider.get()

调用 get() 获取 ViewModel 实例时,会先用 “包名+类名” 作为 key 到 ViewModelStore 中查找,已经创建过就直接返回(因此同一作用域下重复获取的 ViewModel 为同一实例),没有就进而调用 Factory 的 create() 反射创建一个新的并保存到 ViewModelStore 中。

ViewModelProvider

public constructor(
    owner: ViewModelStoreOwner       
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

创建无参 ViewModel 使用。

public constructor(

    owner: ViewModelStoreOwner,

    factory: Factory       

) : this(owner.viewModelStore, factory, defaultCreationExtras(owner))

创建有参 ViewModel 使用。

constructor(
    private val store: ViewModelStore,        //指定作用域(保存ViewModel的地方)。
    private val factory: Factory,        //自定义工厂用于创建有参ViewModel。
    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
)

上面两个都是调用的这个,传过来的 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)

CreationExtrasAPPLICATION_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() { }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值