code小生 一个专注大前端领域的技术平台公众号回复Android
加入安卓技术群
作者:唠嗑008
链接:https://www.jianshu.com/p/3651917c9b38
声明:本文已获唠嗑008
授权发表,转发等请联系原作者授权
相信很多同学对MVP和mvvm都玩的很6了,但本文还是想从2个框架的特性、优缺点来深层次解析一下,帮助大家更好的理解框架。本文有深度,也有故事,下面开车。
这里引用官方的一张图来简单介绍MVP模式,可以看出Model层是真正处理数据的,Presenter是联系M和V的中介,P持有M和V的引用,P和V是双向引用。
##Model
class LoginModel : BaseModel() {
fun login(userName: String, pwd: String): Int {
//...省略网络请求
return 1
}
}
##Presenter
class LoginPresenter(var iLoginView: ILoginView) :
BasePresenter(iLoginView) {
init {
mModel = LoginModel()
}
fun login(userName: String, pwd: String) {
var loginResult = mModel?.login(userName, pwd)
iLoginView.loginResult(loginResult == 1)
}
}
##View
class MVPActivity : BaseMVPActivity(),ILoginView {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mvp)
loginBtn.setOnClickListener {
(mPresenter as LoginPresenter).login("u1","123")
}
}
override fun initPresenter() {
mPresenter=LoginPresenter(this)
}
override fun loginResult(isSuccess: Boolean) {
TODO("Not yet implemented")
}
override fun showLoading() {
TODO("Not yet implemented")
}
override fun dismissLoading() {
TODO("Not yet implemented")
}
override fun loadFailure() {
TODO("Not yet implemented")
}
}
这个案例非常简单,就是一个登录校验操作,可以看出Activity把业务逻辑都委托给Presenter操作,Activity只需要做发出请求/根据请求结果展示UI即可,从分层思想上来说还是很清晰的。下面先小结一下:
1.核心要点
把数据和业务逻辑从视图层(View)剥离出来,V和P通过接口回调来通信。
2、优点
这种分层思想在一定程度实现了解耦,符合类的单一职责设计原则;
m、v、p 3层都可以复用,p也可以做单独的单元测试;
从上面的代码可以看出至少2点,第1,Activity/Fragment职责变轻了,代码量也就少了;第2,像登录操作在大多数项目是有多处会用到的,这样的话,就可以把它单独抽出来,下次只需要创建LoginPresenter对象就可以调用登录功能了,总结一下就是那些可以复用的逻辑都可以抽离成单独的MVP。在没有UI界面的时候,你甚至可以单独调试Presenter。
那MVP模式有没有什么缺点或者不足呢?答案是有的。第1,MVP充斥着大量的接口,你的model、view、presenter都需要写接口,这个任务量是很繁重的,而且类的数量会很快膨胀;第2,V和P还有一定耦合,如果V层某个UI元素更改,那相关的接口也需要更改,非常麻烦。第3,内存泄漏问题,比如说,Activity还在做网络请求,用户等不及退出了,由于P持有View的引用,Activity无法及时回收,就发生了内存泄漏;小结一下:
3、缺点
接口太多,类膨胀问题;
在业务逻辑比较复杂的时候,接口粒度大小不好控制。粒度太小,就会存在大量接口的情况;粒度太大,解耦效果不好。
P和V需要通过接口交互,还是存在一定耦合,算不上真正的解耦;如果接口有所变化的时候,需要改动的地方太多;
内存泄漏和空指针问题。由于P和V是互相引用,如果页面销毁时P还有正在进行的任务,那Activity无法回收,就发生了内存泄漏。
下面针对这些缺点,提出一些解决思路
接口太多,类膨胀问题
网上一些方案是引入契约类,就是接口套接口的形式,把MVP相关的接口都整合在一起。Google官方的demo确实引入了Contract。
public interface Contract {
interface Model extends BaseModel {
}
interface View extends BaseView {
}
interface Presenter extends BasePresenter {
}
}
但是这样其实是不好的。你可能会觉得这样让逻辑更紧密,代码更好读。但这样做,反而违背了mvp解耦的本质,让代码更加复杂了。此外,在复用的情况,比如一个Presenter有多个Model,一个Activity有多个Presenter,这种情况下用契约类就比较复杂,不好处理。
接口粒度
之前说过接口粒度大小难以控制。先说一下粒度太小,如果多个Activity有相同的回调方法,我把他做成单独的接口,比如我的页面有网络请求中、请求失败、登录、上传图片等可以复用的接口方法,那我就需要实现多个接口,这样会造成接口太多。如果粒度太大,每个接口会有很多方法,就会出现很多重复的内容,也就变得耦合了。
再举个例子,我有一个业务,包含增删改查4个方法,但是A页面只需改查、B页面只需要增删,按接口设计来说,我应该写在一起吧,这样就会造成2个页面都会实现2个空方法,这是粒度太大;如果我把它拆成2个接口吧,接口数量会增加,这是粒度太小。关于接口粒度,目前不好解决。
P和V耦合
此外P和V之间通过接口回调来交互,还是存在耦合的,没有完全实现视图和业务的解耦。如果因为需求变更导致接口有所改动,需要改动的地方太多。总的来说,关于接口问题目前来说是没有完美的解决办法的。
内存泄漏问题
刚才说过内存泄漏是因为P持有V的引用,导致gc来的时候发现m->p->v这条GC引用链存在,就不会回收Activity,于是Activity内存泄漏了。解决思路:在onDestroy()断开引用关系,并取消网络任务。
override fun onDestroy() {
super.onDestroy()
// 防止内存泄漏
mPresenter?.onDestroy()
mPresenter = null
}
class ePresenter{
fun onDestroy() {
//取消网络请求
cancalNetTask()
mView = null
}
}
也可以通过弱引用来解决
class TestPresenter(view: V) {
var iView: WeakReference? = null
init {
iView = WeakReference(view)
}
fun onDestroy() {
iView?.clear()
iView = null
}
}
说一段历史
现在网上仍然充斥着大量不规范的MVVM的文章,百度首页很多都是,其中也包括我在17年写的一篇,所谓不规范是指这些MVVM仅仅是在MVP基础上引入DataBinding,就被当作MVVM模式了。我来解释一下这个情况,mvvm和MVP的区别有2点,第1,vm和v是单向引用;第2,基于观察者模式把数据从vm传给View,v和vm不再需要接口回调来联系。
mvvm其实分为2个阶段,在2017之前,是基于databinding的,在2017之后是基于AAC架构的,也就是livedata、viewmodel相关。由于在16,17年Jetpack相关的Viewmodel、LiveData还没有推广开,在2017之前要把数据从vm传给v是比较麻烦,不用接口回调的话,用观察者模式来做是比较方便的,但是那时候livedata还没有出来,就只能用databinding的观察者模式或自己手写观察者,由于这样做比较麻烦,很多人甚至直接沿用接口回调去更新UI数据。正是由于当时的技术和认知不足以及很多误导博客的广泛传播,导致了一部分人以为MVP+databinding就是mvvm了。
故事说完了,下面来了解一下MVVM的特性和实现吧。
图片来自https://juejin.im/post/5c2f43796fb9a04a04412a18
1、核心要点
数据和UI完全解耦、数据驱动、不存在内存泄漏问题、代码更简洁。可以说解决了MVVM大部分弊端。
2、优点
从设计上解决了内存问题
在MVP中存在内存泄漏问题,需要手动管理,很是麻烦;而MVVM从系统设计上解决了这个问题,开发者再也不需要担心内存问题了。V和VM是单向引用,VM不持有任何View相关的对象,这样就解决了内存泄漏。由于ViewModel和LiveData内部都是通过lifecycle关联生命周期,会在页面正常销毁的时候(onDestory),解除观察者,销毁自身。
数据驱动
数据变化自动更新UI,用户输入和操作需要数据自动更新,可以通过LiveData和DataBinding来完成,二者都是基于观察者模式。
数据和UI完全解耦
数据和业务逻辑都在的ViewModel中,ViewModel只需要关注数据和业务逻辑,完全不需要管UI操作和变化。
更新UI
在子线程操作完数据之后,可以直接更新ViewModel的数据即可,不需要考虑线程切换,因为ViewModel中的LiveData已经帮我们做了这个事情。
mvvm基础
##Model
class NewsModel {
/**
* 模拟加载网络数据
*/
fun loadDataFromNet(): String {
//...省略网络操作
return "this data from net"
}
}
##ViewModel
class NewsViewModel : ViewModel() {
private val mModel by lazy {
NewsModel()
}
val liveData = MutableLiveData()
fun loadData() {
var result = mModel.loadDataFromNet()
//更新数据
liveData.value = result
}
}
##Activity
class MvvmActivity : AppCompatActivity() {
private lateinit var newsVm: NewsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mvvm)
initViewModel()
initLiveData()
}
private fun initViewModel() {
newsVm = NewsViewModel()
newsVm.loadData()
}
private fun initLiveData() {
newsVm.liveData.observe(this, object : Observer {
override fun onChanged(t: String?) {
//更新UI
textView.text = t
}
})
}
}
这是最基础的基于 AAC 方案的 MVVM,没有过度封装。当然你也可以结合 databinding 库来使用。
我还想说明一点,一个项目中你可以同时使用 mvc、MVP、mvvm,这取决于你的业务,记住一点,框架始终是为业务服务的。欢迎下方留言,说出你的观点。
https://www.jianshu.com/p/3a17382d44de
https://blog.csdn.net/qq137722697/article/details/78275882
https://blog.csdn.net/u011033906/article/details/89448350
https://tech.meituan.com/2016/11/11/android-mvvm.html
https://blog.csdn.net/user11223344abc/article/details/82661128
https://www.jianshu.com/p/4736ebe1114b
1 Android 从 MVP 到 MVVM
2 搭建属于自己的Android MVP 框架
3 Android MVP 架构
4 MVVM 最新学习心得
5 Android 从 MVP 到 MVVM
如果你有写博客的好习惯
欢迎投稿
点个在看,小生感恩❤️