面试问到烂的MVC、MVP以及MVVM

浩浩
• 阅读 1484

MVP

什么是MVP?

在了解MVP之前可以先观察MVC的架构模式。

面试问到烂的MVC、MVP以及MVVM

MVC中三个组成部分:1. View,即视图中的各个控件;2. Controller,即Activity、Fragment;3. Model,即数据源。

但是日常开发中能够发现,对View层的控制也是在Activity中,这时引入Model层数据源的获取再与Controller层发生交互时,不难发现MVC三层互相都存在持有关系,也就产生了严重的耦合。

面试问到烂的MVC、MVP以及MVVM

图片

而MVP的架构实现就是将控制层下移,View层充当Activity、Fragment的存在,Model层保持原样作为数据源的获取层存在,而View和Model层的通信通过中间人Presenter来完成数据的传递,通过这样的方式达到了解耦的目的。

通信的方式就是互相持有,但中间人对于View层的持有使用弱引用的方式实现,以保证View的及时释放。

内存泄漏

解耦的思想在上面已经有所表述,但解耦的背后还有一个我们非常关注的点 --内存泄漏。这个小模块可以分为两个问题进行阐述:1. 什么是内存泄漏?;2. 使用MVP框架能不能帮我们解决内存泄漏的问题?

什么是内存泄漏?

想来这也是老生常谈的问题了,简单了说原本该释放的东西最后竟然没有释放掉,而引起问题可能是一个变量、一个任务等等。

使用MVP框架能不能帮我们解决内存泄漏的问题?

其实这个问题我们应该这样去进行发问MVP框架能不能帮我们解决View层内存泄漏的问题?如果使用标题的问题,其实这算是一个错误的命题,

那是否能够解决这样的问题呢?可以通过一个非常简单的方法直接进行验证。下面是一段代码示例,一个简单的异步线程延迟任务。

new Thread(new Runnable() {
    @Override
    public void run() {
        SystemClock.sleep(200000);
    }
}).start(); 

通过Android Studio集成的Profiler能力,在运行期间就可以直接分析内存信息。

面试问到烂的MVC、MVP以及MVVM

图片

面试问到烂的MVC、MVP以及MVVM

图片

其中有一个打了红框的按钮,点击后可以打印出一段时间的内存分配情况。 你同样可以直接点击对某个时间点进行分析。

这是有两个信息我们去进行关注,View层、Prensenter层、Model层所占用的内存大小。

  1. View层

面试问到烂的MVC、MVP以及MVVM

图片

  1. Presenter层和Model层

面试问到烂的MVC、MVP以及MVVM

图片

上述的Presenter层中已经开启了异步线程,能够明显发现View层所占用的内存明显大于Presenter层和Model层。而如果这个时候使得让我去选择内存泄漏的类,最后的选择肯定是倾向是少的一方,而MVP给我们带来了选择的空间,这也是MVP架构下为我们带来的一大好处。

这里也得出了一个标题的结论,MVP架构能够缓解内存泄漏问题,但不能解决它。

手撸一个MVP架构

在MVP架构中,我们会存在两种代码风格,M层或P层做复杂的逻辑处理,选择其中一层做复杂的逻辑处理,在这里我更喜欢这些事情由M层负责完成。

M-V-P

首先是Presenter层,这一层作为中间人,和Model层以及View层同时存在通信,也就需要对两者同时进行持有,另外为了能够在View层销毁不用时,Presenter层能够不发生内存泄漏问题,对于View层引用方法采用的是弱引用的方式书写。

abstract class BaseMvpPresenter<V: IMvpView, M: IMvpModel> : IMvpPresenter {
    private var vWeakReference: WeakReference<V>? = null
    protected val model: M by lazy { createModel() } 
    override fun bindView(mvpView: IMvpView) {
        vWeakReference = WeakReference(mvpView as V)
    }
    override fun unBindView() {
        if (vWeakReference != null) {
            vWeakReference?.clear()
            vWeakReference = null
        }
    }
    fun getView(): V? = vWeakReference?.get()
    abstract fun createModel(): M
} 

接下来是Model层,这一层是数据源的存在,而数据源的获取方法都是用户自定义,这里Model层只需要在持有Presenter层的前提下做能力预留即可。

abstract class BaseMvpModel<P: IMvpPresenter> (val p: P): IMvpModel 

最后是View层,更具体一点就是Activity、Fragment这些类,一个同样避不开的话题就是持有,View层同样需要先对Presenter层进行持有,也就有了如下的初版代码。

abstract class BaseMvpActivity<P : BaseMvpPresenter<*, *>> : AppCompatActivity(), IMvpView {
    protected var p: P? = null

    abstract fun getPresenter(): P
} 

但是需要思考的一个问题Activity、Fragment什么时候应该和Presenter层发生通信呢?为了能够适应全生命周期的变化,自然最后的考虑就是Activity的onCreate()和onDestroy(),Fragment的onAttach()和onDetach()方法中了。

abstract class BaseMvpActivity<P : BaseMvpPresenter<*, *>> : AppCompatActivity(), IMvpView {
    protected var p: P? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        p = getPresenter()
        p?.bindView(this)
    }
    override fun onDestroy() {
        super.onDestroy()
        p?.unBindView()
    }
    abstract fun getPresenter(): P
} 

abstract class BaseMvpFragment<P : BaseMvpPresenter<*, *>> : Fragment(), IMvpView {
    protected var p: P? = null
    override fun onAttach(context: Context) {
        super.onAttach(context)
        p = getPresenter()
        p?.bindView(this)
    }
    override fun onDetach() {
        super.onDetach()
        p?.unBindView()
    }
    abstract fun getPresenter(): P
} 

完成以上的一系列步骤,其实已经完成了整个架构的构建,但是如果直接继承去玩这套框架的时候是不是感觉还欠缺了什么东西?通信

建立通信

在刚刚全部代码基础上,不论Model层、Presenter层还是View层都已经做好了最基础的事情,就是持有。但是通信一定需要持有,持有不一定能够通信。显然在现有的代码基础上,通信设施是当务之急。

这也就引出了新的通信层Contract,当然它的本名应该说是协议层,就像TCP / UDP啥的,在不同的层次之间引出了这样一个接口类,他负责的事情就是MVP三层的通信是什么样的。以下便是一段协议层的示例:

interface MainContract {
    interface Model {
        fun execute()
    }
    interface View<T: IMvpModel> {
        fun handleResponse(data: T)
    }
    interface Presenter<T: IMvpModel> {
        fun request()
        fun response(data: T)
    }
} 

通过在不同的层次引入这些接口,并完成其具体实现,最后就实现了一套完整的MVP架构。

MVVM

面试问到烂的MVC、MVP以及MVVM

MVVM架构其实和MVP架构整体上相似,但是ViewModel层和View层属于双向通信,使用了DataBinding的能力,使得ViewModel的生成、与View层的绑定完全由系统直接完成简化了开发的流程。但是从设计上出发的时候,MVP更有利于我们对于整体架构的理解。

入门MVVM

  1. 能力引入

android {
  dataBinding {
        enabled true
    }
} 

使用Kotlin编程的开发者需要引入kotlin-kapt

  1. 使用

使用方面可以分为两个小部分:布局使用、绑定使用

  • 布局使用

在布局使用中和平常的XML编写会有一定的出入,需要使用进行第一层的包裹,而其中的代码编写又可以分为数据区布局区两个部分。布局区和平常的书写方式保持一致,重点关注数据区,他需要以做第一层包裹,用于标示数据区,是对变量的定义,其中标签是定义该变量变量名,标签是定义该变量的类型。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="user"
            type="com.clericyi.android.helper.LoginModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.responseCode}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout> 
  • 绑定使用

完成布局绑定以后需要Sync,这一套能力实现和ButterKnife一样都会产生一个新的Binding文件,但是这个Binding拥有更为强大的数据绑定能力。另外这个Binding文件的命名是和XML文件保持一致的,比如activity_main.xml => ActivityMainBinding,activity_main_1.xml => ActivityMain1Binding。

val ac = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
ac.user = p?.let { LoginModel(it, "200") } 

以上一套非常简单的代码过后就已经完成了这套生态下的大部分事项,从开发成本上来讲是优于MVP架构的。

学习最忌盲目,无计划,零碎的知识点无法串成系统。学到哪,忘到哪,面试想不起来。这里我整理了Android面试中最常问的核心知识点以及Android架构、Flutter、KT、framework中最核心的几块知识,分享在我的【Github】,如有面试需要的朋友欢迎前往阅读;

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这