简介
关于MVP的介绍网上有很多,这里不再累述,虽然大家的实现方式也许各不相同,但是有一些基本的共识:
- Model:数据模型;
- View:用户界面,一般对应于Activity、Fragment;
- Presenter:作为View和Model的桥梁,实现两者解耦。
MVP的核心是:将View中与UI无关的部分逻辑转移到Presenter中。
其依赖关系如下:
View与Presenter相互依赖,Presenter依赖于Model,View和Model解耦。
实例
经典的登陆例子
Model
数据来源,一般是从网络或者数据库异步加载通过接口回调。我们从内存模拟,然后通过handler#postDelayed来模拟加载过程。
public class LoginModel {
public static Map<String, String> maps = new HashMap<>();
private Handler mHandler = new Handler();
static {
maps.put("key1", "password1");
maps.put("key2", "password2");
maps.put("key3", "password3");
}
public void getPassword(final String username, final OnGetPasswordListener listener) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
String password = maps.get(username);
if (password == null) {
listener.onGetError();
} else {
listener.onGet(password);
}
}
}, new Random().nextInt(2000));
}
public void addUser(final String username, final String password, final OnRegisterListener registerListener) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
try {
if (LoginModel.maps.containsKey(username)) {
registerListener.onUserExist();
} else {
LoginModel.maps.put(username, password);
registerListener.onSuccessful();
}
} catch (Exception e) {
e.printStackTrace();
registerListener.onNetError();
}
}
}, new Random().nextInt(2000));
}
public interface OnGetPasswordListener {
void onGet(String password);
void onGetError();
}
public interface OnRegisterListener {
void onSuccessful();
void onUserExist();
void onNetError();
}
}
View
首先明确一个View的职责,以LoginActivity为例,他要做的是:
用户点击登陆:
- 检查账号密码合法性;
- 登陆过程给用户提示;
- 如果登陆成功,用户提示消失,执行其他操作;
- 如果登陆失败,用户提示消失,执行其他操作;
用户点击清除:清除已经输入的内容
用户点击注册:跳转到注册界面
我们将其抽象为一个接口:
public interface ILoginView {
void usernameIllegal();
void passwordIllegal();
void showProgress();
void dismissProgress();
void loginSuccessfully();
void loginUnsuccessfully();
void clearState();
}
然后View去实现上诉接口:
public class LoginActivity extends AppCompatActivity implements ILoginView {
@InjectView(R.id.et_username)
EditText mEtUsername;
@InjectView(R.id.et_password)
EditText mEtPassword;
@InjectView(R.id.btn_login)
Button mBtnLogin;
@InjectView(R.id.btn_clear)
Button mBtnClear;
private ProgressDialog mProgressDialog;
private LoginPresenter mLoginPresenter = new LoginPresenter(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
mProgressDialog = new ProgressDialog(this);
mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mLoginPresenter.doLogin(mEtUsername.getText().toString(), mEtPassword.getText().toString());
}
});
mBtnClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mLoginPresenter.doClear();
}
});
}
@Override
public void showProgress() {
mProgressDialog.show();
}
@Override
public void dismissProgress() {
mProgressDialog.dismiss();
}
@Override
public void loginSuccessfully() {
Toast.makeText(LoginActivity.this, "login successfully", Toast.LENGTH_SHORT).show();
}
@Override
public void loginUnsuccessfully() {
Toast.makeText(LoginActivity.this, "login unsuccessfully, please check", Toast.LENGTH_SHORT).show();
}
@Override
public void clearState() {
mEtUsername.getText().clear();
mEtPassword.getText().clear();
}
@Override
public void usernameIllegal() {
mEtUsername.setError("should not be empty");
}
@Override
public void passwordIllegal() {
mEtPassword.setError("should not be empty");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
startActivity(new Intent(this, RegisterActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
可以看到上诉的View只执行UI相关的操作,没有任何逻辑,逻辑都转移到了Prensenter。有一些比较简单的逻辑可以不必转移到Presenter Layer,比如clearState,这一逻辑完全可以直接在View层调用而不经过Presenter回调。还有输入合法性判断,只判断其是否为空也是很简单的逻辑,同样可以在View层实现。总而言之,简单的逻辑可以在View中处理。
Presenter
最后也是最关键的,在Presenter中连接View与Model,承担View的大部分逻辑。
public class LoginPresenter {
private ILoginView mLoginView;
private LoginModel mModel = new LoginModel();
public LoginPresenter(ILoginView loginView) {
mLoginView = loginView;
}
public void doLogin(String inputUsername, final String inputPassword) {
if (inputUsername.equals("")) {
mLoginView.usernameIllegal();
return;
}
if (inputPassword.equals("")) {
mLoginView.passwordIllegal();
return;
}
mLoginView.showProgress();
mModel.getPassword(inputUsername, new LoginModel.OnGetPasswordListener() {
public void onGet(String password) {
if (inputPassword.equals(password)) {
mLoginView.loginSuccessfully();
} else {
mLoginView.loginUnsuccessfully();
}
mLoginView.dismissProgress();
}
public void onGetError() {
mLoginView.loginUnsuccessfully();
mLoginView.dismissProgress();
}
});
}
public void doClear() {
mLoginView.clearState();
}
}
比较
假设没有使用MVP架构,常见的做法会是:
mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mEtUsername.getText().toString().equals("")) {
usernameIllegal();
return;
}
if (mEtPassword.getText().toString().equals("")) {
passwordIllegal();
return;
}
LoginModel loginModel = new LoginModel();
showProgress();
loginModel.getPassword(mEtUsername.getText().toString(), new LoginModel.OnGetPasswordListener() {
public void onGet(String password) {
if (mEtPassword.getText().toString().equals(password)) {
loginSuccessfully();
} else {
loginUnsuccessfully();
}
dismissProgress();
}
public void onGetError() {
loginUnsuccessfully();
dismissProgress();
}
});
}
});
这种写法,虽然一开始写起来会非常方便,但是越到后面越臃肿而难以维护。所以使用MVP架构会使得层次清晰,View与Model解耦,代码也较为整洁。
假设现在我要测试一下逻辑是否有误,如果是上诉写法,除了run一下工程然后手动点击测试之外,仿佛无从下手。使用了MVP架构,将View职责抽象成接口后,我们写起单元测试会比较方便。
最后,很多文章会把Model和Prensenter也抽象出一个接口,目前我不知道这样做有什么好处,所以我倾向于Model和Presenter先不需要抽象接口,需要时再进行重构。
项目地址:
https://github.com/leelit/android-mvp-pattern参考:
http://blog.csdn.net/vector_yi/article/details/24719873
https://segmentfault.com/a/1190000003927200
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0227/2503.html
http://blog.csdn.net/lmj623565791/article/details/46596109
https://github.com/antoniolg/androidmvp
更新
使用这套MVP组合开发了一段时间,有几点新的认识,为了不和上面的弄混乱,新开一个标题。
IPresenter接口
public interface IPresenter {
void doDestroy();
}
每个Presenter都应该实现这个接口,并将View依赖清除。这样做是为了防止内存泄露,如果你的Model使用RxJava,一般最好调用unsubscribe()以取消网络请求。
@Override
public void doDestroy() {
mLoginView = null;
// if you do with RxJava, you should call unsubscribe() here
}
最后当View回调onDestroy()时调用Presenter的doDestroy();
@Override
protected void onDestroy() {
super.onDestroy();
mLoginPresenter.doDestroy();
}
AS开发步骤
AS环境下使用以下步骤进行MVP开发会非常便捷
1、创建一个空的接口,IView;
2、View(Fragment、Activity)实现IView;
public class MyActivity extends Activity implements IView
3、在Presenter里面聚合IView;
public class MyPresenter {
private IView mView;
public MyPresenter(IView view) {
mView = view;
}
}
4、在View里面组合Presenter;
public class MyActivity extends Activity implements IView {
private MyPresenter mPresenter = new MyPresenter(this);
}
5、当View需要Model的逻辑时,用mPresenter来调用一个尚未实现的方法;
if(needModel) {
mPresenter.doSomeThing();
}
6、按alt + enter,MyPresenter里面会自动添加这个方法,然后进行业务处理;
7、当业务需要回调View时,用mView来调用一个尚未实现的方法;
if(needView) {
mView.showSomeThing();
}
8、按alt + enter,IView接口将自动添加这个方法,并且View也会实现这个方法。