参考资料:####
MVP模式也使用了一段时间,没有特定成熟的框架,各有各的实现方式;之前不太清楚为什么要这么设计,后来项目中,增加了新的模块,完全采用MVP来实现,刚开始很痛苦,分离了一大堆接口,直到有一次,某个业务方,要求替换页面,由于采用了MVP,发现只需修改V层,然后根据约定的接口方式,一个个实现,即可,M与P层,不动;这才有点感觉了。
通过查看崩溃日志,发现采用MVP设计的功能,崩溃率很低;
目前总结的2个好处:
- 降低耦合度,让各层各司其事,给测试与开发带来好处;移动端开发,变化最大的可能就是界面,如果在界面中,柔和了逻辑与数据处理,导致维护成本急剧增加;
- 降低出错率;
目前遇到的问题:
- 大量接口的引入,对开发的前期接口设计能力有一定要求;
- view层,界面是会回收的,如何恢复,目前没找到很好的方案,实际操作中,我还是将这部分,让 activity or fragment 自己处理;
- 关于对应一个 页面 V是否可使用多个P问题;在实际中,我们的项目都是一个V对应一个P,但是P可能会对应多个M,来完成功能;
- 如果在V层中,需要取消某一个网络请求(V不销毁),这种情况,目前我们项目没有遇到过,但如有此情况,就智者见智;
demo代码:##
https://github.com/zhaoyubetter/mvpDemo
1. MVP的分层####
- M 数据层:所有与数据操作相关的都放入这里,比如:网络请求,数据库操作等;
- P 业务逻辑层:与业务相关的操作,放入这里,P分别引用了M与V层;
M不直接与V打交道,区别于MVC;
http://www.jianshu.com/p/ee8125b1429d - V 视图层:用来展示页面,如:Activity,Fragment等;
2.MVP调用简单类图
登录图例:
3.show me the code MVP框架
- View层公共接口:
/**
* MVP中View层抽象接口
*/
public interface IMVPView {
/**
* 加载填充界面数据时显示加载进度
* @param msg 提示文字。传null时显示默认
*/
void showLoading(String msg);
/**
* 失败
* @param msg 提示文字。传null时显示默认
*/
void showError(String msg);
void removeLoading();
/**
* 获取Context对象
*/
Context getContext();
}
2.Model(Repo)层公共接口:
/**
* mvp中M层抽象接口,这里采用 Repo 仓库,参考了google mvp demo
*/
public interface IMVPRepo {
/**
* 销毁方法
* 1,释放P引用<br/>
* 2,释放具体逻辑上的数据
*/
void onDestroy();
}
3.Presenter层公共接口:
/**
* MVP层中P层顶层抽象
*/
public interface IMVPPresenter {
/**
* 加载填充界面需要的数据
*/
void onCreate();
/**
* <ol>
* <li>释放View</li>
* <li>释放Model</li>
* <li>释放其他资源</li>
* </ol>
*/
void onDestroy();
}
4.Presenter抽象类的引入,在抽象层,没有强制要求需要传递 M层,
是这样考虑的,
在传统的 JavaEE开发中,Dao层Service层是分开的,
一个Service层可以引入多个Dao层,来实现业务;
在Android中,我们给P层模拟类似于EE开发中的Service层与Dao层的结合,避免层次太多,也就是说:我们可在P层引入多个M,来完成业务;
另: P层只引用一个V,一般情况下,我们是一个页面对应一个具体的业务,多V对应一P,还没用接触过
总结: 可以多M对应一P对应一V;
/**
* MVP层中P层抽象类
*/
public abstract class AbsMVPPresenter<T extends IMVPView> implements IMVPPresenter {
protected WeakReference<T> mViewRef;
/**
* 必要的构造方法
* 可以在构造方法中创建对应的Model
*
* @param view : 绑定对应的View
*/
public AbsMVPPresenter(T view) {
this.mViewRef = new WeakReference<>(view);
}
/**
* 界面恢复时,调用该方法通过M层恢复数据。不需要的界面不要重写
* 此方法为扩展,界面恢复时的处理,目前暂没想好放在哪里,这里留一个入口
*
* @param bundle 恢复数据时需要的参数
*/
public void restore(Bundle bundle) {
//empty
}
/**
* view 是否存活
*
* @return
*/
protected boolean isAlive() {
return mViewRef != null && mViewRef.get() != null && mViewRef.get().getContext() != null;
}
@Override
public void onDestroy() {
if (null != mViewRef) {
mViewRef.clear();
mViewRef = null;
}
}
}
4.MVP 框架的实战练习
需求:访问网络,将结果显示在界面上,现设计约束接口如下:
4. 1 新建Contract契约接口:######
每个业务建立自己的契约接口,如下:
public interface SimpleContract {
interface View extends IMVPView {
void showContent(String data);
}
interface Presenter extends IMVPPresenter {
void doGetData(String url);
}
interface Repo extends IMVPRepo {
void getData(String url, final LoadDataCallback<String> callback);
}
}
4. 2 先从M层下手:######
先将M层实现,这个时候,我们可以针对M来编写测试脚本,测试M层
public class SimpleRepo implements SimpleContract.Repo {
private AbsRequest mHttpRequest;
@Override
public void getData(final String url, final LoadDataCallback<String> callback) {
mHttpRequest = new OkHttpRequest.Builder().url(url).callback(new AbsRequestCallBack<String>() {
@Override
public void onSuccess(Response<String> response) {
super.onSuccess(response);
callback.onDataLoaded(response.responseBody);
}
@Override
public void onFailure(Throwable e) {
super.onFailure(e);
callback.onDataNotAvailable(e.toString(), 0);
}
}).build();
mHttpRequest.request();
}
@Override
public void onDestroy() {
// 销毁网络请求
if (mHttpRequest != null) {
mHttpRequest.cancel();
}
}
}
4. 2 编写P层:#####
P层比较关键,M与V如何进行交互,就在这里了
public class SimplePresenter extends AbsMVPPresenter<SimpleContract.View> implements SimpleContract.Presenter {
/**
* 接口
*/
SimpleContract.Repo mRepo;
/**
* 也可以在 V 层中传入M,这里没这样做,各有各的好处
* @param view
*/
public SimplePresenter(SimpleContract.View view) {
super(view);
mRepo = new SimpleRepo();
}
@Override
public void onCreate() {
}
@Override
public void doGetData(String url) {
if (!isAlive()) return;
final SimpleContract.View view = mViewRef.get();
mRepo.getData(url, new LoadDataCallback<String>() {
@Override
public void onDataLoaded(String data) {
if (isAlive()) {
view.showContent(data);
}
}
@Override
public void onDataNotAvailable(String msg, int code) {
if (isAlive()) {
view.showError(msg);
}
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
if (mRepo != null) {
mRepo.onDestroy();
}
}
}
4. 3 编写V层:######
V层,再这里简单实现契约接口的View接口:
public class SimpleActivity extends AppCompatActivity implements SimpleContract.View {
private ProgressDialog mDialog;
private TextView content;
private SimpleContract.Presenter mPresenter;
private EditText et;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
content = (TextView) findViewById(R.id.content);
et = (EditText) findViewById(R.id.et);
// 初始化P
mPresenter = new SimplePresenter(this);
findViewById(R.id.request).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String url = et.getText().toString();
content.setText("");
mPresenter.doGetData(url);
}
});
}
@Override
public void showLoading(String msg) {
if (mDialog == null) {
mDialog = new ProgressDialog(this);
}
mDialog.setMessage(msg);
mDialog.show();
}
@Override
public void showError(String msg) {
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
}
@Override
public void removeLoading() {
if (mDialog != null) {
mDialog.dismiss();
}
}
@Override
public Context getContext() {
return getApplicationContext();
}
@Override
public void showContent(String data) {
content.setText(data);
}
}