MVVM的学习记录和思考

为何学习MVVM

公司的项目,一直是以Activity为主体,类MVC模式的进行开发的,加入如今的公司一年以来,由于如今接手项目比较老,所以以前一直是在作项目新的开发加填之前的老坑。项目的主页甚至还在用已经废弃了好久的TabActivity,以前下定决心改把主页修改为了Activity+多Fragment的模式,之前的界面,Activity的逻辑过于复杂,正好接着此次重构的机会也和同事了解学习一下MVVM,为以后的开发算是作一个本身的准备吧。java

学习的过程

DataBinding

Google为了MVVM,提供了很多的🌰以及框架,如今Google主推的就是Jetpack了,MVVM的核心就是数据绑定,这个在Android里,由于XML做为view的功能极其孱弱,Google在Jetpack里提供了Databinding的组件,让XML和ViewModel进行数据绑定,经过绑定,若是ViewModel的数据变化,UI便可出现对应的响应。android

XML内使用DataBindinggit

<variable
        name="viewmodel"
        type="com.acclex.ViewModel" />
    <TextView
        android:text="@{viewmodel.user.name}"
复制代码

LiveData

为了让ViewModel去操做数据,方便Activity和XML观察数据的改变,Google还提供了LiveData这个框架,它的本质是一个相似RxJava的实现观察者模式的框架,大体使用方式以下github

ViewModel内数据库

private val _user: MutableLiveData<User> 
    val user:LiveData<User>
        get() = _user

    // 更改数据
    private fun updateUser() {
        _user.value = User() 
    }
复制代码

Activity或者Fragment内设计模式

viewModel.user.observe(this, Observer<User> { user ->
        user?.let {
            //todo do something
        }
    })
复制代码

很简单有效就能够实现观察者模式,让view层和ViewModel层解耦,经过这种方式去处理数据的变更,和RxJava实现的功能是一致的,所以MVVM也可使用RxJava实现同样的功能。经过观察者模式,可让view与数据解耦开来,Activity以及Fragment不须要再去处理任何与数据相关的事情。缓存

LiveData仍是有一些好处的,由于它是Google开发封装的,它自带了生命周期的管理,由于它observe的直接是LifecycleOwner这个对象,若是LifecycleOwner的对象被销毁,LiveData则会本身去clean掉,我的认为和生命周期绑定,这是一个很棒的优势,更多的优势,Google的官方文档有详细的介绍:developer.android.google.cn/topic/libra…bash

ViewModel

Jetpack内还提供了ViewModel让开发者去使用,ViewModel其实就是对业务逻辑和业务数据的操做,在ViewModel里,不会也不能够持有任何View的引用,定义了一个ViewModel后,咱们一般在View层使用val viewModel = ViewModelProviders.of(this).get(ViewModel::class.java)这样的代码去获取ViewModel的实例,这个this能够是Activity或者Fragment,ViewModel被初始化后会一直保留在内存内,直到它所做用域也就是Fragment触发detached或者Activity触发finishes,它才会被回收。 若是咱们须要在初始化ViewModel的时候传入构造参数,那么咱们必需要写一个继承自ViewModelProvider.NewInstanceFactory的类,代码以下框架

class SampleViewModelFactory(
        private val model: Model
) : ViewModelProvider.NewInstanceFactory() {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>) =
            with(modelClass) {
                when {
                    isAssignableFrom(SampleViewModel::class.java) ->
                        SampleViewModel(model)
                    else ->
                        throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
                }
            } as T

}
复制代码

这些都是为了方便开发者使用MVVM模式,Google在Jetpack内提供给咱们的一些组件,单独来看,这些组件的使用方法,学习成本并不高,同时并无涉及到Model层单独作一个组件封装,由于Model能够说是最自由,也是定制最多的组件。以前在我写MVVM的demo的时候,我并无单独的写出一个Model,甚至将获取数据写在了ViewMode里,让ViewModel去获取解析数据,并处理数据,以后的继续学习,特别是阅读了Google的android-architecture的源代码以后,以前的思路能够说是彻底错误的,接下来咱们就来谈谈关于MVVM内Model层的定义与使用ide

Model

不论是MVC,MVP,MVVM的设计模式内,均存在Model层,可见Model层是极其重要的。可是在Android开发内,Model层反而是可能存在感最薄弱的一层,由于如今获取数据的代码,不论是联网获取,或者是读取数据库,或者是读取本地的数据,代码已经精简到短短几行就能够实现,不少开发的时候,不自觉的把这些方式写在了Activity、Fragment内,又或者是写在了ViewModel或者是Presenter内,以前在读一个MVVM实现的时候,就直接将获取数据写在了ViewModel内。

那么这样写,会致使什么问题?若是是简单的数据以及相对简单的逻辑,它并不会形成太多的影响,可读性也没有收到不少的影响,可是若是须要进行单元测试的话,数据耦合在了逻辑里,会对单元测试形成极大的影响。这是我对这个问题的见解。(ps:小弟技术菜,没有想到别的一些问题,只能看出这一点影响可能较大的问题,有补充欢迎评论留言,谢谢!)

按照规范标准来看,Model层是负责数据存储,数据处理,以及获取数据。可是Google不像ViewModel、LiveData、DataBinding提供了现成的规范以及标准,所以我对Model层实际上是有一些问题的

Model层如何构造,包含那些接口以及基本方法

这能够说是Model层最关键的问题了,由于这关系到Model层的实现。这点我以为仍是须要参照代码来讲明的。

恰好在此次项目的新的开发任务,我部分模块采用了MVVM去实现,而且尝试了一下本身进行Model的设计,所以直接上代码,说一下个人理解。

interface BaseModel {
    interface ModelDataCallBack<T> {
        /**
         * 成功的回调函数
         * @param result 成功返回须要的类型
         */
        fun onSuccess(result: T)

        /**
         * 失败的回调函数
         * @param errorLog 失败后传递回去的错误的数据
         */
        fun onFailure(errorLog: String)
    }
}
复制代码

一个很简单的基础的Model,接口ModelDataCallBack负责回调结果给ViewModel处理结果,由于每一个ViewModel、Model须要的数据不同,所以回传的结果是由初始化传进来的泛型决定的。失败的话,在我设想里,应该是返回一个解析的结果或者异常log,也许是弹出一个Toast或者是一些别的逻辑,所以返回失败的结果定义成了返回一个String。 所以ViewModel、Model具体实现的代码大体以下

Model内
class SampleModel : BaseModel {
    fun getData(callBack: BaseModel.ModelDataCallBack<List<User>>){
        // 若是成功
        callBack.onSuccess(listOf(User("A",15)))
        // 失败
        callBack.onFailure("数据获取失败")
    }
}
复制代码
ViewModel内
class SampleViewModel(private val model:SampleModel) : ViewModel {

    private val _list = MutableLiveData<List<User>>()
    val list: LiveData<List<User>>
        get() = _list
    
    private fun updateUser() {
        model.getData(object :BaseModel.ModelDataCallBack<List<User>>{
            override fun onSuccess(result: List<User>) {
                list.value = result
            }

            override fun onFailure(errorLog: String) {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }
        })
    }
}
复制代码

如上代码,能够达成ViewModel负责处理逻辑,而Model负责获取数据,ViewModel内接到成功或者失败的回调,能够触发LiveData的数据更新,View层能够经过观察LiveData内的数据,作到UI的更新或者变换。 这是我设想的一个简单的Model层,若是在开发中,一个Model内有许多的获取数据的方法或者接口,那么会产生大量的接口回调,若是是用kotlin的话,可使用高阶函数直接传入成功或者失败的回调,相似以下代码

fun getData(success:(List<User>) -> Unit,
                fail:(String) ->Unit){
        success.invoke(listOf(User("A",15)))
        fail.invoke("数据获取失败")
    }
复制代码

在个人想法里,大量的接口回调基本是不可能避免的,若是有dalao有更好的方案,欢迎评论区提出,感谢!

上述代码,只是一个很简单的Model设计,若是在开发中使用这样的Model,会碰到的问题还有以下:

  • 单元测试如何实现
  • BaseModel这个接口是否有存在的意义,是否只须要一个ModelDataCallBack接口便可
  • 若是有数据缓存需求,应该怎么处理

这些问题都是这个简单的Model会碰到的,单元测试坑比较深,以后有空再单独写。 这个model的并不存在公共的实现方法,那么根本不须要一个单独的BaseModel接口,BaseModel的意义并不存在。若是这个model须要缓存,若是只是model内存储一个数据,那么这样的逻辑必然会影响到单元测试。所以这只是个人一个简单的想法,后续还要完善。

Google MVVM Sample

在本身的这些想法以后,我专门去学习了一下Google的Sample的源代码。放上Google的Sample,这里是连接:github.com/googlesampl…。 先引用一张来自朋友博客的图片,博客连接:博客连接

每一个Model是一个Respository都是一个DataSource接口的实例,里面可能包含一种或者多种数据,每一个数据类型都实现了DataSource接口。在Google的Sample里,也是根据这种模式去实现的。这是很理想化的Model设计,本地的数据,缓存的数据,测试的数据,单独区分,每一个负责对应的职责,将代码解耦开来,是很是好的。 下面上代码

interface TasksDataSource {

    interface LoadTasksCallback {

        fun onTasksLoaded(tasks: List<Task>)

        fun onDataNotAvailable()
    }

    interface GetTaskCallback {

        fun onTaskLoaded(task: Task)

        fun onDataNotAvailable()
    }

    fun getTasks(callback: LoadTasksCallback)

    fun getTask(taskId: String, callback: GetTaskCallback)

    fun saveTask(task: Task)

    fun completeTask(task: Task)

    fun completeTask(taskId: String)

    fun activateTask(task: Task)

    fun activateTask(taskId: String)

    fun clearCompletedTasks()

    fun refreshTasks()

    fun deleteAllTasks()

    fun deleteTask(taskId: String)
}
复制代码

Repository都实现了TasksDataSource接口,而且包含有多个实现了TasksDataSource接口的数据,或是本地数据,或是缓存数据。而且只有getTasks和getTask这两个函数有回调方法,做为回调给ViewModel的数据接口。别的函数做为Model暴露给ViewModel去操做处理数据的函数。提升了通用性,可让一个Repository去同时完成对缓存数据或者新数据的操做。

总结一下

使用MVVM后,确实对代码解耦产生了极好的效果,代码的可读性也上升的不少。文章里不少东西仍是本人的一些想法, 以及碰到的一些问题并无找到好的解决方案,同时在开发中,使用DataBinding以后代码的debug麻烦程度上升有点多,Model层的设计难度是我以为最可贵一个点,Google Sample里我以为也有一些不太好的地方,以后我会再写一篇讨论一下Google的这个MVVM的Sample。

感谢各位的阅读,若是有什么想法,欢迎提出意见、批评。

相关文章
相关标签/搜索