Android的MVP架构

1. MVP定义

MVP全称 Model-View-Presenter,即模型-视图-层现器。

Model: 对于Model层也是数据层。它区别于MVC架构中的Model,在这里不仅仅只是数据模型。在MVP架构中Model它负责对数据的存取操作,例如对数据库的读写,网络的数据的请求等。

View::对于View层也是视图层,在View层中只负责对数据的展示,提供友好的界面与用户进行交互。在Android开发中通常将Activity或者Fragment作为View层。

Presenter:对于Presenter层是连接View层与Model层的桥梁并对业务逻辑进行处理。在MVP架构中Model与View无法直接进行交互。所以在Presenter层它会从Model层获得所需要的数据,进行一些适当的处理后交由View层进行显示。这样通过Presenter将View与Model进行隔离,使得View和Model之间不存在耦合,同时也将业务逻辑从View中抽离。


MVP结构图如下所示:

调用顺序:

a. 用户操作了界面;

b. View层相应用户操作,然后向Presenter层发出请求;

c. Presenter层接受了View层的请求,调用Module层处理业务逻辑,Module处理完毕后返回给Presenter 层,然后Presenter 层调用View层显示相应的结果。

2. 案例场景

当用户启动 App 进入页面后,页面都会进行像服务端请求数据,根据结果显示获得的数据。如果请求数据发生异常,则显示相应的提示页面。下面将根据这个场景来写相关的 MVP 代码。

3. 最简单的MVP

Presenter 层:

接口:

package cn.zzw.messenger.simplemvpdemo.mvp;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/5 21:35
 */
public interface IPresenter {
    public void getTaskInfo();

    public void requestSuccess(DataBean bean);
}

实现类:

package cn.zzw.messenger.simplemvpdemo.mvp;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/5 21:35
 */
public class SimplePresenter implements IPresenter {
    private IView mView;
    private IModule mModule;

    public SimplePresenter(IView mView) {
        this.mView = mView;
        mModule = new SimpleModule();
    }

    @Override
    public void getTaskInfo() {
        //接收View获取数据的请求,Presenter向Module请求数据
        mView.showProgress();
        mModule.requestData(this);
    }

    public void requestSuccess(DataBean bean) {
        //数据请求成功后,通知View
        mView.hideProgress();
        mView.showSuccess(bean);
    }
}

Module 层:

接口:

package cn.zzw.messenger.simplemvpdemo.mvp;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/5 21:36
 */
public interface IModule {
    public void requestData(IPresenter presenter);
}

实现类:

package cn.zzw.messenger.simplemvpdemo.mvp;

import android.os.AsyncTask;
import android.util.Log;

import java.util.Random;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/5 21:57
 */
public class SimpleModule implements IModule {
    IPresenter mPresenter;

    @Override
    public void requestData(IPresenter presenter) {
        this.mPresenter = presenter;
        new RequestAsyncTask().execute();
    }

    /* 这里用AsyncTask模拟请求数据,并延迟5秒*/
    class RequestAsyncTask extends AsyncTask<Void, Void, DataBean> {

        @Override
        protected DataBean doInBackground(Void... voids) {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.e("zzw", "延迟5秒");
            Random random = new Random();
            DataBean mBean = new DataBean("鹭岛猥琐男", random.nextInt(50));
            return mBean;
        }

        @Override
        protected void onPostExecute(DataBean bean) {
            super.onPostExecute(bean);
            mPresenter.requestSuccess(bean);//当请求数据成功后,通知给Presenter
        }
    }
}

数据的实体类:

package cn.zzw.messenger.simplemvpdemo.mvp;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/5 21:33
 */
public class DataBean {
    private String name;
    private int age;

    public DataBean(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "我的名字是" + name + ",今年" + age + "岁。";
    }
}

View 层:

Activity页面的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/mTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Activity类:

package cn.zzw.messenger.simplemvpdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.widget.TextView;

import cn.zzw.messenger.simplemvpdemo.mvp.DataBean;
import cn.zzw.messenger.simplemvpdemo.mvp.IPresenter;
import cn.zzw.messenger.simplemvpdemo.mvp.IView;
import cn.zzw.messenger.simplemvpdemo.mvp.SimplePresenter;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/3 21:23
 */
public class MainActivity extends AppCompatActivity implements IView {
    ProgressDialog mProgressDialog;
    IPresenter mPresenter;
    TextView mTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mTv = findViewById(R.id.mTv);
        mPresenter = new SimplePresenter(this);
        mPresenter.getTaskInfo();
    }

    @Override
    public void showProgress() {
        if (null == mProgressDialog) {
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setMessage("数据加载中...");
        }
        mProgressDialog.show();
    }

    @Override
    public void hideProgress() {
        if (null != mProgressDialog) {
            mProgressDialog.hide();
        }
        mProgressDialog = null;
    }

    @Override
    public void showError() {

    }

    @Override
    public void showSuccess(DataBean info) {
        mTv.setText(info.toString());
    }

    @Override
    protected void onStop() {
        super.onStop();
        hideProgress();
    }
}

效果:

从上面的例子,可以看出:

1. 业务逻辑已经不在Activity 中处理,Activity只是负责View的显示和隐藏。

2. View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。

但是它存在以下问题:

1. Presenter 持有 View 的引用,以及 Module 的引用,这样会造成内存泄漏

2. 每个页面都需要写相应的接口 IView、IPresente、、IModule 以及对应的实现类,这样文件数就会特别多。

3. 优化后的 MVP

此种写法是参考Google官方的mvp例子进行实现的:https://github.com/googlesamples/android-architecture/tree/todo-mvp/todoapp

页面的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/mTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp"
        android:text="Hello World!"
        android:textSize="20sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Presenter层的接口:

BasePresenter:

package cn.zzw.javamvpdemo.mvp;


/**
 * @author 鹭岛猥琐男
 * @create 2019/9/8 08:38
 */
public interface BasePresenter<V extends BaseView> {
    public void initData();
}

 View层的接口:

BaseView:

package cn.zzw.javamvpdemo.mvp;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/8 08:41
 */
public interface BaseView<T extends BasePresenter> {
    public void showLoading();

    public void cancelLoading();
}

Contract类: 

MainContract :

package cn.zzw.javamvpdemo.mvp;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/8 08:45
 */
public interface MainContract {

    interface IView extends BaseView {
        public void showInfo(DataBean bean);
    }

    interface IPresenter extends BasePresenter {
        public void requestDataSuccess(DataBean bean);

        public void attachView(IView view);

        public void detachView();
    }
}

Presenter层对应的实现类:

MainPresenter:

package cn.zzw.javamvpdemo.mvp;

import android.os.AsyncTask;
import android.util.Log;

import java.util.Random;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/8 09:05
 */
public class MainPresenter implements MainContract.IPresenter {
    private MainContract.IView mView;

    @Override
    public void initData() {
        mView.showLoading();
        new RequestAsyncTask().execute();
    }

    @Override
    public void requestDataSuccess(DataBean bean) {
        if (mView != null) {
            mView.cancelLoading();
            mView.showInfo(bean);
        }
    }

    @Override
    public void attachView(MainContract.IView view) {
        if (view != null) {
            mView = view;
        }
    }

    @Override
    public void detachView() {
        if (mView != null) {
            mView = null;
        }
    }

    /* 这里用AsyncTask模拟请求数据,并延迟5秒*/
    class RequestAsyncTask extends AsyncTask<Void, Void, DataBean> {

        @Override
        protected DataBean doInBackground(Void... voids) {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.e("zzw", "延迟5秒");
            Random random = new Random();
            DataBean mBean = new DataBean("鹭岛猥琐男", random.nextInt(50));
            return mBean;
        }

        @Override
        protected void onPostExecute(DataBean bean) {
            super.onPostExecute(bean);
            requestDataSuccess(bean);
        }
    }

}

Activity:

package cn.zzw.javamvpdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.widget.TextView;

import cn.zzw.javamvpdemo.mvp.DataBean;
import cn.zzw.javamvpdemo.mvp.MainContract;
import cn.zzw.javamvpdemo.mvp.MainPresenter;

/**
 * @author 鹭岛猥琐男
 * @create 2019/9/8 08:35
 */
public class MainActivity extends AppCompatActivity implements MainContract.IView {
    private ProgressDialog mProgressDialog;
    private MainContract.IPresenter mPresenter;
    private TextView mTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv = findViewById(R.id.mTv);
        mPresenter = new MainPresenter();//创建对应的Presenter
        mPresenter.attachView(this);//Presenter绑定View
        mPresenter.initData();
    }

    @Override
    public void showLoading() {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setMessage("加载中...");
            mProgressDialog.show();
        }
    }

    @Override
    public void cancelLoading() {
        if (mProgressDialog != null) {
            if (mProgressDialog.isShowing()) {
                mProgressDialog.hide();
            }
            mProgressDialog = null;
        }
    }


    @Override
    public void showInfo(DataBean bean) {
        mTv.setText(bean.toString());
    }

    @Override
    protected void onDestroy() {    //当页面销毁的时候,取消Presenter对View的持有
        super.onDestroy();
        if (null != mPresenter) {
            mPresenter.detachView();
            mPresenter = null;
        }
    }
}

效果跟上面的例子一样,这里就不贴图了。

此种写法跟上一种写法相比,多了一个Contract类,将 View 层和 Presenter 层写在一起,条理更加清晰了,当接口变多了,Contract 优势明显出来了。而且在这种写法中当Activity关闭的时候,实现了 Presenter 中对View的解绑定,以及在Presenter类中增加了对View的判空,从而规避空指针。

参考:

https://github.com/LRH1993/android_interview/blob/master/android/advance/mvp.md

https://blog.csdn.net/suyimin2010/article/details/82077523

https://www.cnblogs.com/ijaye/p/8588481.html

https://github.com/googlesamples/android-architecture/tree/todo-mvp/

https://www.jianshu.com/p/5eeda2ac1487

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值