37

Android MVP && MVVM深度解析

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ%3D%3D&%3Bmid=2247490373&%3Bidx=1&%3Bsn=998e57055657bb540e1ecdba67bc61e7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

作者:唠嗑008

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

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

前言

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

MVP

e2UbYjR.png!web 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的特性和实现吧。

za2mqma.png!web

图片来自 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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK