Android MVP - Contract
说起Android比较流行的架构模型,MVC、MVP、MVVM这几种是最常见的,也是当前主流的架构模型,本篇通过对MVC到MVP的进化过程,给出一种MVP个人认为比较适合的开发模式 - Contract用法(MVVM下节再讨论):
MVP前世
MVC模式(Model、View、Controller)已经开始渐渐地退出,它最大的痛点在于Controller中既承载了View又包括了业务逻辑,导致Controller最终会很庞大,不利于维护和可读,比如:
public class XXXActivity extents Activity {
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(layout);
initView();
initData();
}
public void initView() {
view1 = findViewById(id);
view2 = findViewById(id);
...
view1.setOnClickListener(this);
view2.setXXListener(..);
...
}
public void initData() {
model.loadData(response -> {
view1.setXXX(response.XX);
view2.setXXX(response.XX);
});
...
}
@Override
public void onClick(View v) {
model.commit();
model.doSth();
...
}
}
即使你拆分出许多XXXHelper或者说XXXViewBlock,并且只在Controller中处理业务流程(Helper或者Block中持有Activity引用,不持有Model,为了不与Model耦合在一起),比如:
public class XXXActivity extents Activity {
private XXViewHelper helper1;
private XXViewHelper helper2;
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(layout);
initView();
initData();
}
public void initView() {
helper1 = new XXViewHelper(this); // 传入Activity
helper2 = new XXViewHelper(this); // 传入Activity
}
public void initData() {
model.loadData(response -> {
helper1.updateView(response);
helper2.updateView(response);
});
...
}
// 供Helper调用
public void commit() {
model.commit();
}
// 供Helper调用
public void doSth() {
model.doSth();
}
}
但仍然解决不了的问题是,Activity向这些Helper或者Block暴露的信息太多了,比如生命周期这些方法,以及不希望Helper它们看到的方法,不利于开发者对业务流程的理解,并且看到没有?现在的Activity已经有点像Presenter了,只是里边还存在一些 Helper的初始化。
MVP今生
由于上述分析,为了进一步减轻Activity的压力,所以决定将Activity只做一些与View相关的事情,那处理业务流程的部分去哪了?这个就是新产生的一个模块-Presenter。虽然很多文章都有写,但我还是再次声明下它们的工作职责,如下:
![](https://img.haomeiwen.com/i2066087/7ff4caf1b1ed841d.jpg)
View: 只处理UI及页面效果的细节,向Presenter暴露更新UI的方法;并且持有Presenter的引用,通过Presenter对其暴露的方法进行一些初始化页面以及业务提交等动作,但不关注动作的具体实现。
Presenter: 只关注业务逻辑的细节,持有View的引用,通过调用View层向其暴露的方法去更新UI (这里的View引用不是具体某个控件的引用,我们也不能让Presenter持有某一控件的引用);并且也持有一个或者多个model的引用(在于你想将Presenter,也就是业务逻辑拆分的程度,避免Presenter也像MVC中Controller一样被撑爆),可以使用model,通过对数据库或者网络的访问从而拿到数据,调用View暴露的方法去刷新UI。
Model:向Presenter暴露获取、存储、提交数据等方法,具体实现细节Presenter不关注;Model通过Callback 将数据返回给Presenter。
Ok,按照之前规定的原则:
- View 向 Presenter 暴露更新UI的方法,于是我们有了 IView
- Presenter 向 View 暴露执行一些特定业务方法,比如初始化页面,提交等。
于是,就产生了第一种MVP模式:
![](https://img.haomeiwen.com/i2066087/614c8290064d792d.png)
看到以上类图,可能会有人有疑问:
-
在IView中为什么我不直接写 updateView(Response r),这样不是写的接口方法更少吗?
这个问题问的好!因为我曾经也有过这样的问题,试想一下,如果我们把Model中的Response直接存在于Activity(View层)中,那当我们更改Response的时候,会导致View层也需要进行相应的变动,还能做到View和Model的完全解耦吗?所以View 向 Presenter暴露的方法参数一定要符合两点:
(1). 基本数据类型
(2). 公共数据类型 -
为什么要写IPresenter,让Activity直接引用 XXXPresenter不就好了?
确实,这样做从功能上来说也可以,但是,为了更严格一些,让Presenter向View暴露那些View关注的方法,这样开发View的同学就会一下子明白自己只需要关注哪些方法就够了;同理,Presenter持有IView的引用而不是Activity的也是这个原因。
按照以上模式,我们用伪代码来实现一下第一种MVP模式(以下代码不严谨,只表达意思):
Presenter:
interface IPresenter {
void initData();
void commit();
}
class XXXPresenter implements IPresenter {
private IView mView;
private Model mModel;
private Callback mCallback = new Callback() {
public void onSuccess(Response r) {
mView.cancelLoading();
if (r.url == 'initDataUrl') {
mView.updateHeader(r.header);
mView.updateContent(r.content);
} else if (r.url == 'commitUrl') {
// 页面跳转或者其他什么的
}
}
public void onError(Error e) {
mView.showError();
}
};
XXXPresenter(IView view) {
mView = view;
mModel = new Model();
}
@Override
public void initData() {
mView.showLoading();
mModel.request(xx, xx, mCallback);
}
@Override
public void commit() {
mView.showLoading();
mModel.request(xx, xx, mCallback);
}
}
View:
interface IView {
void updateHeader(String header);
void updateContent(String content);
void showLoading();
void cancelLoading();
void showError(String error);
}
public class XXXActivity extends BaseActivity implements IView {
private IPresenter mPresenter;
private TextView tvHeader;
private TextView tvContent;
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(layout);
initView();
initData();
}
public void initView() {
tvHeader = findViewById(..);
tvContent = findViewById(..);
tvHeader.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
mPresenter.commit();
}
});
}
public void initData() {
mPresenter = new XXXPresenter(this);
mPresenter.initData();
}
@Override
public void updateHeader(String header) {
tvHeader.setText(header);
}
@Override
public void updateContent(String content) {
tvContent.setText(content);
}
}
由于 showLoading、cancelLoading 以及 showError都是公用方法,所以你可以把它们放到 BaseActivity中,不用每个Activity都实现一遍。
讲完了吗?Contract在哪呢? 好汉不要急,待我娓娓道来~
以上的MVP确实解决了两个问题:
- Activity过重
- View与Model解耦
但是,我觉得还没有解决好,哪里还有问题呢:
- IView 每次都要声明 showLoading等公共方法
- Presenter中每次请求时都要调用mView的showLoading等公共方法
- 能不能把IView和IPresenter定义在同一个文件里,这样我就能一眼看出这个页面的业务是什么样子的。
带着这几个问题,Google官方的MVP Contract横空出世:
关注一下工程结构,如下:
![](https://img.haomeiwen.com/i2612749/bf714fe06ae34ebb.png)
如果我们按照模块划分包的话,最适合这种模式,我们来看下一个模块下的组成:
![](https://img.haomeiwen.com/i2066087/314bca2a4a6b74c4.png)
-
由于我们再BasePresenter中因为网络请求可能会调用到View的showLoading等方法,所以BasePresenter我写成了抽象类,并且指定一个<? extends BaseView>的泛型;
-
上图指定一个Presenter的泛型,目的是为了:
public interface BaseView<T> {
void setPresenter(T presenter);
}
![](https://img.haomeiwen.com/i2066087/a08ffebb3ee96a15.png)
![](https://img.haomeiwen.com/i2066087/318f3c80e8beb8a8.png)
而Presenter的实例化完全可以放到Activity中执行,所以并不需要给BaseView指定Presenter的泛型(可能有点绕。。。。。。),在按照之前第一种MVP模式的业务逻辑再上一张优化后的类图 :
![](https://img.haomeiwen.com/i2066087/326d683730f7a1d0.png)
话不多说,直接贴一段优化后的伪代码:
// BaseView
public interface BaseView {
public void showLoading();
public void cancelLoading();
public void showError();
}
// BaseActivity
public class BaseActivity extends Activity implements BaseView {
public void showLoading() {
LoadingUtil.showLoading(this);
}
public void cancelLoading() {
LoadingUtil.cancelLoading(this);
}
public void showError() {
LoadingUtil.showError(this);
}
}
// BasePresenter
public abstract class BasePresenter<View extends BaseView> {
private View mView;
protected Callback mCallback = new Callback() {
public void onStart() {
mView.showLoading();
}
public void onSuccess(Response r) {
mView.cancelLoading();
onRequestSuccess(r);
}
public void onError(Error e) {
mView.showError();
onRequestFailed(e);
}
};
protected abstract void onRequestSuccess(Response r);
protected abstract void onRequestFailed(Error e);
}
// Contract
interace Contract {
interface View extends BaseView {
void updateHeader(String header);
void updateContent(String content);
}
abstract class Presenter extends BasePresenter<View> {
protected Presenter(View view) {
super(view);
}
protected abstract void initData();
protected abstract void commit();
}
}
// 具体的XXPresenter
class XXPresenter extends Contract.Presenter {
private Model mXXModel;
protected XXPresenter(View view) {
super(view);
mXXModel = new Model();
}
protected void initData() {
mXXModel.request(xx, xx, mCallback);
}
protected void commit() {
mXXModel.commit(xx, xx, mCallback);
}
protected void onRequestSuccess(Response r) {
if (r.url == 'initDataUrl') {
mView.updateHeader(r.header);
mView.updateContent(r.content);
} else if (r.url == 'commitUrl') {
// 页面跳转或者其他什么的
}
}
protected void onRequestFailed(Error e) {
// 做一些其他的error逻辑,基类已经调用baseview的showerror
}
}
// XXActivity
public class XXActivity extends BaseActivity implements Contract.View {
private Contract.Presenter mPresenter;
private TextView tvHeader;
private TextView tvContent;
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(layout);
initView();
initData();
}
public void initView() {
tvHeader = findViewById(..);
tvContent = findViewById(..);
tvHeader.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
mPresenter.commit();
}
});
}
public void initData() {
mPresenter = new XXPresenter(this);
mPresenter.initData();
}
@Override
public void updateHeader(String header) {
tvHeader.setText(header);
}
@Override
public void updateContent(String content) {
tvContent.setText(content);
}
}
如果是一个View组件而不是Activity或者Fragment,也可以使用,那就需要我们把BaseActivity对于View的公共处理抽成一个BaseViewHelper,这样尽量多地复用代码,代码我就不贴了哈,这也是个题外话~
结束语
以上就是MVP的Contract写法,单看Contract,个人感觉还是比较清晰的,而且再加一部分的代码复用,我相信会让你的app更加敏捷地迭代~