摘要:先说一下粒度太小,如果多个Activity有相同的回调方法,我把他做成单独的接口,比如我的页面有网络请求中、请求失败、登录、上传图片等可以复用的接口方法,那我就需要实现多个接口,这样会造成接口太多。第1,MVP充斥着大量的接口,你的model、view、presenter都需要写接口,这个任务量是很繁重的,而且类的数量会很快膨胀。

code小生 一个专注大前端领域的技术平台 公众号回复 Android 加入安卓技术群

作者:唠嗑008

链接:https://www.jianshu.com/p/3651917c9b38

声明:本文已获 唠嗑008 授权发表,转发等请联系原作者授权

前言

相信很多同学对MVP和mvvm都玩的很6了,但本文还是想从2个框架的特性、优缺点来深层次解析一下,帮助大家更好的理解框架。本文有深度,也有故事,下面开车。

MVP

image

这里引用官方的一张图来简单介绍MVP模式,可以看出Model层是真正处理数据的,Presenter是联系M和V的中介,P持有M和V的引用,P和V是双向引用。

看一个最基础的MVP

##Model
class LoginModel : BaseModel() {
    fun login(userName: String, pwd: String): Int {
        //...省略网络请求
        return 1
    }
}

##Presenter
class LoginPresenter(var iLoginView: ILoginView) :
    BasePresenter<LoginModel, ILoginView>(iLoginView) {
    init {
        mModel = LoginModel()
    }

    fun login(userName: String, pwd: String) {
        var loginResult = mModel?.login(userName, pwd)
        iLoginView.loginResult(loginResult == 1)
    }
}

##View
class MVPActivity : BaseMVPActivity<LoginModel,ILoginView>(),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<Presenter> {

    }


    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<M : BaseModel, V : IBaseView>(view: V) {

    var iView: WeakReference<V>? = null

    init {
        iView = WeakReference(view)
    }

    fun onDestroy() {
        iView?.clear()
        iView = null
    }
}

MVVM

说一段历史

现在网上仍然充斥着大量不规范的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<String>()

    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<String> {
            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

相关文章