Android MVP && MVVM深度解析

作者: 唠嗑008 | 来源:发表于2020-05-26 18:04 被阅读0次

    前言

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

    MVP

    MVP.png

    这里引用官方的一张图来简单介绍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、优点

    • 将业务逻辑从View层剥离,一定程度实现了解耦;
    • m、v、p 3层都可以复用,p也可以做单独的单元测试;

    从上面的代码可以看出至少2点,第1,Activity/Fragment职责变轻了,代码量也就少了;第2,像登录操作在大多数项目是有多处会用到的,这样的话,就可以把它单独抽出来,下次只需要创建LoginPresenter对象就可以调用登录功能了,总结一下就是那些可以复用的逻辑都可以抽离成单独的MVP。在没有UI界面的时候,你甚至可以单独调试Presenter。

    那MVP模式有没有什么缺点或者不足呢?答案是有的。第1,MVP充斥着大量的接口,你的model、view、presenter都需要写接口,这个任务量是很繁重的,而且类的数量会很快膨胀;第2,内存泄漏问题,比如说,Activity还在做网络请求,用户等不及退出了,由于P持有View的引用,Activity无法及时回收,就发生了内存泄漏;第3,V和P还有一定耦合,如果V层某个UI元素更改,那相关的接口也需要更改,非常麻烦。小结一下:

    3、缺点

    • 接口太多,类膨胀问题;
    • P和V通过接口交互,在业务逻辑比较复杂的时候,接口粒度大小不好控制。粒度太小,就会存在大量接口的情况;粒度太大,解耦效果不好。
    • 内存泄漏和空指针问题。由于P和V是互相引用,如果页面销毁时P还有正在进行的任务,那Activity无法回收,就发生了内存泄漏。
    • P和V需要通过接口交互,还是存在一定耦合,算不上真正的解耦;如果接口有所变化的时候,需要改动的地方太多

    下面针对这些缺点,提出一些解决思路

    接口太多,类膨胀问题
    网上一些方案是引入契约类,就是接口套接口的形式,把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,这种情况下用契约类就比较复杂,不好处理。

    接口粒度和其它
    之前说过Presenter和View交互的接口粒度大小难以控制。比如说,我的页面逻辑比较复杂,需要展示加载的loading、加载失败的状态显示,还需要登录、上传图片等,大家注意没有,这些业务是可以复用的,所以我会把他抽成单独的MVP,但是这样的话,我的Activity就会引入多个View接口,存在大量接口的情况,会使代码太过碎片化;如果我把它写在一起,耦合又会很严重。此外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不再需要接口回调来联系。由于在16,17年Jetpack相关的Viewmodel、LiveData还没有推广开,很多人不太清楚如何把vm的数据传给v(实际上可以自己写观察者模式),于是继续用接口来做,正是由于当时的技术和认知不足以及很多误导博客的广泛传播,导致了一部分人以为MVP+databinding就是mvvm了。

    故事说完了,下面来了解一下MVVM的特性和实现吧。

    mvvm.png

    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,这取决于你的业务,记住一点,框架始终是为业务服务的。欢迎下方留言,说出你的观点。

    感谢以下作者

    Android架构设计---MVP模式第(二)篇,如何减少类爆炸
    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
    Android MVVM架构分析
    从最简单的Android MVP讲起

    相关文章

      网友评论

        本文标题:Android MVP && MVVM深度解析

        本文链接:https://www.haomeiwen.com/subject/vebeahtx.html