美文网首页
Android:MVP架构分析与封装

Android:MVP架构分析与封装

作者: SupKing_a520 | 来源:发表于2020-05-23 12:50 被阅读0次

Github地址:https://github.com/WangFion/mvp-mode

一、MVP 基本概念


  MVP 全称:Model-View-Presenter ;MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
  谈到这里,我们就不得不把它和MVC做一个对比了,首先看下图:


MVC与MVP.png

  对于上图,我们主要做以下几点说明:
  1、MVP 模式下, V 层和 P 层是分开定义的;而 MVC 模式下,V 层和 C 层是混合定义的,通常都在 Activity 或 Fragment 里面,故上图把 V 和 P 圈在了一起。
  2、MVP 模式下,切断了 V 层和 M 层的一个通讯,他们之间的通讯必须经过 P 层的控制,理论上 M 层是不允许持有 P 层的强引用,故 M 层可以做到完全的独立,因此 M 层的表现形式可以是本地 code、jar、so、aar等等。而 MVC 模式下,M 层做不到完全独立,M、V、C 三层之间耦合度较高,很难应对业务的扩展。
  3、MVP 模式下,理论上三层支持 N:N:N 的模式,但是鄙人建议三层之间的对应关系是 1:1:N 的模式,主要是从业务划分和后期维护来看,至于具体的理由各位看官老爷就自行体会了。

  总的来看,MVP 是在 MVC 的基础上进行了一个优化,使得业务逻辑更加清晰,层次分明,降低了模块层次耦合度。当然了也不是没有缺点,像增加文件和接口个数等等,但鄙人认为这点缺点相较于优点来说可以忽略不记哈。

二、MVP 框架结构


mvp框架.png
  首先,左侧贯穿 M、V、P 三层的包括 Application、工具类(Utils、Tools)、实体类(Entitiy、java bean)。Application就不说了整个项目的唯一对象;工具类则是我们定义的一些工具,它和三层中的任何一层都不会有直接的联系,故可以共同使用;而实体类则是数据的承载,用来在三层之间传递数据。
  其次,最上层是我们的 View 层,主要用来显示信息和做人机交互,包括但不限于上图所列项。这一层是直面用户的、给人以真是感受的地方,故在 MVP 模式中此层主要专注于界面的显示和事件的响应而不会关心数据的具体信息以及业务的逻辑处理,所以在这层里面我们看到的是一系列类似 setXXX()、 showXXX()、hideXXX()、onXXXListener() 的接口方法,它会持有一个 Presenter 的引用,将一切的业务逻辑动作都传递给 Presenter 处理。
  紧接着,View层之下的是我们的Presenter层,此层主要是用来做业务逻辑处理和链接 M、V两层。在这层中我们看到的更多的是 switch-case、if-else、for、while、toXXX等等一系列的逻辑处理和数据转换等操作;另外,这层会持有 M、V两个引用,将逻辑和数据处理的结果通个 V 层的引用传递到 View 来展示,通过 M 层的引用来获取逻辑和数据处理过程中需要的数据服务支持。
  最后,最下层是我们的Modle层,此层主要是提供数据服务支持的,这层包括但不限于我上图所列的内容。这层的设计我个人理解的有一个标准:独立于项目,怎么理解这个标准呢,套用毛主席的一句话:M 是项目的一块砖,哪里需要往哪搬,也就是说任何一个项目或者业务模块需要,直接把这个 Model 文件拷贝过去并且不需要做任何额外的工作就能直接使用,并且能正常工作。因此在这层里面异步数据建议通过 callback、message等形式返回,对外部对象的持有一律采用 SoftReference 或者 WeakReference。

三、MVP 架构封装


  上面的内容,简单的给大家介绍了一下关于 MVP 的基本信息,我相信仔细阅读过上面内容的童鞋心里已经有了相关的概念。下面的内容我会给大家分析一下我封装的一个 MVP 框架,从 code 层面让大家对 MVP 进一步深入学习,本框架分 Kotlin 和 Java 两种编程语言,虽然编程语言不一样,但是基本思想和架构是一样的,我这里就不都分析了,下面的内容主要讲解 Kotlin 版本的,Java 版本的大家可以去源码自行查看。


项目结构图.png

  整个项目工程包含三个 Module。app Module 为项目业务模块,在项目开发中用来编写业务逻辑代码,当然了组件化开发模式下 app Module 一般会根据业务拆分出很多个子 Module,这里不是本文重点就不过多分析了。mvp-java 和 mvp-kotlin 是本文分析的重点,也就是我封装的 MVP 框架,所以接下来我就们开始详细的分析 mvp-kotlin Module。

1、接口层

impl.png
  • 首先 Impl.kt 里面定义的是公共接口,内容比较简单大家一看就懂,这里就不详细介绍了。
  • 接下来是 Presenter.kt,详细内容如下:
package com.wf.mvp.kotlin.impl

import com.wf.mvp.kotlin.customize.UiHandler

/**
 * MvpMode -> com.wf.mvp.kotlin.impl -> IPresenter
 * @Author: wf-pc
 * @Date: 2020-05-10 15:24
 * <p>
 * Mvp's p-layer specification, used to limit the interfaces that the presenter must implement.
 */
interface Presenter : Impl {

    fun <U: Ui> attachView(view: U, handler: UiHandler?)

    fun detachView()

}

  此 Presenter 接口是所有 P 层都必须实现的顶层接口,它继承至 Impl 接口。这里定义了两个最重要的接口方法 attachView 和 detachView,这两个方法是用来供 V 层调,使得 V 与 P 相互建立联系,其中 attachView 绑定的 view 则是我们后面会讲到的实现了 Ui 接口的 V 层的实例对象。

  • 最后是 Ui.kt,详细内容如下:
package com.wf.mvp.kotlin.impl

/**
 * MvpMode -> com.wf.mvp.kotlin.impl -> IView
 * @Author: wf-pc
 * @Date: 2020-05-10 15:26
 * <p>
 * Mvp's v-layer specification, used to limit the interfaces that the view must implement.
 */
interface Ui : Impl {

    fun attachPresenter()

    fun detachPresenter()

}

  此 Ui 接口则是所有 V 层都必须实现的顶层接口,同 Presenter 接口一样它亦继承至 Impl 接口。这里定义了两个 attachPresenter 和 detachPresenter接口方法,分别用来绑定和解绑 P 层的实例对象。

  总结,以上便是接口协议层的所有内容,这里规定了 V 层和 P 层的基础协议规则,使得 V 和 P 建立双向联系,后续具体的 View 和 Presenter 实例都是从这里派生出去。到这里你可能会问:为什么没有 M 层的协议接口?如果有这样的疑问那说明没有认真阅读上两节的内容哦,前面我们讲过 M 层是要做到独立于项目,那么它就不需要公共的协议,各自的数据服务实现各自的业务即可,另外 Presenter 下面还可能对应于很多个 M,故 M 层没有统一的接口协议,也不用必须于 P 层建立联系。

2、抽象层 Presenter

abs_presenter.png
  其中 DefaultIPresenter 是一个空实现的 Presenter,没有什么可讲的内容。我们主要给童鞋们分析 IPresenter 的实现。
package com.wf.mvp.kotlin.presenter

import ......

/**
 * MvpMode -> com.wf.mvp.kotlin.presenter -> Presenter
 * @Author: wf-pc
 * @Date: 2020-05-10 16:13
 * <p>
 * Mvp's p-layer basic Presenter, used to attach activity and implement common functions.
 */
abstract class IPresenter<V : Ui> : Presenter {

    /**
     * The instance object of V layer.
     */
    protected var mView: V? = null

    /**
     * The instance object of main thread handler.
     */
    protected var mUiHandler: UiHandler? = null

    /**
     * Establish a reference relationship with the V layer.
     * 
     * @param view The instance object of V layer, must be a subclass of Ui.
     * @param handler The instance object of main thread handler.
     */
    @Suppress("UNCHECKED_CAST")
    override fun <U : Ui> attachView(view: U, handler: UiHandler?) {
        mView = view as V;
        mUiHandler = handler;
        mUiHandler?.attachRefs(this)
        init()
    }

    /**
     * Release references to layer V.
     */
    override fun detachView() {
        release()
        mUiHandler?.detachRefs()
        mUiHandler = null
        mView = null
    }

    /**
     * Only initialization code can be written here, no other operations can be performed.
     * For example, initialize the M layer instance object.
     */
    protected abstract fun init()

    /**
     * Used to release resources.
     */
    protected abstract fun release()

    ......
}

  上面的内容注释都写的挺详细的,IPresenter 抽象类里面持有了两个 V 层的实例对象 mView 和 mUiHandler。

  通过泛型方法 attachView 将 V 层的实例对象和 UI 线程的 Handler 对象传递到 P 层,使得 P 层持有了 V 层的引用,这样 P 层就能通过 mView 对象来调用 V 层的接口方法了。而 detachView 则是用来与 V 层解除联系和释放相关的资源。

  在 attachView 和 detachView 里面分别预留了 init 和 release 抽象方法,子类通过实现这两个接口来处理各自业务具体的初始化和释放工作。

  最后,省略掉的是 Impl 里面定义的公共接口,其中除了 onHandleMessage(msg: Message) 需要 Presenter 自己实现用来处理 Handler 消息以外,其余的全都是通过 mView 去调用了 V 层的实现,其主要目的是方便 P 层使用。

3、抽象层 View

abs_view.png
  此层定义了四个类,其中 N 开头的是指的不需要 P 层的业务模块,例如应用的启动页面等,在 MVP 模式下理论上是不推荐使用的,哪怕是定义一个空的 P 层,故本文我们不做详细介绍。而 IActivity 和 IFragment 的实现逻辑是一样的,只不过一个是针对 Activity,一个是针对 Fragment 而已,所以我们选择分析 IActivity 即可。
package com.wf.mvp.kotlin.view

import ......

/**
 * MvpMode -> com.wf.mvp.kotlin.view -> IActivity
 * @Author: wf-pc
 * @Date: 2020-05-10 16:42
 * <p>
 * Mvp's v-layer basic Activity, used to bind presenter and implement common functions.
 */
abstract class IActivity<P : Presenter> : Activity(), Ui {

    /**
     * The instance object of P layer.
     */
    protected var mPresenter: P? = null

    /**
     * The instance object of UI thread handler.
     */
    protected var mUiHandler: UiHandler? = null

    private var mToast: Toast? = null
    private var mLoading: ProgressDialog? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        beforeCreate()
        super.onCreate(savedInstanceState)
        setContentView(bindLayoutId())
        initView(intent)
        attachPresenter()
        initListener()
        initData()
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        setIntent(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        detachPresenter()
        release()
    }


    /**
     * Binding P layer instance object.
     * Obtain the generic class type through reflection, and then instantiate the Presenter object.
     */
    @Suppress("UNCHECKED_CAST")
    override fun attachPresenter() {
        mUiHandler = UiHandler()
        mUiHandler?.attachRefs(this)
        mPresenter = try {
            val type = this.javaClass.genericSuperclass as ParameterizedType
            val tClass: Class<P> = type.actualTypeArguments[0] as Class<P>
            tClass.newInstance()
        } catch (e: Exception) {
            DefaultIPresenter() as P
        }
        mPresenter?.attachView(this, mUiHandler)
    }

    /**
     * Unbind the P layer instance object.
     */
    override fun detachPresenter() {
        mUiHandler?.detachRefs()
        mPresenter?.detachView()
        mUiHandler = null
        mPresenter = null
    }

    /**
     * Used to do something before Create.
     */
    protected open fun beforeCreate() {}

    /**
     * Used to bind layout files.
     */
    protected abstract fun bindLayoutId(): Int

    /**
     * Used to initialize view.
     */
    protected abstract fun initView(intent: Intent?)

    /**
     * Used to initialize listener.
     */
    protected abstract fun initListener()

    /**
     * Used to initialize data.
     */
    protected abstract fun initData()

    /**
     * Used to release resources.
     */
    protected abstract fun release()

    /**
     * It is not defined as an abstract function here,
     * it is recommended to put the message to the Presenter layer for processing.
     */
    override fun onHandleMessage(msg: Message) {}

    ......
}

  抽象 IActivity 继承至 Activity 和实现了 Ui 接口,代表着 IActivity 拥有了 V 层的控制能力。同样的,IActivity 持有了一个 Presenter 的实例对象和一个 UiHandler 实例对象,在 V 层就是通过这个 mPresenter 来调用 P 层的相关接口处理业务逻辑。

  首先,我们在 onCreate 方法里面进行了功能代码模块的划分,分别预留出了相应的接口来供其子类实现,各个接口是用来做什么的上面也有详细的注释,就不一一翻译了。

  接下来,我们着重分析一下 attachPresenter 和 detachPresenter,不知道大家还记得不?这两个接口是定义在接口层的 Ui 接口里面的。

  • 在 attachPresenter 里面主要干了两个事儿:
    (1)实例化了 UiHandler 对象并将当前对象也就是 this (这里要说明一下:抽象类是不能实例化的,因此是没有 this 对象的,而这里的 this 其实是会向下转型为它的具体的子类对象)传递给它,而在 UiHandler 里面会回调 this 的 onHandleMessage 方法。
    (2)通过反射获取到泛型参数的 class 类型,进而通过 class 实例化了 mPresenter 对象,紧接着调用 mPresenter 的 attachView 将 this 和 mUiHandler 传递给 P 层,从而建立了 P 层和 V 层的绑定关系。其中,当反射出错的时候会默认绑定一个空实现的 DefaultIPresenter。

  • 相应的 detachPresenter 里面也干了两个事儿:
    (1)释放 UiHandler 资源。
    (2)断开 V 层与 P 层的联系。

  最后,onHandleMessage(msg: Message) 有一段特殊的说明:建议消息处理放到 P 层去,故此处采用了空实现。为什么这么建议呢?因为大多数情况下消息也是用来处理业务逻辑的,只有极少数是用来作用于 UI的。其余省略掉的是 Impl 里面定义的公共接口,都很简单,这里不做过多说明。

4、UiHandler

package com.wf.mvp.kotlin.customize

import android.os.Handler
import android.os.Message
import com.wf.mvp.kotlin.impl.Impl

/**
 * MvpMode -> com.wf.mvp.kotlin.customize -> UiHandler
 * @Author: wf-pc
 * @Date: 2020-05-10 15:11
 */
class UiHandler: Handler(){

    private var mRefsList = ArrayList<Impl>()

    fun attachRefs(refs: Impl){
        mRefsList.add(refs)
    }

    fun detachRefs(){
        removeCallbacksAndMessages(null)
        mRefsList.clear()
    }

    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        for (refs in mRefsList){
            refs.onHandleMessage(msg)
        }
    }
}

  UiHandler 是我们实现的一个自定义 Handler,这里有一点需要注意:我们只是取名为 UiHandler,并不是说它一定就是 UI 线程的 Handler。

  其中我们通过一个 List<Impl> 来存储它所持有的外部引用,通过 attachRefs 来添加引用,detachRefs 来释放引用。

  在 handleMessage 方法里面将消息回调给 List<Impl> 里面的引用的 onHandleMessage 方法,因此我们这里的引用都必须是实现了 Impl 接口的对象。


  到这里,我们这个框架的封装就讲完了,整个框架主要是搭建了 V 层和 P 层的联系,由于我们讲过 M 层理论上是要独立于项目,故不对其进行封装。后续我们在项目开发中只需要将 Activity 继承至 IActivity 、Fragment 继承至 IFragment、Presenter 继承至 IPresenter 就可以使用 mViewmPresenter 来进行相互调用了。

四、使用


1、索引 contract

package com.wf.mvp.mode.kotlin.contract

/**
 * MvpMode -> com.wf.mvp.mode.kotlin.contract -> KotlinContract
 *
 * @Author: wf-pc
 * @Date: 2020-05-10 17:55
 */
interface KotlinContract {
    interface View {
        fun setText(text: String?)
        fun setTextColor(color: Int)
    }

    interface Presenter {
        fun initText(text: String?);
        fun getInfo()
    }
}

  contract 翻译过来就是合约的意思,他的功能就类似于我们字典的索引,在我们不熟悉这个模块的时候,我们只需要看这个文件就能大概知道这个模块里面有些什么内容。虽然不是必须的,但是强烈建议加上。

2、V 层 KotlinIActivity

package com.wf.mvp.mode.kotlin.view

import ......

class KotlinIActivity : IActivity<KotlinIPresenter>(), KotlinContract.View {

    override fun bindLayoutId(): Int {
        return R.layout.activity_kotlin
    }

    override fun initView(intent: Intent?) {

    }

    override fun initListener() {
        btn_kotlin.setOnClickListener {
            mPresenter?.getInfo()
        }
    }

    @SuppressLint("SetTextI18n")
    override fun initData() {
        // 获取关联的 P 层的具体 class 类型
        mPresenter?.initText("${mPresenter?.javaClass}")
    }

    override fun release() {
    }

    override fun setText(text: String?) {
        tv_kotlin.text = text ?: "null"
    }

    override fun setTextColor(color: Int) {
        tv_kotlin.setTextColor(color)
    }

}

  KotlinIActivity 为一个简单的 V 层的实现,它实现了 IActivity 和 KotlinContract.View 索引,绑定了 KotlinIPresenter。我们可以看到,除了我们封装时预留的接口外,KotlinIActivity 只有两个 set 方法,这就是我们封装的时候讲到的 V 层只做 UI 的更新和响应,具体的业务逻辑通过 mPresenter 交给 P 层去处理。

3、P 层 KotlinIPresenter

package com.wf.mvp.mode.kotlin.presenter

import ......

/**
 * MvpMode -> com.wf.mvp.mode -> MainPresenter
 *
 * @Author: wf-pc
 * @Date: 2020-05-09 22:06
 */
class KotlinIPresenter : IPresenter<KotlinIActivity>(), KotlinContract.Presenter {

    private var mText: StringBuilder = StringBuilder()

    override fun init() {
    }

    override fun release() {

    }

    override fun onHandleMessage(msg: Message) {
        if (msg.what == 1002) {
            showLoading("1002")
        }
    }

    override fun initText(text: String?) {
        mText.clear()
        mText.append(mView?.javaClass).append("\n")
        mText.append(text).append("\n\n")
        mView?.setText(mText.toString())
    }

    override fun getInfo() {
        mText.append(getResources().toString()).append("\n")
        mText.append(getString(R.string.app_name)).append("\n")
        mText.append(getString(R.string.app_hello, "kotlin")).append("\n")
        mText.append("dimen_720p=${getDimension(R.dimen.dimen_720p)}").append("\n")
        mText.append(getDrawable(R.mipmap.ic_launcher).toString()).append("\n")

        mView?.setText(mText.toString())
        mView?.setTextColor(getColor(R.color.colorPrimary))

        Thread(Runnable {
            try {
                Thread.sleep(3000);
            } catch (e: InterruptedException) {
                e.printStackTrace();
            }

            runOnUiThread {
                showToast("Hello Kotlin !");
            }

            try {
                Thread.sleep(3000);
            } catch (e: InterruptedException) {
                e.printStackTrace();
            }

            mUiHandler?.sendEmptyMessage(1002);

            mUiHandler?.postDelayed({
                hideLoading();
                toggleKeyboard();
            }, 3000)

            mUiHandler?.postDelayed({
                toggleKeyboard();
            }, 6000)

        }).start()
    }
}

  KotlinIPresenter 为一个简单的 P 层的实现,它实现了 IPresenter 和 KotlinContract.Presenter 索引,绑定了 KotlinIActivity。我们可以看到,除了我们封装时预留的接口外,这里面做了很多的数据获取和逻辑操作,最终通过 mView 将结果传递到 V 层显示。

4、M 层实例
  我们说了 M 层是独立于项目的,这里就不做演示实例了,前面的内容已经讲的很清楚了。

总结


  以上内容就是我个人对 MVP 模式的理解和简单的封装,模式或者架构无非是一种规范或者约束,具体的实现仁者见仁智者见智,以上的内容仅为本人的个人见解和思考,有不对的、可以优化的或者更好的建议,欢迎大家评论留言!

Github地址:https://github.com/WangFion/mvp-mode

相关文章

网友评论

      本文标题:Android:MVP架构分析与封装

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