谈一谈我对mvp框架的理解

栏目: Android · 发布时间: 6年前

内容简介:我是最近才开始写Android文章,暂时不知道该写些什么东西。外加上一位朋友好像对mvp有点疑问。我本不想一开始就写这个,但是我又不耐烦的去给他讲什么mvp,mvp该怎么写。我想了一下,与其一点一点告诉他什么是mvp,还不如写下一篇文章来分享我关于MVP的一些理解。首先,在我的观点里面,阅读该源码是需要有一点Android的开发经验的。如果你只是一个初学者或者是没有基础的小伙子,我奉劝你别花费时间来阅读我这篇文章,可能对你的发展并没有多大的作用。然后谈到框架,其实首先映入眼帘的应该是

我是最近才开始写Android文章,暂时不知道该写些什么东西。外加上一位朋友好像对mvp有点疑问。我本不想一开始就写这个,但是我又不耐烦的去给他讲什么mvp,mvp该怎么写。我想了一下,与其一点一点告诉他什么是mvp,还不如写下一篇文章来分享我关于MVP的一些理解。

说在前面

首先,在我的观点里面,阅读该源码是需要有一点Android的开发经验的。如果你只是一个初学者或者是没有基础的小伙子,我奉劝你别花费时间来阅读我这篇文章,可能对你的发展并没有多大的作用。

然后谈到框架,其实首先映入眼帘的应该是 mvc框架 ,这是最早在学习 java 的时候常见的。m是model层,v是view层、c是control层。这篇文章呢?我希望由mvc的概念讲起、延伸至mvp的概念,然后再简单的写一个mvp的demo、到最后实际来封装出一个在我掌控之内的mvp框架。结尾希望能结合我的一些开发经验谈一谈mvp的优劣势。

一、 mvc

首先mvc框架共分为三层。m是实体层用来组装数据的;v是视图层用来显示数据的;c是控制层用来分发用户的操作给视图层。总的来说,基本的流程应该是下图:

谈一谈我对mvp框架的理解

简单的来说mvc的运行流程就是:用户通过控制层去分发操作到实体层去组装数据,最后将数据展示到视图层的过程。

如果按照Android如今的分法的话,原本的实体层里面就应该还是实体层,然后fragment/activity里面就会富含生命周期、业务逻辑、视图的操作等等。这样做的好处呢?是代码量比较统一,易于查找。

但是当业务逻辑比较复杂的时候呢?就会出现代码量比较庞大,我甚至在之前的一个项目内看到了将近2000行的一个activity。当时我惊了个呆。由于刚接触那个项目,我调试、log等等一系列操作都用上了,硬是用了三天才搞清楚代码的流程。

作为一个有追求的程序员,也为了成为一个有责任心的程序员。我建议你看一看mvp。

二、 mvp

前面谈到在mvc里面,业务逻辑层和视图都会放在activity/fragment里面进行操作,并且本身activity就需要维护自己的生命周期。这会导致activity/fragment里面代码的臃肿,减少代码的可读性和代码的可维护性。

在我看来mvp框架其实是mvc框架变种产品。讲原本的activity/fragment的层次划分成present层和view层。m还是原来的实体层用来组装数据,p层则用来隔离view层,被称为中介层,v层还是view层主要用来展示数据的层。如下图所示:

谈一谈我对mvp框架的理解

有了present层之后呢?view层就专心在activity/fragment里面主要去处理视图层和维护自己的生命周期,将业务逻辑委托给present层,present层作为实体层和视图层的中介。实体层和视图层不直接进行交互,而是通过委托给persent层进行交互,这样做的好处是:

  • 分离了视图逻辑和业务逻辑,降低了耦合
  • Activity只处理生命周期的任务,代码变得更加简洁
  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
  • Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
  • 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM
  • 方便代码的维护和单元测试。

其实说了这么多,都是瞎说

Talk is cheap, let me show you the code!

三、 用mvp简单实现一个实例

我看了很多mvp都在模拟写一个登陆的界面,我也就来简单的模拟一个登陆的界面吧。

activity_main的代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">

    <android.support.constraint.Group
        android:id="@+id/login_group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="visible"
        app:constraint_referenced_ids="edit_username,edit_password,guide_view,login_btn,clear_btn" />

    <EditText
        android:id="@+id/edit_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入账号"
        android:inputType="text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/edit_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:hint="请输入密码"
        android:inputType="textPassword"
        app:layout_constraintStart_toStartOf="@id/edit_username"
        app:layout_constraintTop_toBottomOf="@id/edit_username" />


    <android.support.constraint.Guideline
        android:id="@+id/guide_view"
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

    <Button
        android:id="@+id/login_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="10dp"
        android:layout_marginTop="20dp"
        android:text="登陆"
        app:layout_constraintEnd_toStartOf="@id/guide_view"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintTop_toBottomOf="@id/edit_password" />

    <Button
        android:id="@+id/clear_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:text="重置"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@id/guide_view"
        app:layout_constraintTop_toTopOf="@id/login_btn" />

    <android.support.v4.widget.ContentLoadingProgressBar
        android:id="@+id/progress_bar"
        style="?android:attr/progressBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="parent"
        app:layout_constraintStart_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />

    <Button
        android:id="@+id/retry_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="重试"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="parent"
        app:layout_constraintStart_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />

    <TextView
        android:id="@+id/login_success_tips"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="登陆成功!!"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="parent"
        app:layout_constraintStart_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />


</android.support.constraint.ConstraintLayout>
复制代码

说明一下 :我里面用到了很多 ConstraintLayout 的新属性,如果你对这个有疑问,请翻阅我之前的文章 ConstraintLayout用法详解 .

MainActivity的代码(视图层):

class MainActivity : AppCompatActivity(), IView, View.OnClickListener {


    private var persent: IPresent? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        persent = MainPresent(this)

        login_btn.setOnClickListener(this)

        clear_btn.setOnClickListener(this)

        retry_btn.setOnClickListener(this)

    }

    override fun onClick(view: View?) {
        when (view?.id) {
            R.id.login_btn -> {
                persent?.checkFrom(edit_username.text.toString(),
                        edit_password.text.toString())

            }
            R.id.clear_btn -> {
                edit_username.setText("")
                edit_password.setText("")
            }
            R.id.retry_btn -> {
                retry_btn.visibility = View.GONE
                persent?.checkFrom(edit_username.text.toString(),
                        edit_password.text.toString())
            }
        }
    }

    override fun errorShowTips(tips: String) {
        toast(tips)
    }

    override fun onSubmit() {
        login_group.visibility = View.INVISIBLE
        progress_bar.visibility = View.VISIBLE
    }

    override fun showResult(loginSuccess: Boolean) {
        progress_bar.visibility = View.GONE

        if (loginSuccess) {
            login_success_tips.visibility = View.VISIBLE
        } else {
            retry_btn.visibility = View.VISIBLE
        }
    }
}
复制代码

MainModel的代码(实体层):

class MainModel : IModel{

    // 模拟请求数据
    override fun login(username: String, password: String): Observable<Boolean> {
        return Observable.just(true)
    }
}
复制代码

MainPresent的代码(中介层):

class MainPresent(view: IView) : IPresent {
    private var view: IView? = null
    private var model: IModel? = null

    init {
        model = MainModel()
        this.view = view
    }

    override fun checkFrom(username: String, password: String) {
        if (username.isEmpty()) {
            view?.errorShowTips("请输入用户名")
            return
        }
        if (password.isBlank()) {
            view?.errorShowTips("请输入密码")
            return
        }
        view?.onSubmit()

        // 模拟一下网络加载的延时
        model?.run {
            login(username = username, password = password)
                    .delay(2, TimeUnit.SECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeBy(
                            onNext = {
                                view?.showResult(it)
                            },
                            onError = {
                                view?.showResult(false)
                            }
                    )
        }
    }
}
复制代码

IFeature的代码(封装接口):

interface IView {
    
    fun errorShowTips(tips:String)

    fun onSubmit()

    fun showResult(loginSuccess: Boolean)

}

interface IPresent {
    
    fun checkFrom(username:String,password:String)

}

interface IModel {

    fun login(username:String,password: String): Observable<Boolean>

}
复制代码

四、 重新封装一下mvp

如果你是一个对自己的要求非常高的程序员,你会尽量去优化重复的代码。如果你对上面的代码已经纯熟了之后,你会发现:我们每次都会写想同的代码。好处就是增加了你对代码层面的熟悉程度,但是坏处就会造成大量的代码冗余。

所以此时我们就需要一个抽取想同的代码进行封装操作。当然我的经历也不算太过丰富,可能代码考虑面没有那么全。如果存在疑虑的呢?可以进行讨论、完善。

基类:IFeature.kt

interface IModel {

    fun onAttach()

    fun onDetach()
}

interface IView

interface IPresenter<in V : IView, in M : IModel> {

    fun attach(view: V?, model: M?)

    fun onResume()

    fun onPause()

    fun detach()

    fun isAttached(): Boolean
}

复制代码

Presenter:

abstract class Presenter : PresenterLifecycle, PresenterLifecycleOwner {

    protected open var mContext: Context? = null
    /**
     * mHandler is main thread handler
     */
    protected val mHandler: Handler = Handler(Looper.getMainLooper())
    /**
     * currentState is current present lifecycle state
     */
    override var currentState: Event = Event.DETACH
    /**
     * mOnAttachStateChangedListeners contains listeners object who would be notified when this presenter's lifecycle changed
     */
    private val mOnAttachStateChangedListeners: FastSafeIterableMap<OnAttachStateChangedListener, Unit> = FastSafeIterableMap()
    /**
     * isAttached is true after presenter has been invoked [onAttach]
     */
    protected var mIsAttached: Boolean = false
    /**
     * isPaused is true when presenter's lifecycle is ON_PAUSE
     */
    protected var mIsPaused: Boolean = false


    open fun onAttach(context: Context) {
        mContext = context
        mIsAttached = true
        currentState = Event.ATTACH
        synchronized(this) {
            mOnAttachStateChangedListeners.forEach { (listener, _) ->
                listener.onStateChanged(this, Event.ATTACH)
            }
        }
    }

    open fun onResume() {
        mIsPaused = false
        currentState = PresenterLifecycle.Event.ON_RESUME
        synchronized(this) {
            mOnAttachStateChangedListeners.forEach { (listener, _) ->
                listener.onStateChanged(this, Event.ON_RESUME)
            }
        }
    }

    open fun onPause() {
        mIsPaused = true
        currentState = PresenterLifecycle.Event.ON_PAUSE
        synchronized(this) {
            mOnAttachStateChangedListeners.forEach { (listener, _) ->
                listener.onStateChanged(this, Event.ON_PAUSE)
            }
        }
    }

    open fun onDetach() {
        mIsAttached = false
        currentState = PresenterLifecycle.Event.DETACH
        synchronized(this) {
            mOnAttachStateChangedListeners.forEach { (listener, _) ->
                listener.onStateChanged(this, Event.DETACH)
                mOnAttachStateChangedListeners.remove(listener)
            }
        }
    }

    override fun addOnAttachStateChangedListener(listener: PresenterLifecycle.OnAttachStateChangedListener) {
        synchronized(this) {
            mOnAttachStateChangedListeners.putIfAbsent(listener, Unit)
        }
    }

    override fun removeOnAttachStateChangesListener(listener: PresenterLifecycle.OnAttachStateChangedListener) {
        synchronized(this) {
            mOnAttachStateChangedListeners.remove(listener)
        }
    }

    override fun getLifecycle(): PresenterLifecycle {
        return this
    }

}
复制代码

PresenterLifecycle

interface PresenterLifecycle {

    var currentState: Event

    fun addOnAttachStateChangedListener(listener: OnAttachStateChangedListener)

    fun removeOnAttachStateChangesListener(listener: OnAttachStateChangedListener)

    interface OnAttachStateChangedListener {
        fun onStateChanged(presenter: Presenter, event: Event)
    }

    enum class Event {
        ATTACH, ON_RESUME, ON_PAUSE, DETACH
    }
}
复制代码

VMpresent:

abstract class VMPresenter<V : IView, M : IModel>(val context: Context) : Presenter(), IPresenter<V, M> {

    /**
     * viewRef is weak reference of view object
     */
    private var viewRef: WeakReference<V>? = null
    /**
     * modelRef is weak reference of model object
     */
    private var modelRef: WeakReference<M>? = null
    /**
     * Convenient property for accessing view object
     */
    protected val view: V?
        get() = viewRef?.get()
    /**
     * Convenient property for access model object
     */
    protected val model: M?
        get() = modelRef?.get()
    /**
     * isPaused is true when presenter's lifecycle is ON_PAUSE
     */
    protected val isPaused: Boolean
        get() = mIsPaused


    override fun attach(view: V?, model: M?) {
        super.onAttach(context)
        viewRef = if (view != null) WeakReference(view) else null
        modelRef = if (model != null) WeakReference(model) else null
    }

    override fun detach() {
        super.onDetach()
        // clear the listeners to avoid strong retain cycle
        modelRef = null
        viewRef = null
    }

    override fun isAttached(): Boolean = mIsAttached

}
复制代码

Model:

abstract class Model(context: Context) : IModel {

    protected val context: Context = context.applicationContext

    override fun onAttach() {}

    override fun onDetach() {}

}
复制代码

以上就是我自己对mvp框架的一个封装,可能还存在着很多的漏洞。

五、 mvp的劣势以及介绍一下mvvm

首先对于mvp的优势,我想我就不用说了。至于mvp的劣势:是需要加入Presenter来作为桥梁协调View和Model,同时也会导致Presenter变得很臃肿,在维护时比较不方便。而且对于每一个Activity,基本上均需要一个对应的Presenter来进行对应。

如果外加上 自己封装的话,这种代码的框架性就会愈发明显。所以我觉得如果不是对逻辑有很大要求的情况之下,没必要使用mvp框架了。

当然除了mvp框架之外,还有mvvm,甚至还有更加出色的mvpvm框架。我在这里呢?就简单介绍一下:

  • MVVM MVVM其实是对MVP的一种改进,他将Presenter替换成了ViewModel,并通过双向的数据绑定来实现视图和数据的交互。也就是说只需要将数据和视图绑定一次之后,那么之后当数据发生改变时就会自动的在UI上刷新而不需要我们自己进行手动刷新。在MVVM中,他尽可能的会简化数据流的走向,使其变得更加简洁明了。示意图如下:

    谈一谈我对mvp框架的理解
  • MVPVM

MVPVM即:Model-View-Presenter-ViewModel。此模式是MVVM和MVP模式的结合体。但是交互模式发生了比较大的变化。

Presenter同时持有View、Model、ViewModel,负责协调三方的之间的交互。

View持有ViewModel。ViewModel是View展示数据的一个映射,两者之间双向绑定: (1)当View的数据发生变化时,View将数据更改同步到ViewModel。比如用户在输入框输入了内容。 (2)View监听ViewModel的数据变化,当ViewModel的数据发生变化时,View根据ViewModel的数据更新UI显示。比如更新来自后端的数据列表。

Presenter持有View,并且View的动作响应传递至Presenter。当收到View的动作响应之后,Presenter通过Model获取后端或者数据库数据,请求参数来自于Presenter持有的ViewModel。

当Model请求到数据之后,将数据返回给Presenter,Presenter将返回的数据传递到ViewModel,由于View和ViewModel之间的绑定关系,View会根据ViewModel的数据更新UI显示。

谈一谈我对mvp框架的理解

说在最后

说到项目本身呢?我是用的最新的kotlin+anko配合rx的写法,这也是我认为我这篇文章不适合新手学习的原因。首先你能看懂这篇文章呢?可能要对kotlin有一定的了解,然后可能还需要对rx有一定的了解。这能看懂这篇文章。

至于后面的mvvm和mvpvm其实我基本上都只是有些了解,具体的我没有进行深究,如果后面有需要 我也会深究一下这里只是做简单的介绍罢了

我接触kotlin也有一年多了,也写了一个大的项目 对于这个语法有一定的心得,后续我会结合我自己的心得和体会给诸位读者一一讲述出来。好了,时间不早了,对于一个失眠的人,现在已经到极点了。先洗澡睡觉了。


以上所述就是小编给大家介绍的《谈一谈我对mvp框架的理解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Bad Blood

Bad Blood

John Carreyrou / Knopf / 2018-5-21 / USD 27.95

The full inside story of the breathtaking rise and shocking collapse of Theranos, the multibillion-dollar biotech startup, by the prize-winning journalist who first broke the story and pursued it to t......一起来看看 《Bad Blood》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具