专栏名称: klivitamJ
android/前端工程师
目录
相关文章推荐
黄建同学  ·  mark,微调-20250210142341 ·  昨天  
爱可可-爱生活  ·  【[1.2k星] Cloudflare ... ·  昨天  
倪军金融与流动性研究  ·  银行间流动性预计维持中性-银行资负跟踪202 ... ·  昨天  
倪军金融与流动性研究  ·  银行间流动性预计维持中性-银行资负跟踪202 ... ·  昨天  
AI范儿  ·  DeepSeek 平替 99% ... ·  2 天前  
AI范儿  ·  DeepSeek 平替 99% ... ·  2 天前  
宝玉xp  ·  AI 不直接调用工具,程序代码调用 AI ... ·  3 天前  
51好读  ›  专栏  ›  klivitamJ

谈一谈我对mvp框架的理解

klivitamJ  · 掘金  ·  · 2019-05-17 05:57

正文

阅读 43

谈一谈我对mvp框架的理解

我是最近才开始写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是控制层用来分发用户的操作给视图层。总的来说,基本的流程应该是下图:

image.png

简单的来说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层主要用来展示数据的层。如下图所示:

有了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






请到「今天看啥」查看全文