1.MVC架构的特点

在Android当中,相比传统的MVC设计模式,View层(视图层)对应的是Android当中的布局文件。而布局文件是用XML格式来写的。但是这个XML布局文件它并不像Java Web端那么强大,能做的事情非常有限。而Controller所对应的Activity这个类,它不仅要处理业务逻辑,同时它也要处理Android当中的一些操作UI的功能。我们在实际项目开发过程中,有很多UI操作的逻辑写在Activity当中。这就导致Controller层中充斥了很多View层应当做的内容,从而使得Controller层非常冗余厚重。为了改善这一点,我们引入了MVP的架构模式。

2.MVP定义

<1>M:依然是业务逻辑和实体模型。

<2>V:对应于Activity,负责View的绘制以及与用户交互。

<3>P:负责完成VIew与Model间的交互。

3.MVP的优点

相较于MVC,MVP加入了Presenter层,我们便可以把之前Activity中作为Controller层的内容抽出来放入Presenter层,Activity则可以彻底加入View层了。这样我们也就完全切断了View层与Model层之间的联系。Activity与XML作为View层只对如何操作UI、渲染数据到界面上关心,而Model层则只关心如何获取数据源。至于数据源的二次处理以及数据注入UI则由Presenter关心,彻底切断数据与界面渲染。

4.MVP实例讲解

根据实例我们学习一下MVP,顺手搭建一套MVP的框架

首先是View层,我们先定义一个接口

/**
 * View层接口基类
 */
public interface MvpBaseIView {

}

这个接口基类其实是为了一会儿多个类的关联使用,里面是没有代码的。

接着是Activity的基类

/**
 * View层Activity基类,在子类中实现MvpBaseIView子类
 */
public abstract class MvpBaseActivity<T extends MvpBaseIPresenter> extends AppCompatActivity{

    protected Bundle savedInstanceState;
    protected T mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //在界面未初始化之前调用的初始化窗口
        initWindows();
        if (initArgs(getIntent().getExtras())) {
            this.savedInstanceState = savedInstanceState;
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(getContentLayoutId());
            initWidget();
            mPresenter = getPresenterImpl();
            initData();
        } else {
            finish();
        }
    }

    /**
     * 初始化窗口
     */
    protected void initWindows() {

    }

    /**
     * 初始化相关参数
     *
     * @param bundle 参数bundle
     * @return 如果参数正确返回true, 错误返回false
     */
    protected boolean initArgs(Bundle bundle) {
        return true;
    }

    /**
     * 得到当前界面的资源文件Id
     *
     * @return 资源文件Id
     */
    protected abstract int getContentLayoutId();

    /**
     * 初始化控件
     */
    protected void initWidget() {

    }

    /**
     * 初始化数据
     */
    protected void initData() {

    }

    protected abstract T getPresenterImpl();
}

这样View层的两个基类就都有了。

接下来是Model层的两个

Model层回调接口基类

public interface MvpBaseCallBack {

}

Model层接口基类

public interface MvpBaseIModel {

}

Model基类

public class MvpBaseModel<T extends MvpBaseCallBack> {

    protected T callback;
    protected Context context;

    public MvpBaseModel(Context context, T callback) {
        this.context = context;
        this.callback = callback;
    }
}

最后就是我们的Presenter层

Presenter接口基类

/**
 * Presenter层接口基类
 */
public interface MvpBaseIPresenter {

}

Presenter基类

public abstract class MvpBasePresenter<T extends MvpBaseIModel,U extends MvpBaseIView> {

    protected T mModel;
    protected U iView;

    public MvpBasePresenter(Context context,U mView) {
        mModel = getModelImp();
        iView = mView;
    }

    protected abstract T getModelImp();
}

这样基类就完成了。我们可以看到,Activity中持有Presenter的对象,而Presenter中又持有IView和Model的对象。这样三者就联系在了一起。但是Activity和Model之间却没有直接的关联。

接下来是我们对于基类的使用。

首先先上一个实体类,模拟网络请求返回的实体

public class Entity {
    private String str;
    private int hide;

    public Entity(String str, int hide) {
        this.str = str;
        this.hide = hide;
    }

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    public int getHide() {
        return hide;
    }

    public void setHide(int hide) {
        this.hide = hide;
    }
}

接着就是我们的View层两大件

/**
 * View层视图接口
 */
public interface DemoIView extends MvpBaseIView {
    void showText(String str);
    void showView();
    void hideView();
    String getXText();
}

这里可以看到View层接口我定义了四个动作,分别是显隐控件和设置获取TextView上的文字,这个要根据具体页面设置。

/**
 * View层视图实现类
 */
public class DemoActivity extends MvpBaseActivity<DemoIPresenter> implements DemoIView{

    private TextView tv_center;
    private View view_gang;

    @Override
    protected int getContentLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initWidget() {
        tv_center = findViewById(R.id.tv_center);
        view_gang = findViewById(R.id.view_gang);
    }

    @Override
    protected void initData() {
        mPresenter.getData();
    }

    @Override
    protected DemoIPresenter getPresenterImpl() {
        return new DemoPresenter(this,this);
    }

    @Override
    public void showText(String str) {
        tv_center.setText(str);
    }

    @Override
    public void showView() {
        view_gang.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideView() {
        view_gang.setVisibility(View.GONE);
    }

    @Override
    public String getXText() {
        return tv_center.getText().toString().trim();
    }
}

Activity实现了IView的接口,并且实现了里面的接口方法,将定义的动作表现在了界面上。这里注意,我们在getPresenterImpl中返回了Presenter类的实例,并且该Activity还实现了IView接口。

下来便是我们的Model层

public interface DemoCallback extends MvpBaseCallBack {
    void success(Entity entity);
    void failure();
}

Callback中规中矩,还是成功和失败两个接口。

接着是Model接口类

public interface DemoIModel extends MvpBaseIModel {
    void getData(String strEnter);
}

Model实现类 

public class DemoModel extends MvpBaseModel<DemoCallback> implements DemoIModel {

    public DemoModel(Context context, DemoCallback callback) {
        super(context, callback);
    }

    @Override
    public void getData(String strEnter) {
        try {
            new Thread().sleep(2000);
            if (strEnter.equals("11111")){
                //成功
                callback.success(new Entity("xixixxi",0));
            }else{
                //失败
                callback.failure();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Model类中我们在getData方法中模拟了一个网络请求,并进行了回调。

这样我们的Model层也编写完成了。

下来就是Presenter层了。

我们先进行接口设计

public interface DemoIPresenter extends MvpBaseIPresenter {
    void getData();
}

由于我们只有一个获取界面数据的需求,所以这里只定义了一个接口方法

public class DemoPresenter extends MvpBasePresenter<DemoIModel,DemoIView>
        implements DemoIPresenter,DemoCallback{

    private Context mContext;

    public DemoPresenter(Context context, DemoIView mView) {
        super(context, mView);
        mContext = context;
    }

    @Override
    protected DemoIModel getModelImp() {
        return new DemoModel(mContext,this);
    }

    @Override
    public void success(Entity entity) {
        iView.showText(entity.getStr());
        if (entity.getHide() == 0) {
            iView.hideView();
        }else{
            iView.showView();
        }
    }

    @Override
    public void failure() {
        ToastUtil.toastWord(mContext,"错误");
    }

    @Override
    public void getData() {
        mModel.getData(iView.getXText());
    }
}

在Presenter的实现类中,一方面是刚才我们定义的接口方法getData中调用了Model层的方法。另一方面是我们在回调中调用了IView接口中方法对界面进行了操作。当然还有在getModelImp中返回我们创建的Model类实例。这就是一套完整的MVP设计模式。我个人觉得MVP除了解耦,最大的魅力就是开发的不耽误功夫。我们可以先进行界面的开发,将界面可能需要的工作提前都设置好,比如给TextView设置文本,从Edittext获取值,根据变量显隐控件,按钮需要的点击事件等,都可以提前设置好对应的方法。接着等接口出来我们就可以很快对接接口,并将数据设置进去。不影响之前的界面,方便快捷,思路清晰。