我就不绕弯子了,我就是来推广自己的MVP、MVVM框架的,求小星星:https://github.com/xiazunyang/brick
首先,我们将一个操作拆分成以下步骤来讲解一下从MVC到MVP,再到MVVM的演化过程。
1.用户点击屏幕。
2.显示等待动画。
3.启动后台线程加载数据。
4.获取到数据结果,关闭等待动画。
5.显示数据结果。
我们将MVC的各个角色代入到以上步骤中:
1.用户操作View产生指令。
2.View将指令传递到Controller,Controller通知View显示等待动画。
3.Controller通过Model获取数据。
4.Model将结果传递给View,并关闭等待动画。
5.View加载数据结果。
可以发现,控制等待动画的可能是Controller,也可能是Model。并且Model传递数据时,可能传递给Controller,也可能直接传递给View。把Controller的角色无限弱化,就是人人学写代码时就会使用的MVC架构了。
画成图就是这样:
在MVC中,每个界面有一套自己的Model-View-Controller,界面之间无法复用其它界面的角色。比如,在两个不同的界面中访问相同的网络接口,甚至要写两遍相同的代码。
优点:对于小项目来讲,简单易用,学习成本低。
缺点:高耦合;复用性低;不易做单元测试;项目过大时,代码非常混乱。
把MVC中各角色的职责划分开来,让Model与View不能直接相互访问,并且始终通过中间层来访问,就是我们常用的MVP架构了。
将MVP的各个角色代入后的调用过程:
1.用户操作View产生指令。
2.View将指令传递到Presenter。
3.Presenter通知View显示等待动画,并调用Model获取数据。
4.Model将结果传递给Presenter,Presenter通知View关闭等待动画。
5.Presenter将数据传递给View,加载数据结果。
画成图就是这样:
在MVP中,View与Model都是负责原子性的操作,由Presenter将它们的动作连接起来完成一个完整的流程,在大多数MVP框架中,1个界面实现1个View,每个View持有一个Presenter,每个Presenter持有多个Model,由此来实现Model的复用。
优点:提高了代码的复用性;可以方便地进行单元测试。
缺点:各角色初始化的代码略多;Model复用时,Presenter中依然存在重复代码;
在这种MVP架构中,Model的代码实现了复用,但是Presenter中依然存在重复的代码,这给MVP的演进留有了一定的空间。我将在下面对MVVM的讲解中,进一步完善它。
从MVC到MVP,只需要划分角色职责就能实现,无须其它框架的支持。而MVVM是在MVP的基础上,还需要引入dataBinding、Lifecycler、ViewModel、LiveData等框架后才能实现,学习成本成倍的上升。
但是本文并不打算讲解dataBinding、Lifecycler、ViewModel和LiveData,因为它们每一个都是可以独立使用的框架,相关的文章大家可以另外去找。但是Lifecycler、ViewModel和LiveData最核心的特性,必需先讲一讲。
1.Lifecycle可以理解为用于监听Activity和Fragment的生命周期回调的框架,当Activity和Fragment的生命周期发生变化时,Lifecycle可以感知到变化。
2.ViewModel不会因为Activity和Fragment的配置更改(比如旋转)而被销毁,ViewModel的持有者被重新创建时,将重新连接到之前持有的ViewModel。只有当Activity和Fragment确定销毁时,ViewModel才会销毁。
3.LiveData是一个数据容器,控件可以订阅它里面的数据,订阅时需要Lifecycle作为参数,当数据发生变化时,并且在Lifecycle处于活动状态时,才会通知订阅它的控件。
这也就意味着,储存在ViewModel中的数据在Activity和Fragment销毁前是不会丢失的。
并且中间层不再需要把数据传递回View层,直接放在ViewModel里面的LiveData中即可,这样可以少定义一些View的接口。
MVP中的Presenter是没有要求继承任何类的,而ViewModel是一个非常优秀的中间层,所以让Presenter继承ViewModel是可行的!
同时这也带来了一个好处,ViewModel会在View销毁时销毁自己,这可以帮助我们减少内存泄漏的风险。
即使使用了Lifecycler、ViewModel和LiveData之后能有这么多好处,但是它与MVVM之间还差多少呢?答案是,一个dataBinding。
是的没错,MVVM与MVP的区别,仅仅是用没用dataBinding的区别(至少在Android中,是这样的)。
但是想理解MVVM,并充分发挥它的全部实力,掌握使用了Lifecycler、ViewModel和LiveData的MVP是必需的。
并且,我要彻底解决掉Presenter中依然存在重复代码的问题,要把业务代码的复用率提升到极致;并且利用Kotlin的特性,消灭掉MVP初始化代码太多的问题!最终实现如以下图中的超强复用性:
框架特色:
1.1套业务代码对应1套MVVM体系,界面只需要实现业务的View接口即可,一个界面可调用多套业务代码。
2.提供Kotlin扩展方法与工厂方法来创建ViewModel,并自动为其创建Model。
3.在创建Model时,可以通过构造方法注入Retrofit Api接口实例,并且通过Kotlin代理特性,实现无代码体的Model类。
4.兼容纯Java项目。
框架依赖:
1.在项目根目录下的build.gradle文件中添加以下代码,如果已存在,则忽略此步。
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
2.在相应的模块的build.gradle文件中的适当位置添加以下代码:
implementation 'com.github.xiazunyang.brick:brick:1.2.1'
框架使用:
1.在自己项目中的Activity和Fragment的基类中,实现IVew接口,如:
abstract class AbstractMVVMActivity : AppCompatActivity(), IView {
override fun showLoading(message: String, isCancelable: Boolean) {
//在此处显示等待动画
}
override fun hideLoading() {
//在此处关闭等待动画
}
}
IView接口继承了LifecycleOwner接口,而AppCompatActivity又是继承自ComponentActivity,而ComponentActivity是实现了LifecycleOwner接口的,所以我们不需要实现getLifecycle方法。
也正是IView接口继承了LifecycleOwner接口,我们才可以在ViewModel中感知View的生命周期。
2.在自己的Application的onCreate方法中初始化框架:
override fun onCreate() {
super.onCreate()
//传入Retrofit或实现了IRetrofit接口的实例
installRetrofit(Retrofit or IRetrofit)
}
3.创建一个业务的View接口,要求实现IVew接口,并添加若干与业务相关的抽象方法,如:
interface MainView : IView {
fun onLoadWeChatAuthorsFailure(throwable: Throwable)
}
4.创建一个业务的Model类。并创建Retrofit Api接口,如:
class MainModel {
suspend fun getWeChatAuthor(): JsonResult> {
//Retrofit访问网络的代码
}
}
//Retrofit Api接口
interface MainApi {
@GET("wxarticle/chapters/json")
suspend fun getWeChatAuthor(): JsonResult>
}
通过以上代码可以发现,Model中的方法与Retrofit Api中的方法完全一致,该方法中只做了使用Retrofit创建Api接口实例的操作,我们可以使用Kotlin代理的特性,将Model中的该方法省略掉:
class MainModel(mainApi: MainApi) : MainApi by mainApi
interface MainApi {
@GET("wxarticle/chapters/json")
suspend fun getWeChatAuthor(): JsonResult>
}
这样一来,MainModel就直接继承了MainApi中的方法,你只需要把Retrofit Api的接口作为Model的构造方法的参数传入就可以了。框架会调用第2个步骤中设置的Retrofit或网络工具类来创建Retrofit Api的实例,并调用Model的构造方法来完成Model创建工作。
另外需要说明的是,如果你的网络请求框架并不是Retrofit,也不理解为什么要创建MainApi接口,你可以省略第2个步骤中的初始化操作,也可以省略这个步骤中的MainApi接口,但是你的Model角色必需拥有一个没有参数的构造方法(不写任何关于构造方法的代码就可以了),框架会通过这个构造方法来创建Model的实例。
不使用Retrofit Api注入功能的Model类:
class MainModel {
fun getWeChatAuthors(): List {
//请求网络的代码
}
}
5.创建一个业务相关的ViewModel类,要求继承自AbstractViewModel,AbstractViewModel要求接收两个泛型参数,分别传入第3步和第4步创建的View接口和Model类。框架中提供了两个AbstractViewModel,一个在com.numeon.brick包下,一个在com.numeon.brick.coroutine包下,它们唯一的区别就是后者实现了CoroutineScope接口,如果你需要使用Kotlin协程的话,就使用com.numeon.brick.coroutine包下的AbstractViewModel。如:
class MainViewModel : AbstractViewModel()
6.在View接口中创建一个对应的ViewModel的属性,然后给一个默认的get方法实现,如:
interface MainView : IView {
//以下两行是新增的代码,createViewModel是框架中提供的扩展方法
val mainViewModel: MainViewModel
get() = createViewModel()
fun onLoadWeChatAuthorsFailure(throwable: Throwable)
}
此处注意,Kotlin中的接口是可以有默认实现的,虽然写作属性,在Kotlin中调用也是属性的方式,但是在编译后其实是编译成了Java中的getMainViewModel方法,你可以Java代码中调用此方法来验证。然后如果是在Java项目中,应该启动jdk1.8,然后为方法加上default关键字,然后才能给一个个默认实现代码。
7.在对应的ViewModel中创建处理业务的方法,如:
class MainViewModel : AbstractViewModel() {
//用于保存结果数据的LiveData
val weChatAuthorLiveData = MutableLiveData>()
//业务代码示例
fun getWeChatAuthors() {
launch {
//通知View显示等待框
view.showLoading()
try {
//切换线程获取网络上的数据,并将结果放入weChatAuthorLiveData中
weChatAuthorLiveData.value = model.getWeChatAuthor().result
} catch (e: Exception) {
e.printStackTrace()
//当获取数据失败时,通知View
view.onLoadWeChatAuthorsFailure(e)
}
//通知View关闭等待框并向View层传递结果
view.hideLoading()
}
}
}
8.创建一个Activity,实现业务对应的View接口,在以下示例中,我们可以访问到MainView中的mainViewModel属性,并调用其方法:
class MainActivity : AbstractMVVMActivity(), MainView {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//监听LiveData来获取数据
mainViewModel.weChatAuthorLiveData.observe(this, Observer { list ->
Log.d("MainActivity", "获取到的数据:$list")
})
//调用mainViewModel中的方法来获取数据
mainViewModel.getWeChatAuthors()
}
override fun onLoadWeChatAuthorsFailure(throwable: Throwable) {
Log.e("MainActivity", "获取数据时发生了错误", throwable)
}
}
以上,就是此框架最基本的使用了,使用起来相当简单,你不需要关心太多东西,只需要按照约定的格式定义完MVP、MVVM中的各个角色类,就可以了。
1.建议允许Model被继承,这样可以减少Model之间复用的代码量。
2.如果使用继承无法满足需求,或者不想使用继承实现,框架提供了createModel方法,用于创建Model的实例。
3.因为View是接口,Activity和Fragment可以实现多个View接口,由此提升了业务代码的复用性。
最后,只要项目启用了dataBinding,你的项目就是一个完全体的MVVM架构了。
Kotlin示例及框架源码:https://github.com/xiazunyang/brick
纯Java项目也能用,只是少了by关键字,Model对Retrofit Api的调用不能像Kotlin那么方便了。
查看纯Java示例:https://github.com/xiazunyang/BrickJavaDemo
最后,感谢WanAndroid提供的网络接口。