Android MVP 模式使用指南
参考github代码:github
参考博客:Google 官方Android MVP架构实践
简单的以google官方代码和自己的demo记录Android的MVP设计模式。
1.官方MVP模式:
官方MVP的samples的github地址是(https://github.com/googlesamples/android-architecture/)
若水三千,我们先取一瓢。先看看最简单的todo-mvp分支,这是最基础的MVP架构samples。
在我从前认识的MVP模式中,并不存在BasePresenter和BaseView两个文件,只需要针对不同的Presenter和View来定义不同的接口就行了。
来看一下这两个文件中都写了啥:
BasePresenter.java
public interface BasePresenter {
void start();
}
BaseView.java
public interface BaseView<T> {
void setPresenter(T presenter);
}
BasePresenter中声明了一个方法,当我们的实现类presenter需要开始进行动作时,事先调用start方法,来进行一些预处理动作。(代码中都是在View的实现类的onResume中调用presenter的start方法)。
因为google的sample中presenter需要做一些画面初始化的操作,所以每个画面都需要这样的方法将数据加载到画面上,所以使用了这个方法来作为初始化方法,我们在实际编码时要根据情况观察是否使用这个模板。
BaseView中声明了一个方法,setPresenter(),这里的设计和我平常的理解有所不同。我平时的想法是在view的onCreate方法中new出presenter实例作为view的成员变量,而google的例子是在activity中使用fragment实例new出presenter实例,但是这个presenter不属于fragment,需要在presenter中调用mTasksView.setPresenter(this)。
TasksActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
...
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
...
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
...
}
TasksPresenter.java
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
mTasksView.setPresenter(this);
}
另外,我们发现google提供的包结构与我常用的不同,我们会按照模块来进行划分,例如:view,presenter,util,contract。但是google是将view,presenter,contract放在一起的,仁者见仁吧。
2.我认识的MVP模式:
首先我们会定义一个接口叫做Contract,在其中我们会定义三个接口,分别是Presenter,View和Model的接口,其中View和Model中都只声明了一个方法供presenter调用,而Presenter中声明了两个方法供View和Model调用。
当然,一般在一个app中不可能只有一个Contract,因为我们需要针对不同类型的画面定义不同的画面方法,每个不同类型的画面上的逻辑也不会相同。
接下来我们来说明一下一个正常的mvp模式是如何运作的,我们需要实现一个很简单的功能,登录检查。
(检查用户名和密码是否与我们数据库中的匹配,这里为了简单起见,我们就不再写数据库的操作了,而是直接使用现成的字符串来检查)。
首先,这是我们的Contract部分,因为我们只需要针对一个登录画面,所以我们只需要一个Contract来存放三个接口。
/**
* This specifies the contract between the view and the presenter.
*/
public interface LoginContract {
interface Presenter {
/**
* login action.
* @param name user name.
* @param pwd user password.
*/
void login(String name, String pwd);
/**
* set login result for presenter instance.
* @param resultCode
*/
void setLoginResult(int resultCode);
}
interface View {
/**
* show login result for view instance.
* @param resultCode login result;
* {@link LoginPresenter#LOGIN_ERROR}, {@link LoginPresenter#LOGIN_SUCCESS}.
*/
void showLoginResult(int resultCode);
}
interface Model {
/**
* login action.
* @param name user name.
* @param pwd user password.
*/
void login(String name, String pwd);
}
}
接下来使我们的presenter的具体实现类,login方法只是向下调用了model的login方法,有人会觉得这样做很奇怪,不是多此一举吗?其实,Presenter的作用就是一个桥梁,连接View和Presenter的桥梁。
/**
* Listens to user actions from the UI ({@link com.siw.mvp.view.MainActivity}), retrieves the data and updates the
* UI as required.
*/
public class LoginPresenter implements LoginContract.Presenter {
private final static String TAG = LoginPresenter.class.getSimpleName();
private LoginContract.View mView;
private LoginContract.Model mModel;
public static final int LOGIN_ERROR = 0;
public static final int LOGIN_SUCCESS = 1;
public LoginPresenter(LoginContract.View view) {
this.mView = view;
mModel = new LoginModel(this);
}
@Override
public void login(String name, String pwd) {
Log.d(TAG, "login() enter, name = " + name + "pwd = " + pwd);
mModel.login(name, pwd);
}
@Override
public void setLoginResult(int resultCode) {
Log.d(TAG, "setLoginResult() enter, resultCode = " + resultCode);
if (null != mView) {
mView.showLoginResult(resultCode);
} else {
Log.e(TAG, "mView = null");
}
}
}
最后是Model,这里定义了其实现的接口中的方法,为了方便起见,在Model中的login逻辑处理只用了最简单的字符串比较。
public class LoginModel implements LoginContract.Model {
private final static String TAG = LoginModel.class.getSimpleName();
private LoginContract.Presenter mPresenter;
public LoginModel(LoginContract.Presenter mPresenter) {
this.mPresenter = mPresenter;
}
@Override
public void login(String name, String pwd) {
Log.d(TAG, "login() enter");
if (null == mPresenter) {
Log.e(TAG, "mPresenter = null");
return;
}
if ("admin".equals(name) && "root".equals(pwd)) {
mPresenter.setLoginResult(LoginPresenter.LOGIN_SUCCESS);
} else {
mPresenter.setLoginResult(LoginPresenter.LOGIN_ERROR);
}
}
}
最后聊一聊一个问题,为什么要使用MVP模式,或者说,这种模式的好处在哪?
通过以上两个sample的分析,我们发现,使用MVP模式后,原本会被放在Activity或Fragment中的代码都被抽象出来了,被分成了MVP三个部分。这三个部分其实承担了三个不同的职责,View负责显示以及响应用户操作,Model负责处理逻辑,而Presenter则负责将View和Presenter两层联系起来,就像是一座桥。这样的三个部分的划分,极大的降低了代码之间的耦合度,让代码的执行变得十分清晰明确。
另外,有人想问,中间的Presenter层的作用是什么呢?Presenter的存在进一步分离了上层画面和底层逻辑,在Android中,我们的底层逻辑会更多的使用异步线程的方式执行,那么Presenter就可以承担起一个业务逻辑层的作用,让Model层进行纯粹的底层逻辑的执行。
一般来说,MVP设计模式适合稍微大一些的项目,如果只是简单的小demo,那么没有必要使用MVP。