为啥需要有MVP模式?根本在于我们的Activity太重了,承担的功能过于复杂,不符合软件开发高内聚低耦合
的要求,所以需要我们可以仿照网页开发MVC模式,将传统的Activity一分为二,将视图和控制视图逻辑抽离出来,也就是这里的View和Presenter,当然Model还是保持一致的。
当View与Model解耦后就不一样了,因为View并不知道Model的存在,我们可以直接对Model进行单元测试,无需依赖 Android 的环境。同理当我们进行 UI 测试时,可以通过 Mock 测试用数据来检查V层是否可以正常显示,而无需依赖后台!
MVP实现总的原则就是,将传统中Activity中的操作都抽象成接口。View与Presenter互相持有彼此对象
,通过接口进行通信。这句话看上去挺抽象的,下面通过代码来详细介绍。
这里最具代表的例子,用户登录功能。首先我们建立view接口和presenter接口。
view接口中,需要展示登录,展示重置登录两个功能:
public interface LoginView{
void onClear();
void onLogin(boolean result, int code);
}
presenter接口中,就要有view对应展示的逻辑:
public interface LoginPresenter {
void doclear();
void doLogin(String name, String passwd);
}
这种写法会导致我们要创建大量的接口文件,Google 推荐我们的做法是使用 Contract 类来管理接口:
public interface ContractLogin{
public interface LoginView{
void onClear();
void onLogin(boolean result, int code);
}
public interface LoginPresenter {
void doclear();
void doLogin(String name, String passwd);
}
}
那么回到之前的问题,如何让“View与Presenter互相持有彼此对象,通过接口进行通信”。通过在presenter的实现类中,将view注入,实现presenter的方法时回调view中定义的方法:
public class LoginPresenterImpl implements ContractLogin.LoginPresenter{
private ContractLogin.LoginView mView;//在presenter实现类中持有view,并将其注入
public LoginPresenterImpl(ContractLogin.LoginView mView){
this.mView=mView;
}
@Override
public void doclear() {
//回调view中的方法
mView.onClearText();
}
@Override
public void doLogin(String name, String passwd) {
boolean isLoginSuccess = true;
final int code = check(name,passwd);//这里比对数据库中和用户输入的是否一致
if (code!=0)
isLoginSuccess = false;
final boolean result = isLoginSuccess;
//回调view中的方法
mView.onLogin(result,code);
}
}
至此,presenter完成了对view的持有,下面让Activity去实现View,让View持有presenter对象:
public class LoginActivity extends AppCompatActivity implements ContractLogin.LoginView , View.OnClickListener{
private ContractLogin.LoginPresenter mPresenter;//在View中实现类中持有presenter,并将其注入
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//初始化控件
EditText acount = (EditText) this.findViewById(R.id.login_username);//账户
EditText Pass = (EditText) this.findViewById(R.id.login_password);//密码
Button btnLogin = (Button) this.findViewById(R.id.btn_login_login);//登录按钮
Button btnClear = (Button) this.findViewById(R.id.btn_login_clear);//重置按钮
//设置监听
btnLogin.setOnClickListener(this);
btnClear.setOnClickListener(this);
//注入presenter
loginPresenter = new LoginPresenterCompl(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login_clear:
loginPresenter.doclear();
break;
case R.id.btn_login_login:
loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());
break;
}
}
//实现View中定义的方法,当用户重置账号密码时在此回调view方法
@Override
public void onClear() {
editUser.setText("");
editPass.setText("");
}
@Override
public void onLoginResult(boolean result, int code) {
if (result){
Toast.makeText(this,"haha",Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(this,"wuwu " + code,Toast.LENGTH_SHORT).show();
}
}
至此,我们完成了view和presenter的互相持有,对应开图中双向箭头。
综上这就是最简单的mvp框架的实现。
优点:
- 降低耦合度,实现了 Model 和 View 真正的完全分离,可以修改 View 而不影响 Modle;
- 模块职责划分明显,层次清晰;
- Presenter 可以复用,一个 Presenter 可以用于多个 View,而不需要更改 Presenter 的逻辑;
- 利于测试驱动开发,以前的 Android 开发是难以进行单元测试的;
- View 可以进行组件化,在 MVP 当中,View 不依赖 Model。
缺点:
- Presenter 中除了应用逻辑以外,还有大量的 View->Model,Model->View 的手动同步逻辑,造成 Presenter 比较笨重,维护起来会比较困难;
- 由于对视图的渲染放在了 Presenter 中,所以视图和 Presenter 的交互会过于频繁;
- 如果 Presenter 过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密,一旦视图需要变更,那么 Presenter 也需要变更了。