Android——最简单易懂的MVP模式

前言

说起MVP大家现在也肯定不陌生了,当项目越来越复杂,参与的开发人员越多的时候,MVP的优势就体现出来了,他会让代码的逻辑特别的清晰,在维护代码或者我们要在一个Activity中加入新功能的时候,特别的方便。其实Android本身的开发可以说是一个MVC模式,Activity兼顾View和Controller,既绑定xml的布局文件,又要在Activity中处理一些逻辑代码,其实这样不太好,耦合度太高,我们把View和Controller抽离出来变成了View和Presenter,这便是MVP模式。

MVP简单介绍

其实网上对它的介绍已经很多了,我也不说了,一张图足以:


Model:比如在Model层中处理网络请求,注意Model并不是JavaBean,要区分开来

View:就是Activity,只处理简单的UI操作,设置文本值,或者弹个框啦什么的

Presenter:负责处理Model中的数据,并且写一些逻辑的代码

实践

我自己写了一个MVP,假想了一些场景和需求,然后用最接近实战的方式演示一下:

那么我们要实现三个功能:

  1. 发送网络请求得到一个名字,显示在Activity中
  2. 发送网络请求得到一篇文章,显示在Activity中
  3. 存在两个按钮加入群组或者不加入,也是发送网络请求,一个Toast提示“提交成功“或者“提交失败”


这个也是我随机想的一些需求吧,实际的需求可能比较复杂,我们就用这几个非常简单的例子来实现MVP:

1、首先先来Model层,我发送网络请求是用Retorfit,那么写个接口,url都是虚拟的,这里模拟一下

public interface GetMyInfoApi {
    /**
     * 获取我的名字
     * @param myId
     * @return
     */
    @GET("exempt/getName")
    Call<NameBean> getMyName(@Query("authId")String myId);

    /**
     * 获取我的文章
     * @param myId
     * @return
     */
    @GET("exempt/getArticle")
    Call<ArticleBean> getMyArticle(@Query("authId")String myId);

    /**
     * 加入群组,status表示状态:00加入,01不加入
     * @param myId
     * @param status
     * @return
     */
    @POST("exempt/addGroup")
    Call<AddGroupBean> addGroup(@Query("authId") String myId,
                                @Query("status") String status);
}

2、三个bean就不贴出来了,接着写Model,构造方法中实例化,用了个工具类,封装了一下Retrofit的创建过程,三个方法都是发送网络请求,如果在Presenter中调用Model中的方法,只需传入参数和一个callback回调:

public class MainModel {
    private GetMyInfoApi getMyInfoApi;

    public MainModel() {
        getMyInfoApi = RequestUtils.createService(GetMyInfoApi.class);
    }

    /**
     * 获取我的名字
     * @param myId
     * @param callback
     */
    public void getMyName(String myId, Callback<NameBean> callback){
        getMyInfoApi.getMyName(myId).enqueue(callback);
    }

    /**
     * 获取我的文章
     * @param myId
     * @param callback
     */
    public void getMyArticle(String myId, Callback<ArticleBean> callback){
        getMyInfoApi.getMyArticle(myId).enqueue(callback);
    }

    /**
     *   加入群组
     * @param myId
     * @param state
     * @param callback
     */
    public void addGroupBean(String myId, String state, Callback<AddGroupBean> callback){
        getMyInfoApi.addGroup(myId,state).enqueue(callback);
    }
}

3、接下来就是写接口啦,官方推荐的写法是将View和Presenter这两个接口都放到Contract这个类当中,可以看到我们的任务已经非常的清晰啦,这里要继承BasePresenter和BaseView也是之前的项目这么写,在BaseView当中有个setPresenter的方法,将Presenter传入Activity中。其实也可以简单一点,Presenter和View都不用继承基类,然后在Activity中直接new个Presenter出来,然后直接传入this,Presenter的构造方法中得到mView,这样也是可以的,和大家说一下,以免看不懂为何要继承BasePresenter和BaseView,就是一个习惯问题吧,怎么整都是可以的,只要把View和Presenter整的他俩能联系上了就行。

interface MainContract {
    interface Presenter extends BasePresenter{
        /**
         * 发送网络请求得到name
         */
        void getMyName(String myId);
        /**
         * 发送网络请求得到文章内容
         */
        void getMyArticle(String myId);
        /**
         * 加入群组
         */
        void addGroup(String myID,String state);
    }
    interface View extends BaseView<Presenter>{
        /**
         * 显示名称
         */
        void showMyName(String name);
        /**
         * 显示文章
         */
        void showMyArticle(String article);
        /**
         * 显示toast
         */
        void showToast(String msg);
    }
}

4、接下来就是Activity了,让它接上View接口,new一个Presenter出来传入this,调用mPresenter的getMyName()和getMyArticle方法,id是随便写的假的哈哈,点击事件呢调用mPresenter的addGroup()方法,那么就会发送网络请求,然后在Presenter中又会调用View中的show方法来显示数据。

public class MainActivity extends AppCompatActivity implements MainContract.View{
    private TextView tvName;
    private TextView tvArticle;
    private Button btnAdd;
    private Button btnAddNO;
    private MainContract.Presenter mPresenter;
    /**加入群组00,不加入01*/
    public static final String ADD_GROUP = "00";
    public static final String ADD_GROUP_NO = "01";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MainPresenter(this);
        initView();
    }
    public void initView(){
        tvName = (TextView) findViewById(R.id.tv_name);
        tvArticle = (TextView) findViewById(R.id.tv_article);
        btnAdd = (Button) findViewById(R.id.btn_add);
        btnAddNO = (Button) findViewById(R.id.btn_add_no);
        mPresenter.getMyName("123");
        mPresenter.getMyArticle("123");
        btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.addGroup("123",ADD_GROUP);
            }
        });
        btnAddNO.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.addGroup("123",ADD_GROUP_NO);
            }
        });
    }

    @Override
    public void setPresenter(MainContract.Presenter presenter) {
        mPresenter = presenter;
    }

    /**
     * 显示名称
     *
     * @param name
     */
    @Override
    public void showMyName(String name) {
        tvName.setText(name);
    }

    /**
     * 显示文章
     *
     * @param article
     */
    @Override
    public void showMyArticle(String article) {
        tvArticle.setText(article);
    }

    /**
     * 显示toast
     *
     * @param msg
     */
    @Override
    public void showToast(String msg) {
        Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
    }
}

5、接下来就是Presenter了,得到mView,并且实例化Model,这就是我们说的Presenter与View和Model联系,View和Model是不能直接联系的,得到数据后处理数据,直接在回调中调用mView.show()方法,把数据以参数的形式传过去就OK,我这里因为是假的接口,肯定会执行onFailure()方法,哈哈把真的代码注释掉,在onFailure中模拟个假数据,这样运行项目就可以显示数据啦。

public class MainPresenter implements MainContract.Presenter{
    MainContract.View mView;
    MainModel model;

    public MainPresenter(MainContract.View mView) {
        this.mView = mView;
        this.mView.setPresenter(this);
        model = new MainModel();
    }
    /**
     * 发送网络请求得到name
     *
     * @param myId
     */
    @Override
    public void getMyName(String myId) {
        model.getMyName(myId, new Callback<NameBean>() {
            @Override
            public void onResponse(Call<NameBean> call, Response<NameBean> response) {
//              NameBean nameBean = response.body();
//              String name = nameBean.name;
//              mView.showMyName(name);
            }

            @Override
            public void onFailure(Call<NameBean> call, Throwable t) {
                mView.showToast("网络错误!");

                String name = "pengboboer";
                mView.showMyName(name);
            }
        });

    }

    /**
     * 发送网络请求得到文章内容
     *
     * @param myId
     */
    @Override
    public void getMyArticle(String myId) {
        model.getMyArticle(myId, new Callback<ArticleBean>() {
            @Override
            public void onResponse(Call<ArticleBean> call, Response<ArticleBean> response) {
//              ArticleBean articleBean = response.body();
//              String article = articleBean.article;
//              mView.showMyArticle(article);

            }

            @Override
            public void onFailure(Call<ArticleBean> call, Throwable t) {
                mView.showToast("网络错误!");

                String article = "詹姆斯哈登在本赛季打的非常努力,球队也取得了联盟第一的战绩," +
                        "可惜的是依然止步于西部决赛无法继续前进。哈登在技术细节上面依然和最顶级的NBA历史球形有所差距," +
                        "比如乔丹,科比以及现如今的库里的投篮,所以,在投篮技术上无法和当代球星拉开差距的情况下," +
                        "想要统治比赛得到冠军尤其是连冠创建王朝,可能性就很低了。";
                mView.showMyArticle(article);
            }
        });

    }

    /**
     * 加入群组
     *
     * @param myID
     * @param state
     */
    @Override
    public void addGroup(String myID, String state) {
        model.addGroupBean(myID, state, new Callback<AddGroupBean>() {
            @Override
            public void onResponse(Call<AddGroupBean> call, Response<AddGroupBean> response) {
//                AddGroupBean addGroupBean = response.body();
//                String myState = addGroupBean.state;
//                if (TextUtils.equals(myState,"success")) {
//                    mView.showToast("提交成功!");
//                } else {
//                    mView.showToast("提交失败!");
//                }
            }

            @Override
            public void onFailure(Call<AddGroupBean> call, Throwable t) {
                mView.showToast("网络错误!");

                String myState = "success";
                if (TextUtils.equals(myState,"success")) {
                    mView.showToast("提交成功!");
                } else {
                    mView.showToast("提交失败!");
                }
            }
        });
    }
    /**
     * 开始
     */
    @Override
    public void start() {

    }
}

可能有的人会觉得这调过来调过去的,不是多此一举吗,其实这也是我的例子写的比较简单,当你的项目很大的时候,用MVP会感觉非常的舒适,你会感觉逻辑非常的清晰,当然,如果有那种特殊的情况,比如一个Activity特别简单,甚至连网络都不用请求,那就不用写MVP了。我们一般目录的放法是这样的:Model层独立放一个包,Activity、Contract、Presenter是放在一个包下:


Demo地址:https://github.com/pengboboer/MvpTest

总结

这就是一个最接近实战的一个MVP模式,自己也是重新想了一个也亲自写了一下,加深记忆,网上很多人写的非常的复杂,Model层也不知道写了些什么,模拟也没模拟一个真实的网络请求,有的看起来乱七八糟的,不好理解。如果没用过Retrofit的同学提前看一下Retrofit,那么希望能帮助到一些初学者,大家一起共勉!



猜你喜欢

转载自blog.csdn.net/pengbo6665631/article/details/80956087
今日推荐