公司的项目,一直是以Activity为主体,类MVC模式的进行开发的,加入如今的公司一年以来,由于如今接手项目比较老,所以以前一直是在作项目新的开发加填之前的老坑。项目的主页甚至还在用已经废弃了好久的TabActivity,以前下定决心改把主页修改为了Activity+多Fragment的模式,之前的界面,Activity的逻辑过于复杂,正好接着此次重构的机会也和同事了解学习一下MVVM,为以后的开发算是作一个本身的准备吧。java
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}"
复制代码
为了让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
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
不论是MVC,MVP,MVVM的设计模式内,均存在Model层,可见Model层是极其重要的。可是在Android开发内,Model层反而是可能存在感最薄弱的一层,由于如今获取数据的代码,不论是联网获取,或者是读取数据库,或者是读取本地的数据,代码已经精简到短短几行就能够实现,不少开发的时候,不自觉的把这些方式写在了Activity、Fragment内,又或者是写在了ViewModel或者是Presenter内,以前在读一个MVVM实现的时候,就直接将获取数据写在了ViewModel内。
那么这样写,会致使什么问题?若是是简单的数据以及相对简单的逻辑,它并不会形成太多的影响,可读性也没有收到不少的影响,可是若是须要进行单元测试的话,数据耦合在了逻辑里,会对单元测试形成极大的影响。这是我对这个问题的见解。(ps:小弟技术菜,没有想到别的一些问题,只能看出这一点影响可能较大的问题,有补充欢迎评论留言,谢谢!)
按照规范标准来看,Model层是负责数据存储,数据处理,以及获取数据。可是Google不像ViewModel、LiveData、DataBinding提供了现成的规范以及标准,所以我对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具体实现的代码大体以下
class SampleModel : BaseModel {
fun getData(callBack: BaseModel.ModelDataCallBack<List<User>>){
// 若是成功
callBack.onSuccess(listOf(User("A",15)))
// 失败
callBack.onFailure("数据获取失败")
}
}
复制代码
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,会碰到的问题还有以下:
这些问题都是这个简单的Model会碰到的,单元测试坑比较深,以后有空再单独写。 这个model的并不存在公共的实现方法,那么根本不须要一个单独的BaseModel接口,BaseModel的意义并不存在。若是这个model须要缓存,若是只是model内存储一个数据,那么这样的逻辑必然会影响到单元测试。所以这只是个人一个简单的想法,后续还要完善。
在本身的这些想法以后,我专门去学习了一下Google的Sample的源代码。放上Google的Sample,这里是连接:github.com/googlesampl…。 先引用一张来自朋友博客的图片,博客连接:博客连接
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。
感谢各位的阅读,若是有什么想法,欢迎提出意见、批评。