这是我参加11月更文挑战的第19天,活动概况检查:2021最后一次更文挑战
前语
网上介绍ViewModel
的文章许多,底子都是介绍其作为数据的持有者,结合LiveData
,作为MVVM的一个成员,担任保护数据状态和派发来运用。
今日这篇文章不想介绍ViewModel
的原理,主要是想同享下我在开发顶用ViewModel
又做了些啥。
解耦,数据生命周期及代码鸿沟
组件化后形成的问题
App体积越来越大,经历了几次包体积优化,体积依然120MB左右,为了加快编译速度,咱们对App 做了模块解耦,在这儿先不评论模块解耦的优点,先评论模块解耦的直观的影响,一是模块间粒度区分很难操控,分的粗了达不到预期效果,过细则易形成循环依靠;另一方面是模块间相互引证变得十分不便利,甚至很简略的场景就需求加个Bridge,面条式代码十分影响漂亮,这何尝不是新的耦合呢。
场景1.数据初始化和销毁的机遇
咱们的App 主页是一个多Tab布局,有一份数据原本只要TAB A 运用,这次改版又增加了一个新的Tab B,Tab B 仍是可有可无的,现在这份数据在 TAB A 和 B 都需求运用,怎样初始化这份数据呢? 由于知道多个Fragment
是能够同享一个ViewModel
的,因而把数据的初始化放在ViewModel
中就是一种可行的方案,生成一个适宜的ViewModel
并监听需求的事件和适宜的机遇初始化就能够了。
class ConfigInitializer : ViewModel(), LifeCycleAware, LoginStateAware {
//...
init{
listenOnAction(action){
//doInit
}
}
}
新增加了一个Tab,这个Tab随着用户退出登录会自动消失,咱们怎样在用户退出登录时清空这Tab的数据呢?
首要清空数据的逻辑是不能放到这个Tab Fragemnt
中的,因为这个Fragemnt
是否显现是一个可选项,因而把数据销毁逻辑放在这个Tab里是会无法覆盖一切的场景的。作为模块化的架构,假如主页Fragment
对主页的生命周期感兴趣,能够注册一个简略的钩子办法(这儿运用WMRouter
),这姿态就能够不向主Module暴漏新的办法来完结数据清理逻辑,削减代码耦合。
@RouterService(interfaces = [BootPageLifeCycleObserver::class], singleton = true)
class MyModuleLifeCycleAware : BootPageLifeCycleObserver {
override fun onAppDestroy(app: Application) {
}
override fun onCreate(main: FragmentActivity) {
ViewModelProviders.of(main)[DisposeViewModel::class.java]
}
override fun onDestroy(main: FragmentActivity) {
}
}
在DisposeViewModel
监听适宜的事件,完结数据清理逻辑
class DisposeViewModel: ViewModel() {
init {
listenLogout {
MyModuleStore.clearCache()
}
}
}
场景2. 跨Module的和谐者
主页上每个Tab都是一块业务单元,分在一个独自的Module里,现在有个需求,Tab 上需求增加动画呼应Tab对应Fragemnt
的布局改变,Tab 的单击和双击事件也会影响Fragment
布局的改变,假如没有做模块解耦,完结这个需求仍是挺简略的。
现在面临的问题是Tab组件散布在主Module中,在页面临应的Module中是无法引证的,另一方面按照代码功用每个页面的动画逻辑应该局限在自己的Module里,不适宜抽取到公共的组件中,本着最小暴漏的准则,这儿Tab 组件和每个对应的页面都能通过其context
拿到唯一的ViewModel
实列,因而抽取公共的功用接口TabAnimObservable
和TabAnimConsumer
作为动画的派发者和顾客,一起运用一个ViewModel
作为两者和谐器完结生成者和顾客的连接。
这儿运用ViewModel
有两个优点,一是两个Fragment
的最小公共生命周期是其承载的Activity
防止了运用全局单例,另一方面,向外只暴漏了功用接口,每个接口对应的详细功用彻底没有暴漏到公共Module。
class TabAnimSupportCoordinator : ViewModel() {
private val consumerMap = mutableMapOf<String,TabAnimConsumer<Boolean>>()
private val driverMap = mutableMapOf<String, TabAnimObservable>()
fun addAnimTabDriver(id: String, driver: TabAnimObservable) {
driverMap[id] = driver
linkDriverAndConsumer(id)
}
fun addAnimTabConsumer(id: String, consumer: TabAnimConsumer<Boolean>) {
consumerMap[id] = consumer
linkDriverAndConsumer(id)
}
private fun linkDriverAndConsumer(id: String) {
val driver = driverMap[id]
val consumer = consumerMap[id]
if (driver != null && consumer != null) {
driver.observer(consumer)
}
}
fun onSingleTap(id: String) {
driverMap[id]?.onSingleTap()
}
fun onDoubleTap(id: String) {
driverMap[id]?.onDoubleTap()
}
fun onTabRebuild() {
//do clear on fragment recreate
}
}
More Clean Code
View 中运用ViewModel
在日常开发中,假如有一些逻辑绑定在ViewModel
上,怎样通过View来获取ViewModel
来防止参数层层传递呢?
通常咱们能够运用View#getContext()
转型为FragmentActivity
来获取所属Activity
的ViewModel
,j削减代码耦合。
ViewModelProviders.of(view.context.unwrap())[FunctionViewModel::class.java]
fun Context.unwrap(): FragmentActivity {
var context: Context = this
while (context !is Activity && context is ContextWrapper) {
context = context.baseContext
}
return context as FragmentActivity
}
假如场景比较复杂,比方在多Tab页面,咱们的View
只出现在其间一个Fragment
页面中,继续运用FragmentActivity
来引证ViewModel
就不是十分适宜了。
比方最近开发遇到的一个场景,竖向的RecycleView
中有多个横向的RecyclerView
,需求需求横向的RecyclerView
需求前次滑动停留的方位,功用完成并不难,需求咱们细心考虑的是怎样安排数据在适宜的生命周期里,以及怎样防止影响已有的代码结构?
考虑到Fragment
能够作为ViewModel
的Scope
,咱们能够从View
中拿到Fragment
的 scope
么?
通过View#``findViewTreeViewModelStoreOwner``()
咱们能够简略的拿到操控View
生命周期的最直接的ViewModelStore
,拿到ViewModelStore
,在Fragment中对应的是FragmentViewLifecycleOwner
*,至此又能够便利的取到ViewModel
比方在Adapter
中,能够onBindViewHolder
恢复横向RecycleView
前次滚动的方位,无痛插入了新的业务逻辑。
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
init(viewHolder as VH)
recoverAndRecordOffset(viewHolder)
}
private fun recoverAndRecordOffset(viewHolder: VH) {
viewHolder.itemView.post {
val target = viewHolder.recycler
val owner = target.findViewTreeViewModelStoreOwner()
if (owner != null) {
val key = model::class.java.simpleName
OffSetChangeRecorderHolder.get(owner).recover(key, target)
OffSetChangeRecorderHolder.get(owner).record(key, target)
}
}
考虑到ViewModel的生命周期比View长,上面的数据依然可能在View销毁后保存在Fragment 中,考虑到数据量很小,问题不大,这个方式也不失为一个一个可行的解决方案了。
总结
合理运用ViewModel
,能让咱们更好的标准数据的生命周期和处理代码的鸿沟问题;当然更多的场景底子就不合适运用ViewModel
,没有ViewModel
,咱们也需求合理的操控代码鸿沟,细心的标准数据的生命周期呀。
Happy Ending.