您的位置:首页 > 移动开发 > Android开发

Android:MVP模式

2016-03-07 15:37 477 查看

简介

关于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也会实现这个方法。

项目地址:

https://github.com/leelit/android-mvp-pattern
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: