MVP的概念
MVP中,M层负责数据的读取和存储;V层负责视图逻辑的处理;P层负责业务逻辑的处理。同时,P层在处理业务逻辑时需要与V层和M层交互,所以会获取两层的引用实例,充当掌控者的角色。M层与V层彻底解耦的。
MVP是在MVC的基础上升级版,重在解耦,并不一定减少代码量。在大型项目中,引入MVP开发模式能充分体现出其强大功效。目前,一些敏捷开发的小型项目还是用MVC模式的较多。只是C层与V层的耦合度很容易变高,使得activity变得很臃肿。
MVP的作用
把Activity里的许多逻辑都抽离到View和Presenter的接口中去,并由具体的实现类来完成(用接口的形式因为有多种具体的实现),方便进行单元测试。
Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。同时把业务逻辑抽到Presenter中去。
注意:由于activity/fragment是V层的实现类,视图逻辑可通过接口的形式在其中实现。
MVP的使用步骤
创建V层的接口,根据视图逻辑列出对应的接口。视图逻辑:即用户的操作中与视图相关的。如获取清除用户输入的数据,显示隐藏进度条/对话框,页面跳转等。
创建P层的接口,根据业务逻辑列出对应的接口。业务逻辑:即用户的操作中与视图无关的。如点击滑动事件的处理等。
在V层的实现类中(即activity/fragment)实例化Presenter的实现类。并在Presenter的实现类中获取V层和M层的引用,并调用V层实现类的中的视图逻辑方法。
检验MVP模式写的是否规范的办法
- Activity除了FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。
- 试想着若界面改动或者业务逻辑改动,是否只影响到某一层逻辑的改动(逻辑复用操作产生的代码不是逻辑改动)。若M V P层都影响了,就说明没有解耦彻底。(注意:单个功能单个Presenter,若功能相近可单个Presenter内多个接口)
- P层的业务逻辑达到可以直接复用到其他V层而不需要该V层另做非视图逻辑外的处理(即逻辑复用)。
- V层不见业务逻辑处理。仅仅有视图逻辑处理。更不能有M层的逻辑处理。
注意:2中说明下,页面改动引起的业务逻辑改动在MVP模式中还不能达到仅在某一层中改动,MVVM是针对此种情况的改善版。
案例解析
案例情景:用户输入账户名和密码后,调用服务器登录接口,并显示返回数据。
从业务需求来看,罗列出各层的逻辑接口,并且各层统一由契约类约束,使用户对各层的功能一目了然,维护起来也方便。
契约类代码如下:
/**
* Created by jinzifu on 2017/2/8.
* 契约类来统一管理MVP模式中的Model View Presenter 的接口,使用户对各层的功能一目了然,维护起来也方便。
*/
public class LoginContract {
public interface View {
public void clear();
public String getUsername();
public String getPassword();
public void showProgress();
public void hideProgress();
public void error();
public void success();
public void showEmpty();
public void showInfo(String string);
}
public interface Presenter {
public void loginP();
}
public interface Model {
public void loginM(String username, String password, OnLoginListener onLoginListener);
}
}
其中的clear接口我忘了用了[汗颜],可以看出各层都以接口的形式实现各层的逻辑。接口的形式便于扩展和单元测试,同一个接口可以有不同的实现类。
P层的实现类代码如下:
/**
* Created by jinzifu on 2017/02/08
* 有时候为了实现一个复杂的业务,需要多个Presenter配合的。
* Presenter 获取的是V层的引用,因此是在UI线程中操作的。而M层的异步网络请求是在子线程的。
*/
public class LoginPresenterImpl extends BasePresenter<LoginContract.View> implements LoginContract.Presenter {
private LoginModelImpl hiModel;
private LoginContract.View view;
public LoginPresenterImpl(LoginContract.View view) {
hiModel = new LoginModelImpl();
this.view = view;
}
@Override
public void loginP() {
if (TextUtils.isEmpty(view.getPassword()) || TextUtils.isEmpty(view.getUsername())) {
view.showEmpty();
return;
}
view.showProgress();
hiModel.loginM(view.getUsername(), view.getPassword(), new OnLoginListener() {
@Override
public void loginSuccess(User user) {
view.success();
view.showInfo("账号:" + user.getUsername() + "\t\t密码:" + user.getPassword());
}
@Override
public void loginFailed() {
view.error();
}
@Override
public void finish() {
view.hideProgress();
}
});
}
}
可以看到在其构造方法内获取的V层和M层的引用(不是把activity的view传过来的意思,因为activity做了V层的实现类,这里获取的是V层的引用实例)。
实现了P层接口的loginP方法,在这里处理业务逻辑。并通过V层的引用调用V层的接口实现方法。如 view.showEmpty()、view.showProgress()等。
我们看到了该类继承了BasePresenter这个类,下面看下源码:
/**
* Created by jinzifu on 2017/2/9.
* BasePresenter基类,在此引入弱引用,防止内存泄漏
*/
public class BasePresenter<T> {
protected Reference<T> viewRef;
public void attachView(T view) {
viewRef = new WeakReference<T>(view);
}
public void detachView() {
if (viewRef != null) {
viewRef.clear();
viewRef = null;
}
}
}
可以看到在这个基类中,通过attachView将V层的引用由强引用转化为弱引用(强引用宁可报错也不会被垃圾回收器回收,弱引用可在内存不足时被回收),对内存泄漏做了规避。detachView方法是对该引用置空处理。
这里采用泛型是因为作为基类,用于适配不同类型的Presenter。
下面看看V层的实现类即activity的源码:
public class LoginActivity extends BaseMvpActivity<LoginContract.View, LoginPresenterImpl> implements LoginContract.View {
@BindView(R.id.username)
EditText username;
@BindView(R.id.password)
EditText password;
@BindView(R.id.login)
Button login;
@BindView(R.id.show_info)
TextView showInfo;
private LoginPresenterImpl hiPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp);
ButterKnife.bind(this);
}
@Override
protected LoginPresenterImpl createPresenter() {
hiPresenter = new LoginPresenterImpl(this);
return hiPresenter;
}
@OnClick(R.id.login)
public void onClick() {
hiPresenter.loginP();
}
@Override
public void clear() {
username.setText("");
password.setText("");
}
@Override
public String getUsername() {
return username.getText().toString();
}
@Override
public String getPassword() {
return password.getText().toString();
}
@Override
public void showProgress() {
Toast.makeText(this, "加载中...", Toast.LENGTH_SHORT).show();
}
@Override
public void hideProgress() {
Toast.makeText(this, "加载完毕...", Toast.LENGTH_SHORT).show();
}
@Override
public void error() {
Toast.makeText(this, "请求失败...", Toast.LENGTH_SHORT).show();
}
@Override
public void success() {
Toast.makeText(this, "请求成功...", Toast.LENGTH_SHORT).show();
}
@Override
public void showEmpty() {
Toast.makeText(this, "账号和密码不能为空", Toast.LENGTH_SHORT).show();
}
@Override
public void showInfo(String string) {
showInfo.setText(string);
}
}
V层做的仅仅是和视图逻辑有关的接口实现方法以及Presenter的初始化。避免了逻辑冗余。用户的操作中设计到业务逻辑的可以通过调用Presenter的接口方法实现。
通过createPresenter方法获取Presenter实例。父类BaseMvpActivity< V,T>在该类中实现V层由强引用变成弱引用。并在V生命周期结束时引用置空。
父类源码如下:
/**
* Created by jinzifu on 2017/2/9.
*/
public abstract class BaseMvpActivity<V, T extends BasePresenter<V>> extends Activity {
protected T presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = createPresenter();
presenter.attachView((V) this);
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.detachView();
}
protected abstract T createPresenter();
}
项目中后续的需求变更,可以通过P层的接口变更或者多个接口组合实现业务逻辑的变动。也可以仅仅改变其实现类的业务逻辑。
思考:MVP是为了降低耦合而产生的,代码量及类文件也许会增多。不过对于大型项目后续的维护很有帮助。业务分离出V层,各个业务通过接口实现,接口又利于拓展和多个具体实现。便于同一套业务逻辑独立抽出来用在不同的V层。
如何利于新手上手,并且在满足降耦的基础上减少代码量,是接下来我们要做的事情。敬等后续更新。