这是我参加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实列,因而抽取公共的功用接口TabAnimObservableTabAnimConsumer作为动画的派发者和顾客,一起运用一个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来获取所属ActivityViewModel,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能够作为ViewModelScope,咱们能够从View中拿到Fragmentscope 么?

通过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.