Andorid-MVP模式

MVP简介

如果知道MVC模式那么应该清楚,MVC不符合单一模式原则,其V层和C层几乎在一起。
MVP模式则是为了更好的实现解耦

MVP字母分别代表:

  • M (Model) 数据相关
  • V (View) 视图层,针对UI做操作
  • P (Presenter) 纽带层,链接Model和View,实现具体事物

在android开发中:

  1. View层通常是Activity或Fragemnt,view层的作用就是操控ui界面组件,提供友好的界面与用户进行交互。
  2. Model层即数据层。负责对数据的存取操作,数据库的读写,网络数据的请求。
  3. Presenter层作为View层和Model层的桥梁对业务逻辑进行处理。

三者之间关系图示:
在这里插入图片描述
三者通过持有相关的引用关联起来。

MVP实现

MVP最简单实现

目录结构如图:
在这里插入图片描述

BaseView接口
/**
 * @author Chenchangsheng
 * @since 2019/7/28
 */
interface BaseView<T>{
    /**
     * 为view层绑定Presenter
     */
    fun setPresenter(presenter:T)
}
BasePresenter接口
/**
 * @author Chenchangsheng
 * @since 2019/7/28
 *
 * basePresenter接口
 */
interface BasePresenter {
    /**
     * 初始化方法,可以将一些初始化操作放在此方法中
     */
    fun start():Unit
}
MainContract接口
/**
 * @author Chenchangsheng
 * @since 2019/7/28
 *
 * 将Presenter层和View层的接口放在一起可以更清晰的看到它们中都有那些功能,方便后期维护
 */
interface MainContract  {
    /**
     * view层的接口
     *
     * 声明一些ui界面交互的方法
     */
    interface View :BaseView<Presenter>{

    }

    /**
     * presenter层接口
     *
     * 声明一些业务逻辑方法
     */
    interface Presenter : BasePresenter{
    }
}
MainPresenter 类

MainPresenter实现了MainContract.Presenter接口

/**
 * @author Chenchangsheng
 * @since 2019/7/28
 *
 * Main页面的Presenter,业务逻辑的实现
 */
class MainPresenter(view:MainContract.View):MainContract.Presenter{
    /**
     * MainActivity的引用,可以通过view调用MainActivity重写的方法
     */
    private  var mView = view
    init {
        mView.setPresenter(this)
    }
    /**
     * 实现BasePresenter的start方法,可以进行一些初始化操作
     */
    override fun start() {
    }
}
MainActivity
/**
 * @author Chenchangsheng
 * @since 2019/7/28
 * 
 * view层
 */
class MainActivity : AppCompatActivity(),MainContract.View{
    private lateinit var presenter:MainContract.Presenter

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

        /**
         * 这里或许会有疑问,为什么不:
         * var presenter:MainPresenter= MainPresenter(this)
         * 而是直接
         * MainPresenter(this)
         *
         * 仔细看就会发现
         * MainPresenter(this)————调用————>MainPresenter:init{}块的 mView.setPresenter(this)————也就是说会调用————>MainActivity:重写
         * 过的setPresenter(presenter: MainContract.Presenter)方法 (就在下面⬇️)
         *
         * 这样一来 MainActivity就持有了MainPresenter的引用 而MainPresenter也持有了 MainActivity的引用
         *
         * 因此Presenter层和View层就关联了起来
         */
        MainPresenter(this)
    }

    override fun setPresenter(presenter: MainContract.Presenter) {
        this.presenter=presenter
    }
}

梳理

以上代码就是一个很简单的MVP模式结构了,每个部分的作用我也加上注释了。

  • BaseView、BasePresenter 所有View层和Presenter层都需要继承这两个接口,接口中声明了必要的方法
  • MainContract 是一个契约类,该类中有两个内部接口,View、Presenter分别继承了BaseView和BasePresenter。View和Presenter所有的具体方法,都定义在这两个接口中,这样做好处就是view和presenter的方法都集中在一起,一目了然。
  • MainActivity 则是View接口的实现类,View接口所有方法的实现均在此类中
  • MainPresenter 是Presenter接口实现类,实现了Presenter接口相关的方法

引用过程
在MainActivity中通过MainPresenter(this)新建一个MainPresenter实例,并传入当前View对象,使该MainPresenter实例持有View层的引用,因此Presenter可以调用View层的方法。而在MainPresenter的构造函数中(我用的kotlin 则为init块),将会调用view的setPresenter并传入当前的MainPresenter实例,这样一来就想View和Presenter两个层关联起来。

MVP实际上就是将各功能代码分开,通过持有对方引用调用代码而已。

MVP小例子

在上面的MVP简单实现的基础上,做一个小demo,实现一个登陆功能,相当简陋的登陆功能!

效果如下:

在这里插入图片描述

布局文件

在这里插入图片描述
在这里插入图片描述

为MainContract添加方法
    interface View :BaseView<Presenter>{
        /**
         * 登陆成功
         */
        fun onSuccess()
        
        /**
         * 登陆失败
         */
        fun onFail()


    }

    /**
     * presenter层接口
     *
     * 声明一些业务逻辑方法
     */
    interface Presenter : BasePresenter{
        /**
         * 登陆
         */
        fun login(user:String)

    }
实现接口新增方法

MainPresenter类中:


    private  var mView = view
    private lateinit  var mModel:MainModel
    init {
        mView.setPresenter(this)
        mModel = MainModel()
    }
   /**
     * 实现登陆功能
     * 登陆失败 调用view的onFail反之调用onSuccess
     */
    override fun login(user: String) {
        if (mModel.selectUser(user)){
            mView.onSuccess()
        }else{
            mView.onFail()
        }
    }

MainActivity类中:

    override fun setPresenter(presenter: MainContract.Presenter) {
        this.presenter=presenter
    }

    override fun onSuccess() {
        Toast.makeText(this,"登陆成功",Toast.LENGTH_SHORT).show()
    }

    override fun onFail() {
        Toast.makeText(this,"登陆失败",Toast.LENGTH_SHORT).show()
    }


    fun setListener():Unit{
        btMain.setOnClickListener(this)
    }

    override fun onClick(p0: View?) {
        if (p0 != null) {
            if (p0.id==btMain.id){
                presenter.login(etMain.text.toString())
            }

        }
    }
MainModel类中

此类主要进行数据操作

/**
 * @author Chenchangsheng
 * @since 2019/7/28
 *
 * 数据层,主要是针对数据操作,比如获取网络数据,数据库的增删查改等
 */
class MainModel {

    /**
     * 查找指定用户
     *
     * 应该是数据库操作,但是为了方便我写简单一点
     */
    fun selectUser(user:String):Boolean{
        val admin = "admin"
        return admin.equals(user)
    }
}

MVP包结构两种方式

第一种

第一种结构就是分别建立三个4个包:

  • contract
  • model
  • presenter
  • view

如图所示:
在这里插入图片描述

第二种

第二种则是像谷歌官方实例一样

按照每个功能页面建立包

比如:一个login页面建立一个包,里面包括contract、presenter、view

在这里插入图片描述

MVP优缺点

优点

  • 降低耦合度
  • 模块职责划分明显
  • 利于测试驱动开发
  • 代码复用
  • 隐藏数据
  • 代码灵活性

缺点

MVP缺点也十分明显

  • 类太多了 主要表现在每个view都有presenter ,类相对比较多
  • 代码复杂度和学习成本高,在某些场景下presenter的复用会产生接口冗余。
  • Presenter和View相互持有引用,解除不及时的话容易出现内存泄漏。
  • Model进行一步操作的时候,获取结果通过Presenter会传到View的时候,出现View引用的空指针异常。比如说执行网络耗时操作,如果view层已经结束,model才传回数据并且通过presenter操作view,则显然会出现空指针异常。

优化方案

这篇文章我们一步到胃,也说说优化方案。

解决内存泄漏问题

解决方法很简单就是为pasenter添加一个detach()方法,当activity结束时调用此方法解除引用

// 在MainContrct中声明方法
 interface Presenter : BasePresenter{
        /**
         * 解绑view
         */
        fun detach()

    }
    
// 在MainPresenter中实现方法


    override fun detach() {
        this.mView=null
    }

// MainActivity中调用
    override fun onDestroy() {
        super.onDestroy()
        presenter.detach()
    }

注意
1.使用此方法虽然解决了内存泄漏问题,但是可能会引起空指针问题,比如,耗时操作的例子,因此可以考虑增加结束耗时任务的方法,在解除引用的时候调用。
2.如果每个presenter都增加绑定、解除方法那么就十分麻烦,并且增加无用的代码量,因此可以考虑将它们写入BasePresenter中,这样一来每个Presenter实现类都拥有这两个方法

interface BasePresenter {
    /**
     * 初始化方法,可以将一些初始化操作放在此方法中
     */
    fun start():Unit

    /**
     * 绑定view
     * @param view
     */
    fun attach(view: MainContract.View)

    /**
     * 解绑view
     */
    fun detach()
}

未完不想写了 待续

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值