MVP(Model-View-Presenter)模式:
Model: 数据层,提供数据,负责与网络层和数据库层的逻辑交互.
View: UI层,负责显示数据, 并向Presenter报告用户行为.
Presenter: 逻辑处理层,从Model拿数据, 应用到UI层, 管理UI的状态, 决定要显示什么, 响应用户的行为.
1. 主要优势
耦合降低, Presenter变为纯Java的代码逻辑, 不再与Android Framework中的类如Activity, Fragment等关联, 便于写单元测试.
2. MVP的原则
1). Activity/Fragment实现View接口, View中的方法都只是和UI显示相关的. View要尽可能的dummy, 不涉及业务逻辑, presenter告诉它干什么它干什么就行了.
2). Presenter中没有Android相关的类, 是一个纯Java的程序. 这样有利于解耦和测试. (所以一个检查方法是看你的presenter的import中有没有android的包名.)
3).注意生命周期的处理, 因为异步任务callback返回之后View的状态不一定还是活跃的, 所以要有一定的措施检查View是否还在以及处理注销等, 避免crash或内存泄露.比如,在Presenter中数据回调的方法中, 先检查View.isActive()是否为true, 来保证对Fragment的操作安全.
3. 官方demo
每个界面会定义一个Contract, 里面分别定义View和Presenter的接口. 用Repository包装local和remote的数据, local和remote的数据源会和repository实现相同的data source接口,
4. 自己实现一个中规中矩的mvp - login
定义一个 Contract契约接口,把 Login的Model、View的接口都放入 Contract 的内部,这里的一个 Contract 就对应一个页面(一个 Activity 或者一个 Fragment).放在 Contract 内部是为了让同一个页面的逻辑方法都放在一起,方便查看和修改
public interface LoginContract {
interface Model{
void doLogin(String uName, String pwd, Callback callback);
}
interface View{
void onShowPB(boolean show);
void onLoginSuccess();
void onLoginFailure(String msg);
}
interface Callback{
void success();
void failure(String msg);
}
}
LoginContract.Model由LoginModel实现,LoginContract.View由LoginActivity实现。当 View 需要更新数据时,首先去找 Presenter,然后 Presenter 去找 Model 请求数据,Model 获取到数据之后通知 Presenter,Presenter 再通知 View 更新数据,这样 Model 和 View 就不会直接交互了,所有的交互都由 Presenter 进行,Presenter 充当了桥梁的角色。很显然,Presenter 必须同时持有 View 和 Model 的对象的引用,才能在它们之间进行通信。
public class LoginPresenter {
private LoginContract.View mView;
private LoginContract.Model mModel;
public LoginPresenter(LoginContract.View v){
mView = v;
mModel = new LoginModel();
}
/**
* 防止内存泄漏
* 如果在点击 Button 之后,Model 获取到数据之前,退出了 Activity,
* 此时由于 Activity 被 Presenter 引用,而 Presenter 正在进行耗时操作,
* 会导致 Activity 的对象无法被回收,造成了内存泄漏
*
* 解决的方式很简单:在 Activity 退出的时候,把 Presenter 对中 View 的引用置为空即可。
* 即在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak。
*/
public void detachView() {
mView = null;
}
public void login(String uName, String pwd){
mView.onShowPB(true);
mModel.doLogin(uName, pwd, new LoginContract.Callback() {
@Override
public void success() {
mView.onShowPB(false);
mView.onLoginSuccess();
}
@Override
public void failure(String msg) {
mView.onShowPB(false);
mView.onLoginFailure(msg);
}
});
}
}
//Model: 数据层,负责从网络或数据库中获取数据.
public class LoginModel implements LoginContract.Model {
@Override
public void doLogin(final String uName, final String pwd, final LoginContract.Callback callback) {
//请求数据,并和用户输入的数据匹配
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
String nameFromNet = "lym";
String pwdFromNet = "123456";
if (nameFromNet.equals(uName) && pwdFromNet.equals(pwd)){
callback.success();
} else {
callback.failure("请检查用户名/密码");
}
}
}, 2000);
}
}
public class LoginActivity extends Activity implements LoginContract.View {
private LoginPresenter mPresenter;
private EditText et_uname, et_pwd;
private ProgressBar pb_show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
et_uname = findViewById(R.id.et_uname);
et_pwd = findViewById(R.id.et_pwd);
pb_show = findViewById(R.id.pb_show);
mPresenter = new LoginPresenter(this);
}
@Override
protected void onDestroy() {
//在 Activity 退出的时候,把 Presenter 对中 View 的引用置空
mPresenter.detachView();
super.onDestroy();
}
public void login(View v){
mPresenter.login(et_uname.getText().toString(), et_pwd.getText().toString());
}
@Override
public void onShowPB(boolean show) {
pb_show.setVisibility(show ? View.VISIBLE: View.GONE);
}
@Override
public void onLoginSuccess() {
Toast.makeText(this, "success", Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginFailure(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
5. 参考: