Context
很早之前在项目中实现了一套从 ViewModel 获得生命周期 LifecycleOwner 的机制,最近引入到新项目中,也正好抽空整理分享一下以供其他同学参考,一起讨论一下 :P
先说一下背景,LifecycleOwner
/Lifecycle
作为 Google Android Jetpack 的核心设计底座,不得不说真的是个非常牛逼和先进的 Idea. 从我个人理解来说,一切具有生命周期特征的对象理论上都可以抽象出 Lifecycle
,于是有了下面的内容。
Why do this?
在之前的项目中,我负责的业务中基本上是比较严格地按照 MVVM
的模式编写代码(即在 VM 中只负责接受用户事件并转换成业务意图分发给 biz model,同时维护业务场景数据和 UI 状态)。
而在实际开发的过程中会遇到这种场景:在 ViewModel
中调用某些业务 model
的 API 时需要提供 Lifecycle
/LifecycleOwner
.
(其实这个也算是比较常见的 case 了,比如调用某个业务 service 接口需要透传生命周期相关的参数)
由于个人原因我很讨厌在有更优选择的情况下增加代码耦合(像是给 ViewModel
增加方法 注入来自 Fragment
/Activity
的 LifecycleOwner
之类的操作)。例如下面这种写法:
Kotlin
class XXFragment {
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel.bindLifecycleOwner(this)
}
}
class XXViewModel {
...
override fun onCleared() {
...
this.hostLifecycleOwner = null
}
fun bindLifecycleOwner(lifecycleOwner: LifecycleOwner) {
this.hostLifecycleOwner = lifecycleOwner
lifecycleOwner.doOnDestroy { this.hostLifecycleOwner = null }
}
}
为了满足业务场景需求,同时又不想增加这种很丑陋且没必要的函数,我就开动脑筋灵感一闪:ViewModel
这个东西,按照我对生命周期的理解其实也是一个具有生命周期的对象,为什么不能抽象成 LifecycleOwner
呢?
在经过了仔细思考之后,我确定这是一个完全可行的方向和做法。
开搞...
How to do it?
在确定了方向之后做法就比较明确了:我希望 ViewModel
可以作为 LifecycleOwner
相对单独存在,即可以从 ViewModel
实例直接获得生命周期相关的对象。
换句话说,其实需要做的事情就只有一个:当我有一个 ViewModel
的实例时,可以通过类似 viewModel.getLifecycleOwner()
或者 viewModel.getLifecycle()
的方式获得非空、可靠的生命周期对象。
(很简单对吧 :)
带生命周期的 ViewModel
首先 ViewModel
作为 Jetpack-lifecycle 套件提供给应用开发的基础组件,它只是一个普通的 Generic Java Class. 我们在业务开发中是不太能够对它作出修改的。
这里防杠一下,如果说通过 ASM/KCP 之类的方式修改编译产物当然是可以
怎么说,可以但没必要,而且有大炮打蚊子的嫌疑,阿巴阿巴...
so... 那么既然不能直接修改 ViewModel
,那就换个思路,在参考了 Jetpack
套件的部分实现后,很快发现了两个比较快速的方法:
- 自定义
lazy
实现,完成生命周期的注入 - 自定义
ViewModelProvider.Factory
注入 ([注 1
](#注 1 "#%E8%A1%A5%E5%85%85%E8%AF%B4%E6%98%8E"))
通过生产者注入生命周期
先看一下现在比较常见和广泛使用的官方推荐 的创建 ViewModel
方法:
Kotlin
class XXActivity {
val viewModel: XXViewModel by viewModels()
override fun onCreate(...) {
...
viewModel.doSomething()
}
}
Google 为了让我们使用 ViewModel
更便捷,不再推荐开发者使用 ViewModelProvider(this).get(XXViewModel::class.java)
这种稍显啰嗦的写法来创建了,取而代之的是通过 Activity
/Fragment
的扩展方法来一键获取实例。
热知识:众所周知 ,viewModels()
函数其实很简单,只是通过 inline
提供了范型信息、同时依赖 receiver
提供 ViewModelStoreOwner
和 factory
来创建了一个 ViewModelLazy
对象并返回。跟手写 val viewModel: XXViewModel by lazy { ... }
没有本质区别,只是把一些模版代码封在了 ViewModelLazy
内部更加便捷罢了。
那么接下来就很清楚了,参考 ViewModelLazy
我们自己实现一个 LifecycleViewModelLazy
就好了,只需要给我们创建的 ViewModel
自动注入一个 LifecycleOwner
,ok 很简单
Kotlin
/**
* Customized `lazy` delegate for [ViewModel] with lifecycle
*/
class LifecycleViewModelLazy<VM : ViewModel>(
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory,
private val lifecycleOwnerProducer: () -> LifecycleOwner,
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(store, factory).get(viewModelClass.java).also {
cached = it
it.acquireLifecycleOwner().also { lifecycleOwner ->
lifecycleOwner.attachToHost(lifecycleOwnerProducer())
/* hook viewModel.viewModelScope, use lifecycle.coroutineScope instead
* lifecycle.coroutineScope 是 lifecycle-runtime-ktx 的 internal class LifecycleCoroutineScopeImpl, 会进行类型校验无法替换
*/
it.hookViewModelScope(lifecycleOwner.lifecycle)
}
}
} else {
viewModel
}
}
override fun isInitialized(): Boolean = cached != null
}
这里要解决的主要是在 lazy
内部创建 ViewModel
实例的时候,如何注入 LifecycleOwner
的问题。
实际上就是自定义了一个 ViewModelLifecycleOwner
类型实现 LifecycleOwner
接口,内部维护一个 LifecycleRegistry
.
此外,我们还需要做的就是把自定义的 LifecycleOwner
跟当前 ViewModel
实例关联起来,并提供一个 ViewModel
的扩展方法来取回相关联的 LifecycleOwner
即可。
然后提供一个 Activity
/Fragment
可使用的 lazy
函数快速使用:
Kotlin
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.lifecycleViewModels(
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: { defaultViewModelProviderFactory }
return LifecycleViewModelLazy(VM::class, { viewModelStore }, factoryPromise, { this })
}
@MainThread
inline fun <reified VM : ViewModel> Fragment.lifecycleViewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this }, noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: { defaultViewModelProviderFactory }
return LifecycleViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryPromise, { this })
}
通过 ViewModel 取得 LifecycleOwner
关于如何建立自定义 LifecycleOwner
与 ViewModel
实例的 1-1 关联其实有很多办法,这里有一个我认为最简单的法子:直接复用 ViewModel
的 getTag(String)
函数和其内部的 mBagOfTags
Map 容器。
由于 ViewModel::getTag(String)
和 <T> ViewModel::setTagIfAbsent(String, T)
的访问级别是 package 访问,所以只需要在相同包名下提供两个桥接方法即可顺畅的调用了:
Kotlin
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun <T> ViewModel.setTagIfAbsentX(key: String, value: T): T = this.setTagIfAbsent(key, value)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun <T> ViewModel.getTagX(key: String): T? = this.getTag(key)
直接将创建实例时生成的 LifecycleOwner
存入 ViewModel::mBagOfTags
中,然后提供一个公开的扩展方法用于获取 LifecycleOwner
:
Kotlin
@Throws(IllegalStateException::class)
fun ViewModel.asLifecycleOwner(): LifecycleOwner = acquireLifecycleOwner().also {
check(it.isAttached) { "Invalid: require `by lifecycleViewModels()`, and must called after instantiation complete." }
}
Enjoy
打完收工,实现过程并不复杂。从个人实际使用的感受上来说,雀食很香 :)
举个例子:
在 ViewModel
中需要调用业务 model 中的一个功能,而这个功能需要调用到其他业务 service 的方法,需要传递 LifecycleOwner
, 这个时候就在 ViewModel
中直接提供无需依赖 Fragment
的传递,编码上更干净便捷。
Kotlin
// 某业务 Fragment
@AndroidEntryPoint
class XxxFragment : Fragment() {
private val viewModel: XxxViewModel by lifecycleViewModel()
...
}
// 业务 ViewModel
@HiltViewModel
class XxxViewModel @Inject constructor(private val bizModel: XxxBizModel) : ViewModel() {
...
fun doSometing() {
// 业务 model 某些方法需要传递一个 LifecycleOwner, 直接从 ViewModel 获取无需任何依赖
bizModel.someBizMethod(this.asLifecycleOwner(), ...)
}
}
// 业务 biz model
class XxxBizModel @Inject constructor() {
...
fun someBizMethod(lifecycleOwner: LifecycleOwner, ...) {
// 比如某些业务 service 的接口需要 LifecycleOwner, 就需要调用处传入,这算是一种很常见的场景
getService<XxBizService>.someFunc(lifecycleOwner, ...)
}
}
争议和意见
虽然通过这种小的技巧实现了 ViewModel
的独立生命周期访问,但是之前在项目中给其他同学介绍了这种用法和思路的时候被 argue 了。
总的来说,关于 ViewModel
作为 LifecycleOwner
的用法主要有两个争议点:
- 这种设计没有必要:
ViewModel
正常来说在应用中就是作为 view-model 和胶水来使用的,官方也没有类似的实践和建议,不需要; - 属于强行设计,没有道理:
ViewModel
没有生命周期的概念,强行认定为LifecycleOwner
没有意义。
我的想法
对于不同的意见我们当然是认真倾听和思考了,当然作为开发者来说,保持开放的心态和学习态度非常重要。而且每个人有不同的思考和理解也很正常,这里再阐述一下我的个人理解,希望有其他的同学提供更多的角度来让大家一起思考。
对于我个人而言,这个 case 的出发点始于项目中实际应用的场景诉求。
但是回头再来思考整个设计,我依然坚持 ViewModel
有作为独立 LifecycleOwner
的合理性,理由有几点:
-
首先,从
ViewModel
的角色和作用来说,在一个完整的 MVVM 场景中作为View
的协作者,它的实例生命周期理应是跟相关的 View (Activity
/Fragment
) 保持同步的,即随着View
创建而创建,随着View
的销毁而消亡。 -
从
ViewModel
的代码实践中也能看出,ViewModel
实例存储于ViewModelStore
([注 2:
](#注 2: "#%E8%A1%A5%E5%85%85%E8%AF%B4%E6%98%8E") 这个基本上可以认为就是Activity
/Fragment
),ViewModel::onCleared()
方法在所属的 host (Activity
/Fragment
) 销毁时被调用,即认为是消亡并释放资源。 -
论证
ViewModel
具备生命周期:
a. 从上述两点来看,ViewModel
其实是具备生命周期特征的(这一点我觉得应该是没什么疑问的);
b. 从另一个场外信息 分析(从代码实现角度论证其独立性 ,仅作侧面参考),在分析了 Hilt (Dagger) 生成的 Components (Injector) 实现可以看到:
Activity
->Fragment
的注入链是存在嵌套依赖关系的(即FragmentCI
作为ActivityCImpl
的内部类且构造器依赖 outer class 实例,可以注入来自 activity scope 的内容);- 而在
ViewModel
注入的实现中可以看到,ActivityCImpl
与ViewModelCImpl
同为ActivityRetainedCImpl
内部类各自独立没有任何依赖和耦合关系,因此我认为从这个角度来看,ViewModel 在设计设计为度来看也是完全可以作为完整独立的组件来看待的。
结合上述三个角度来看:ViewModel
具有生命周期特征、ViewModel
有生命周期相关行为,且 ViewModel
完整独立。
所以我认为 ViewModel
是完全可以认定为具有独立生命周期的 LifecycleOwner
的。
补充说明
-
注 1: 自定义
lazy
实现与ViewModelProvider.Factory
可以结合在一起使用,将生命周期相关的注入逻辑沉入自定义的Factory
, 再改造一下LifecycleViewModelLazy
内部的创建过程使用我们的自定义Factory
:Kotlinopen class LifecycleViewModelLazy<VM : ViewModel>( private val viewModelClass: KClass<VM>, private val storeProducer: () -> ViewModelStore, private val factoryProducer: () -> ViewModelProvider.Factory, private val lifecycleOwnerProducer: () -> LifecycleOwner, ) : Lazy<VM> { private var cached: VM? = null override val value: VM get() { val viewModel = cached return if (viewModel == null) { val factory = factoryProducer() val realFactory = if (factory is LifecycleViewModelFactory) factory else LifecycleViewModelFactory(factory, lifecycleOwnerProducer) ViewModelProvider(storeProducer(), realFactory).get(viewModelClass.java).also { cached = it } } else { viewModel } } final override fun isInitialized(): Boolean = cached != null }
这样修改之后,在业务侧使用就不再限制需要使用特定的
lazy
函数了,可以通过重写 hostgetDefaultViewModelProviderFactory()
方法来保证创建的 ViewModel 是符合了预期的 -
注 2: 对于
ViewModel
来说不论在Activity
还是Fragment
中使用,其存储与状态(如ViewModel::onCleared
)仅依赖ViewModelStore
,Activty
/Fragment
则是通过实现ViewModelStoreOwner
接口并提供一个ViewModelStore
实例来管理ViewModel
实例。理解了这一点那么对于
ViewModel
的使用就不限于Activity
/Fragment
了,任意业务组件其实都可以通过同样的方式来管理自己的ViewModel
和依赖注入达到更彻底的解耦。比如目前尚在草稿阶段的某统一组件抽象方案(此处不展开)