Android--MVP设计模式的理解和总结

本文深入介绍了MVP(Model-View-Presenter)设计模式的概念及其在Android应用开发中的实践。通过一个日记管理应用的例子,展示了如何利用MVP模式进行模块化设计,有效分离关注点并提升代码的可维护性和可测试性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MVP模式的介绍

这里写图片描述

简单介绍

Android中的设计模式很多,MVP是目前比较流行的一种设计模式,全称为Model-View-Presenter。MVP模式能有效地降低View的复杂性,避免业务逻辑被塞入View中,使得View变得更为简单专一。MVP模式会解除View和Model的耦合,同时又带来良好的可拓展性、可测试性,保证系统整洁性、灵活性。对于一个复杂的应用来说,MVP模式是一种良好的架构模式,它可以非常好地组织应用结构,使得应用变得灵活,拥抱变化。

MVP中各自的职责

MVP模式解耦了View和Model,使用Presenter来处理业务逻辑。View层主要用Activity和Fragment来体现,View层主要负责用户交互部分,展示数据等,主要为UI部分,View中持有Presenter实例,通过该实例来调用Presenter层的方法来实现交互。Model层主要对数据的处理,包括获取数据和保存数据等,如:从网络获取数据,从数据库操作数据等。MVP模式中,Model层和View层没有直接联系,各自负责各自的职责。业务逻辑部分由Presenter来负责,如何获取数据,如何展示数据,都是放在Presenter中进行处理,Presenter中持有View和Model的实例。

MVP的好处

MVP模式下,项目中会多出很多类和接口,虽然多出了这些类和接口,但是这样便于我们维护,使用MVP模式来写项目,可以说是面向接口编程,调用的方法基本都是抽象的,直接使用接口来调用,提高可维护性。在MVP模式中,一般我们都会将要调用的方法抽象成接口,Model、View、Presenter接口,在各实现类中,直接对接口进行操作。讲到这里,好像没有说到MVP的好处,接下来说下我使用MVP后的一些感想。
1. 如果哪一天,APP要升级,界面要修改,但是,但是展示的数据源没有变化,这时候,我只需要修改View层的代码,其他代码不需要修改。假设把数据处理业务逻辑都融合到View中的话,修改起来会比较麻烦,改动的代码会很多。
2. 如果哪天,有更好到网络框架出现,你很想换掉原先的网络框架,那么,你原先写的Model层代码就要做相应的修改,这样,你只需要对Model部分进行修改,不需要对Presenter和View进行修改。
3. 假设业务也要该了,那只需要修改Presenter层就行,不需要修改其他两个模块。
总结:使用MVP模式,后面改动代码可以更有针对性、目的性。

MVP模式使用

上面大致提到了,在使用上,Activity和Fragment为主要的View层,在View中,View持有Presenter,并调用Presenter接口来实现一些业务操作。在Presenter中,将持有View和Model实例,从而实现业务逻辑的处理等。Model中主要实现数据处理,一般为异步从网络获取数据等。

那么问题来了!Presenter中持有View实例,也就是说,Presenter中持有Activity等实例,在Presenter中又会调用Model层去进行一些数据处理的耗时操作,有一种情况,当用户调起从网络中获取数据,但是数据还没加载出来,用户退出了该Activity,此时,Model还在进行异步操作,这是,由于Presenter对View是强引用,所以,当Activity结束的时候,该Activity实例还被Presenter持有,此刻将出现内存泄漏问题。所以,为了处理好这种情况,我们要可以对Presenter中对View进行弱引用。

有人可以会问,从上面的关系图中看出,Presenter直接访问Model层,但是Model层中没有对Presenter进行处理,那么怎样做数据交互呢?
一般的,我们自定义一个回调接口,通过回调接口来实现Presenter和Model的交互。

下来简单用一个例子来说明一下MVP模式的使用

为了不让Presenter对View强引用而出现内存泄漏,下面代码我抽象出BasePresenter类和BaseActivity类来实现Presenter对View的若应用,并在Activity的onDestroy()中解除引用。

BasePresenter的编写


import java.lang.ref.Reference;
import java.lang.ref.WeakReference;

/**
 * 对presenter进行抽象
 * 提供View和Presenter进行绑定的操作,使得Presenter对View进行弱引用,避免发生内存泄漏
 * Created by sunxuedian on 2017/9/3.
 */

public abstract class BasePresenter<T> {

    private Reference<T> mViewRef;

    public void attachView(T view){
        mViewRef = new WeakReference<T>(view);//建立关联
    }

    protected T getView(){
        return mViewRef.get();
    }

    public boolean isViewAttached(){
        return mViewRef != null && mViewRef.get()!= null;
    }

    public void detachView(){
        if (mViewRef != null){
            mViewRef.clear();
            mViewRef = null;
        }
    }
}

BaseActivity的编写


import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;

import com.sunxuedian.mvplearn.presenter.BasePresenter;

/**
 * Created by sunxuedian on 2017/9/3.
 */
public abstract class BaseActivity<View, P extends BasePresenter<View>> extends Activity{

    protected P mPresenter;//Presenter对象

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();//创建Presenter
        mPresenter.attachView((View)this);//View和Presenter建立关联
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();//解除View和Presenter的关联
    }

    public abstract P createPresenter();
}

下面的项目我使用一个简单的例子,日记的简单管理(只提供添加和显示所有日记)

先看下项目的目录结构吧

这里写图片描述

Presenter部分

接口部分

/**
 * Created by sunxuedian on 2017/9/3.
 */

public interface IDiaryPresenter {
    void initPresenter();//初始化
    void addDiary();//添加一条日记
    void getAllDiaries();//获取所有日记
}

实现类

import android.text.TextUtils;

import com.sunxuedian.mvplearn.bean.DiaryBean;
import com.sunxuedian.mvplearn.model.IDiaryModel;
import com.sunxuedian.mvplearn.model.impl.DiaryModelImpl;
import com.sunxuedian.mvplearn.presenter.BasePresenter;
import com.sunxuedian.mvplearn.presenter.IDiaryPresenter;
import com.sunxuedian.mvplearn.presenter.callback.IPresenterCallBack;
import com.sunxuedian.mvplearn.view.IDiaryView;

import java.util.List;

/**
 * Created by sunxuedian on 2017/9/3.
 */

public class DiaryPresenterImpl extends BasePresenter<IDiaryView> implements IDiaryPresenter {

    private IDiaryView mView;
    private IDiaryModel mModel;

    @Override
    public void initPresenter() {
        mView = getView();//调用BasePresenter中的方法获取到View
        mModel = new DiaryModelImpl();
    }

    @Override
    public void addDiary() {
        if (TextUtils.isEmpty(mView.getNewDiaryTitle()) || TextUtils.isEmpty(mView.getNewDiaryContent())){
            mView.showFailure("标题和内容不能为空!");
            return;
        }

        final DiaryBean diaryBean = new DiaryBean();
        diaryBean.setTitle(mView.getNewDiaryTitle());
        diaryBean.setContent(mView.getNewDiaryContent());
        diaryBean.setLastEditTime(mView.getNewDiaryCreateTime());

        mView.showLoading();
        mModel.addDiary(diaryBean, new IPresenterCallBack<String>() {
            @Override
            public void onSuccess(String data) {
                mView.hideLoading();
                mView.showAddDiarySuccess(diaryBean);
            }

            @Override
            public void onFailure(String msg) {
                mView.hideLoading();
                mView.showFailure(msg);
            }
        });
    }

    @Override
    public void getAllDiaries() {
        mView.showLoading();
        mModel.getAllDiaries(new IPresenterCallBack<List<DiaryBean>>() {
            @Override
            public void onSuccess(List<DiaryBean> data) {
                mView.hideLoading();
                if (data != null && data.size() > 0){
                    mView.showDiaries(data);
                }else {
                    mView.showFailure("日记为空!请先添加日记!");
                }
            }

            @Override
            public void onFailure(String msg) {
                mView.hideLoading();
                mView.showFailure(msg);
            }
        });
    }
}
这里说明一下,为什么不在构造函数中获取View,因为,在构造函数中获取View的时候,Presenter和View还没关联起来,Presenter和View的关联主要在View中的Activity中的onCreate()中关联,但是关联前,需要通过创建一个Presenter对象,所以这里不能在构造函数中进行获取View。

View层

View层定义的接口

import com.sunxuedian.mvplearn.bean.DiaryBean;

import java.util.Date;
import java.util.List;

/**
 * Created by sunxuedian on 2017/9/3.
 */

public interface IDiaryView {
    void showLoading();//显示进度条
    void hideLoading();//隐藏进度条
    String getNewDiaryTitle();//获取新建日记的标题
    String getNewDiaryContent();//获取日记内容
    Date getNewDiaryCreateTime();//获取日记的创建时间
    void showAddDiarySuccess(DiaryBean diaryBean);//显示添加日记成功
    void showDiaries(List<DiaryBean> diaryBeanList);//显示日记列表
    void showFailure(String msg);//显示操作失败信息
}

Activity实现View接口


import android.app.ProgressDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

import com.sunxuedian.mvplearn.adapter.DiaryListAdapter;
import com.sunxuedian.mvplearn.bean.DiaryBean;
import com.sunxuedian.mvplearn.presenter.IDiaryPresenter;
import com.sunxuedian.mvplearn.presenter.impl.DiaryPresenterImpl;
import com.sunxuedian.mvplearn.view.BaseActivity;
import com.sunxuedian.mvplearn.view.IDiaryView;

import java.util.Date;
import java.util.List;

public class MainActivity extends BaseActivity<IDiaryView, DiaryPresenterImpl> implements IDiaryView {

    private DiaryListAdapter mAdapter;
    private ProgressDialog mProgressDialog;

    private EditText mEtTitle;
    private EditText mEtContent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        mPresenter.initPresenter();//初始化presenter
        mPresenter.getAllDiaries();//调用presenter获取日记列表
    }

    private void initView(){
        //为按钮添加点击事件
        findViewById(R.id.btn_add).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.addDiary();//调用presenter进行添加日记
            }
        });
        findViewById(R.id.btn_show).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.getAllDiaries();//获取日记列表
            }
        });

        //初始化编辑框
        mEtTitle = findViewById(R.id.et_title);
        mEtContent = findViewById(R.id.et_content);

        //为listView绑定adapter
        mAdapter = new DiaryListAdapter(this);
        ListView listView = findViewById(R.id.lv_show);
        listView.setAdapter(mAdapter);

        //初始化进度条对话框
        mProgressDialog = new ProgressDialog(this);
        mProgressDialog.setTitle(null);
        mProgressDialog.setMessage("进行中...");
        mProgressDialog.setCancelable(false);
    }

    @Override
    public DiaryPresenterImpl createPresenter() {
        return new DiaryPresenterImpl();
    }

    @Override
    public void showLoading() {
        if (!mProgressDialog.isShowing()){
            mProgressDialog.show();
        }
    }

    @Override
    public void hideLoading() {
        if (mProgressDialog.isShowing()){
            mProgressDialog.dismiss();
        }
    }

    @Override
    public String getNewDiaryTitle() {
        return mEtTitle.getText().toString().trim();
    }

    @Override
    public String getNewDiaryContent() {
        return mEtContent.getText().toString().trim();
    }

    @Override
    public Date getNewDiaryCreateTime() {
        return new Date(System.currentTimeMillis());
    }

    @Override
    public void showAddDiarySuccess(DiaryBean diaryBean) {
        showToast("添加成功!");
        //将新增的日记显示在列表中
        mAdapter.addItem(diaryBean);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public void showDiaries(List<DiaryBean> diaryBeanList) {
        //显示获取到的列表
        mAdapter.setData(diaryBeanList);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public void showFailure(String msg) {
        showToast(msg);
    }

    private void showToast(String msg){
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
}
可以看出,View层只负责用户交互,代码逻辑清晰

Model层

Model定义的接口

import com.sunxuedian.mvplearn.bean.DiaryBean;
import com.sunxuedian.mvplearn.presenter.callback.IPresenterCallBack;

import java.util.List;

/**
 * Created by sunxuedian on 2017/9/3.
 */

public interface IDiaryModel {
    void addDiary(DiaryBean diaryBean, IPresenterCallBack<String> presenterCallBack);
    void getAllDiaries(IPresenterCallBack<List<DiaryBean>> presenterCallBack);//获取所有日记数据
    boolean isDiaryExist(DiaryBean diaryBean);//判断日记是否存在
}

Model层的实现


import android.os.Handler;

import com.sunxuedian.mvplearn.bean.DiaryBean;
import com.sunxuedian.mvplearn.model.IDiaryModel;
import com.sunxuedian.mvplearn.presenter.callback.IPresenterCallBack;

import java.util.ArrayList;
import java.util.List;

/**
 * 为了方便演示,此处先将数据保存到内存中,不保存到网络或数据库
 * Created by sunxuedian on 2017/9/3.
 */

public class DiaryModelImpl implements IDiaryModel {

    private ArrayList<DiaryBean> mData = new ArrayList<>();
    private Handler mHandler = new Handler();

    @Override
    public void addDiary(final DiaryBean diaryBean, final IPresenterCallBack<String> presenterCallBack) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟网络请求
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (isDiaryExist(diaryBean)){
                            presenterCallBack.onFailure("该标题的日记已经存在!");
                        }else {
                            mData.add(diaryBean);
                            presenterCallBack.onSuccess("添加成功!");
                        }
                    }
                });
            }
        });
        thread.start();
    }

    @Override
    public void getAllDiaries(final IPresenterCallBack<List<DiaryBean>> presenterCallBack) {
        //模拟网络请求
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (mData != null){
                            presenterCallBack.onSuccess(mData);
                        }else {
                            presenterCallBack.onFailure("系统错误");
                        }
                    }
                });
            }
        });
        thread.start();
    }

    @Override
    public boolean isDiaryExist(DiaryBean diaryBean) {
        return mData.contains(diaryBean);
    }

}

再来看看Model和Presenter交互的接口吧

/**
 * Created by sunxuedian on 2017/9/3.
 */

public interface IPresenterCallBack<T> {
    void onSuccess(T data);
    void onFailure(String msg);
}

日记实体类


import java.util.Date;

/**
 * 日记的实体类
 * Created by sunxuedian on 2017/9/3.
 */

public class DiaryBean {

    private String title;
    private String content;
    private Date lastEditTime;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getLastEditTime() {
        return lastEditTime;
    }

    public void setLastEditTime(Date lastEditTime) {
        this.lastEditTime = lastEditTime;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof DiaryBean){
            DiaryBean diaryBean = (DiaryBean) obj;
            return title.equals(diaryBean.getTitle());
        }
        return false;
    }
}

这样一个使用MVP设计模式编写的例子就写好啦,大概的逻辑我再讲解一下

  1. 添加日记,在MainActivity中调用IDiaryPresenter的addDiary()接口,然后如何实现就交给DiaryPresenterImp实体类中的addDiary去实现(通过获取标题内容等构造一个日记对象,然后调用IDiaryModel的addDiary(DiaryBean diaryBean, IPresenterCallBack presenterCallBack))来实现添加日记。
  2. 显示日记列表,在MainActivity中调用IDiaryPresenter的getAllDiaries()接口,然后DiaryPresenterImp通过调用IDiaryModel的获取日记列表接口,但是获取到数据时,将数据传给View去展示。
若有讲得不对或不太理解的,欢迎留言讨论,以上就是我对MVP模式的一个理解。

项目代码:GitHub 项目地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值