安卓MVP框架的简介与搭建


一、基本概念

MVP是Model-View-Presenter的简称,即模型-视图-表现层的缩写。MVP是由MVP模式进化而来的,MVP改进了MVC中的控制器过于臃肿的问题。 与MVC一样,MVP将应用程序的数据处理、数据显示和逻辑控制分开,用一种业务逻辑、数据显示和界面相分离的方法组织代码。

二、MVP与MVC的比较(以Android开发为例)

MVP模式是MVC模式在Android上的一种变体,要介绍MVP就得先介绍MVC。在MVC模式中,Activity应该是属于View这一层。而实质上,它既承担了View,同时也包含一些Controller的东西在里面。这对于开发与维护来说不太友好,耦合度大高了。把Activity的View和Controller抽离出来就变成了View和Presenter,这就是MVP模式。

MVC MVP
Model 业务逻辑和实体模型 Model 业务逻辑和实体模型
View 对性欲布局文件 View 对应于Activity,负责view的绘制和用户交互
Controller 对应于Activity等 Presenter 负责完成view与model的交互,处理程序逻辑

MVP模式是MVC模式在Android上的一种变体,要介绍MVP就得先介绍MVC。在MVC模式中,Activity应该是属于View这一层。而实质上,它既承担了View,同时也包含一些Controller的东西在里面。这对于开发与维护来说不太友好,耦合度大高了。把Activity的View和Controller抽离出来就变成了View和Presenter,这就是MVP模式。

MVP与MVC相比,MVP减少了Activity的职责,简化了Activity的代码,将复杂的逻辑代码提取到了Presenter中进行处理。Presenter的出现,将Activity视为View层,Presenter负责完成View层与Model层的交互。与之对应的好处就是:程序耦合度更低,更加方便地进行测试,程序可扩展性大大提高。

MVP从MVC演化而来,它们的基本思想有相通的地方。Controller与Presenter负责逻辑的处理,Model提供数据,View负责显示数据。MVP作为一个新的模式,与MVC有一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter来进行的,所有的交互都发生在Presenter内部;而在MVC中View会直接从Model读取数据。

MVP解决了MVC问题: 在MVP中,Presenter完全把View与Model进行分离,主要的程序逻辑在Presenter实现。而且,Presenter与具体的View是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更View层的时候可以保持Presenter不变。不仅如此,我们还可以编写测试用的View,模拟用户的操作,从而实现对Presenter的测试——而不需要使用自动化的测试工具。 MVP中的View层是很薄的一层,View只应该有简单的set/get方法、用户输入和界面显示的内容,除此之外不应该有更多的内容,绝不允许直接访问Model——这就是MVP与MVC的很大不同之处。

三、MVP的工作原理和结构

  • 1、模型(Model)

    模型表示业务逻辑和实体模型,提供数据给Presenter。

  • 2、视图(View)

    视图是用户看到并与之交互的界面。视图向用户显示相关的数据,并能接受用户的输入数据,但它不进行任何实际的业务处理。

  • 3、表现层(Presenter)

    应用程序主要的程序逻辑在Presenter内实现,而且Presenter将Model和View完全分离,所有的交互都发生在Presenter内部,具体业务逻辑全部交由Presenter接口实现类中进行处理。

四、MVP的优点

  • 1、模型与视图完全分离,我们可以修改视图而不影响模型。
  • 2、可以更加高效地使用模型,因为所有的交互都发生在Presenter内部。
  • 3、我们可以将一个视图用于多个视图,而不需要改变Presenter内部的逻辑。这个特性非常有用,因为视图的变化总是比模型的变化要频繁。
  • 4、把程序逻辑放在Presenter中,我们就可以脱离用户接口来测试这些逻辑了。(单元测试)

五、MVP的缺点

由于对View的操作放在了Presenter中,所以View和Presenter的交互会过于频繁。如果Presenter过多地操作视图,往往会使得它与特定的 View联系过于紧密。一旦视图需要改变,那么Presenter也需要改变。

六、MVP模式框架的封装

首先看一下项目的基本结构图:

$ tree
.
├── model
│   ├── IPestModel.java
│   └── PestModelImpl.java
├── presenter
│   ├── BasePresenter.java
│   └── PestPresenter.java
└── view
    ├── BaseActivity.java
    └── IPestView.java

项目结构看起来像是这个样子的,MVP的分层还是很清晰的。我的习惯是先按模块分Package,在模块下面再去创建model、view、presenter的子Package,当然也可以用model、view、presenter作为顶级的Package,然后把所有的模块的model、view、presenter类都到这三个顶级Package中,就好像有人喜欢把项目里所有的Activity、Fragment、Adapter都放在一起一样。

Presenter层

BasePresenter.java

package com.ahau.againstpest.presenter;

import java.lang.ref.WeakReference;

/**
 * 注释:
 *
 * @author fanchunchun
 * @date 2018/4/3
 */

public class BasePresenter<T > {
    //1, view 层的引用
    protected WeakReference<T> mViewRef;


    //进行绑定
    public void attachView(T view) {
        mViewRef = new WeakReference<T>(view);
    }

    //进行解绑
    public void detachView() {
        mViewRef.clear();
    }
}

PestPresenter.java

package com.ahau.againstpest.presenter;

import com.ahau.againstpest.model.IPestModel;
import com.ahau.againstpest.model.PestModelImpl;
import com.ahau.againstpest.view.IPestView;

/**
 * 注释:表示层
 *
 * @author fanchunchun
 * @date 2018/4/3
 */

public class PestPresenter<T extends IPestView> extends BasePresenter<T> {

    //2, model层的引用
    IPestModel pestModel = new PestModelImpl();

    //3, 构造方法
    public PestPresenter() {
    }

    //4, 执行操作(UI)逻辑
    public void fetch() {

        if (mViewRef.get() != null) {
            if (pestModel != null) {
                //获取pest的名字
                pestModel.getPest(new IPestModel.PestGetListener() {
                    @Override
                    public void onComplete(String[] pest) {
                        if (mViewRef.get() != null) {
                            mViewRef.get().showPest(pest);
                        }
                    }
                });
                //设置当前时间
                pestModel.getDate(new IPestModel.DateListener() {
                    @Override
                    public void onComplete(String date) {
                        if (mViewRef.get() != null) {
                            mViewRef.get().setDate(date);
                        }
                    }
                });

                pestModel.getLatlng(new IPestModel.LatlngListener() {
                    @Override
                    public void onComplete(String[] latlng) {
                        if (mViewRef.get() != null) {
                            mViewRef.get().setLatlng(latlng);
                        }
                    }
                });
            }

        }


    }
}

View层

BaseActivity.java

package com.ahau.againstpest.view;

import android.app.Activity;
import android.os.Bundle;

import com.ahau.againstpest.presenter.BasePresenter;

/**
 * 注释:
 *
 * @author fanchunchun
 * @date 2018/4/3
 */

public abstract class BaseActivity<V,T extends BasePresenter<V>> extends Activity{

    //表示层的引用
    public T pestPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        pestPresenter = createPresenter();
        pestPresenter.attachView((V) this);
    }

    protected abstract T createPresenter();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        pestPresenter.detachView();
    }
}

IPestView.java

package com.ahau.againstpest.view;

/**
 * 注释:定义出所有UI逻辑
 *
 * @author fanchunchun
 * @date 2018/4/3
 */

public interface IPestView {
    //显示DropEditText的数据(使用会掉的方式返回数据)
    void showPest(String[] pest);
    //显示当前时间
    void setDate(String date);
    //获取当前的经纬度
    void setLatlng(String[] latlng);
}

Model层

IPestModel.java

package com.ahau.againstpest.model;

/**
 * 注释:用来加载数据
 *
 * @author fanchunchun
 * @date 2018/4/3
 */

public interface IPestModel {
    void getPest(PestGetListener pestGetListener);
    //设计一个内部回调接口
    interface PestGetListener {
        void onComplete(String[] pest);
    }

    void getDate(DateListener dateListener);
    interface DateListener {
        void onComplete(String date);
    }
    //获取当前经纬度数据
    void getLatlng(LatlngListener latlngListener);
    interface LatlngListener {
        void onComplete(String[] latlng);
    }

}
package com.ahau.againstpest.model;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.support.v4.app.ActivityCompat;
import android.util.Log;

import com.ahau.againstpest.HttpProcessor.HttpCallback;
import com.ahau.againstpest.HttpProcessor.HttpHelper;
import com.ahau.againstpest.base.MyApplication;
import com.ahau.againstpest.bean.PestBean;
import com.ahau.againstpest.utils.URLUtils;
import com.amap.api.maps2d.CoordinateConverter;
import com.amap.api.maps2d.model.LatLng;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 注释:
 *
 * @author fanchunchun
 * @date 2018/4/3
 */

public class PestModelImpl implements IPestModel {


    /**害虫的名字
     * @param pestGetListener
     */
    @Override
    public void getPest(final PestGetListener pestGetListener) {

        Map<String, Object> params = new HashMap<>();

        HttpHelper.obtain().get(URLUtils.PestUrl, params, new HttpCallback<PestBean>() {
            @Override
            public void onSuccess(PestBean pestBean) {

                ArrayList<PestBean.Pest> mPestNameList;
                String[] arr; //pest种类

                mPestNameList = pestBean.pest;
                Log.i("TAG", pestBean.toString());

                /**
                 * 设置虫种
                 */
                arr = new String[mPestNameList.size() + 1];
                arr[0] = "请选择:";
                for (int i = 0; i < mPestNameList.size(); i++) {
                    arr[i + 1] = mPestNameList.get(i).pestname;
                }
                Log.i("tag", arr[0]);
                pestGetListener.onComplete(arr);
            }

            @Override
            public void onFailure(String e) {
                Log.e("tag", "网络请求失败");
            }

        });

    }

    /**
     * 当前日期
     * @param dateListener
     */
    @Override
    public void getDate(DateListener dateListener) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("  yyyy/MM/dd  ");// yyyy/MM/dd  HH:mm:ss
        //获取当前时间
        Date date = new Date(System.currentTimeMillis());
        String sDate = simpleDateFormat.format(date);
        dateListener.onComplete(sDate);
    }

    /**
     * 经纬度数据
     * @param latlngListener
     */
    @Override
    public void getLatlng(LatlngListener latlngListener) {
        String[] latlng = new String[2];
        String jingdu, weidu, gaodejingdu, gaodeweidu; //经纬度

        LocationManager locationmanager = (LocationManager) MyApplication.getInstance().getSystemService(Context.LOCATION_SERVICE);

        if (ActivityCompat.checkSelfPermission(MyApplication.getInstance(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MyApplication.getInstance(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        Location location = locationmanager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

        if (location != null) {
            double lat = location.getLatitude();// 经度
            double lng = location.getLongitude();// 纬度

            jingdu = "" + lat;
            weidu = "" + lng;

        } else {
            location = locationmanager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
            if (location != null) {
                double lat = location.getLatitude();// 经度
                double lng = location.getLongitude();// 纬度
                jingdu = "" + lat;
                weidu = "" + lng;
            } else {
                jingdu = "";
                weidu = "";
            }
        }
        if (!jingdu.isEmpty() && !weidu.isEmpty()) {

            LatLng latLng = new LatLng(Double.valueOf(jingdu), Double.valueOf(weidu));
            CoordinateConverter converter = new CoordinateConverter();
            // CoordType.GPS 待转换坐标类型
            converter.from(CoordinateConverter.CoordType.GPS);
            // sourceLatLng待转换坐标点 LatLng类型
            converter.coord(latLng);
            // 执行转换操作
            LatLng desLatLng = converter.convert();
            gaodejingdu = "" + desLatLng.latitude;
            gaodeweidu = "" + desLatLng.longitude;
        }
        latlng[0] = jingdu;
        latlng[1] = weidu;

        latlngListener.onComplete(latlng);

    }
}