丁先森 博客园
MVP 理论知识
在MVP 架构中跟MVC类似的是同样也分为三层。
Activity 和Fragment 视为View层,负责处理 UI。
Presenter 为业务处理层,既能调用UI逻辑,又能请求数据,该层为纯Java类,不涉及任何Android API。
Model 层中包含着具体的数据请求,数据源。
三层之间调用顺序为view->presenter->model,为了调用安全着想不可反向调用!不可跨级调用!
那Model 层如何反馈给Presenter 层的呢?Presenter 又是如何操控View 层呢?看图!
图是我借来的
上图中说明了低层的不会直接给上一层做反馈,而是通过 View 、 Callback 为上级做出了反馈,这样就解决了请求数据与更新界面的异步操作。上图中 View 和 Callback 都是以接口的形式存在的,其中 View 是经典 MVP 架构中定义的,Callback 是我自己加的。
View 中定义了 Activity 的具体操作,主要是些将请求到的数据在界面中更新之类的。
Callback 中定义了请求数据时反馈的各种状态:成功、失败、异常等。
MVP模式的核心思想:
MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。
使用MVP的优点
分离了视图逻辑和业务逻辑,降低了耦合
Activity只处理生命周期的任务,代码变得更加简洁
视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM
其中最重要的有三点:
Activity 代码变得更加简洁
相信很多人阅读代码的时候,都是从Activity开始的,对着一个1000+行代码的Activity,看了都觉得难受。
使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。
方便进行单元测试
一般单元测试都是用来测试某些新加的业务逻辑有没有问题,如果采用传统的代码风格(习惯性上叫做MV模式,少了P),我们可能要先在Activity里写一段测试代码,测试完了再把测试代码删掉换成正式代码,这时如果发现业务有问题又得换回测试代码,咦,测试代码已经删掉了!好吧重新写吧……
MVP中,由于业务逻辑都在Presenter里,我们完全可以写一个PresenterTest的实现类继承Presenter的接口,现在只要在Activity里把Presenter的创建换成PresenterTest,就能进行单元测试了,测试完再换回来即可。万一发现还得进行测试,那就再换成PresenterTest吧。
避免 Activity 的内存泄露
Android APP 发生OOM的最大原因就是出现内存泄露造成APP的内存不够用,而造成内存泄露的两大原因之一就是Activity泄露(Activity Leak)(另一个原因是Bitmap泄露(Bitmap Leak))。
Java一个强大的功能就是其虚拟机的内存回收机制,这个功能使得Java用户在设计代码的时候,不用像C++用户那样考虑对象的回收问题。然而,Java用户总是喜欢随便写一大堆对象,然后幻想着虚拟机能帮他们处理好内存的回收工作。可是虚拟机在回收内存的时候,只会回收那些没有被引用的对象,被引用着的对象因为还可能会被调用,所以不能回收。
Activity是有生命周期的,用户随时可能切换Activity,当APP的内存不够用的时候,系统会回收处于后台的Activity的资源以避免OOM。
采用传统的MV模式,一大堆异步任务和对UI的操作都放在Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对Activity的引用。这样一来,即使Activity已经被切换到后台(onDestroy已经执行),这些异步任务仍然保留着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity Leak。Android的组件中,Activity对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收Activity对象,如果有Activity Leak,APP很容易因为内存不够而OOM。
采用MVP模式,只要在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak。
说了这么多,没看懂?好吧,我自己都没看懂自己写的,我们还是直接看代码吧。
先看一下目录结构
这里肯定会有人说,我去每次创建新功能,不能每次创建那么多类吧,那不得麻烦死,这里推荐一个AndroidStudio的插件,AndroidMVP,看一下这个插件能实现的功能
图还是借来的,这个还是挺好用的,看完插件那就再看看代码实现的效果吧,毕竟能看到才知道实现了什么效果
看完效果图,来看看代码是怎么实现的
倒着来IView--->activity--->IPresenter--->PresenterImpl--->IModel--->ModelImpl
主要看请求的数据吧
ITwoActivity
public interface ITwoAView {
//请求标记
int REQUEST_ONE = 0;
int REQUEST_TWO = 1;
int REQUEST_THREE = 2;
//响应标记
int RESPONSE_ONE = 0;
int RESPONSE_TWO = 1;
int RESPONSE_THREE = 2;
T request(int requestFlag);
void response(T response, int responseFlag);
String getToken();
void showToast(String msg);
}
大多数都是自动生成的,只需要你自己到时候需要什么参数自己添加一下就行
TwoActivity
/**
* 测试获取数据集合,网络请求拿到集合对象
*/
public class TwoActivity extends BaseMvpActivity implements ITwoAView {
private ITwoAPresenter mITwoAPresenter;
private Button btn_getdata;//请求数据按钮
private EditText et_token;//模拟参数
private ListView lv_data_list;//listView
private List jsonpuInfoEntityList;//商铺集合
private JsonDataBean jsonDataBean;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mITwoAPresenter = new TwoAPresenterImpl(this);
setContentView(R.layout.activity_two);
initViewBind();
}
private void initViewBind() {
btn_getdata = (Button) findViewById(R.id.btn_getdata);
et_token = (EditText) findViewById(R.id.et_token);
lv_data_list = (ListView) findViewById(R.id.lv_data_list);
btn_getdata.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mITwoAPresenter.getData();
}
});
}
@Override
public T request(int requestFlag) {
return null;
}
@Override
public void response(T response, int responseFlag) {
/*拿到的总的对象*/
if (responseFlag == IMainAView.RESPONSE_ONE) {
jsonDataBean = (JsonDataBean) response;
Log.e("jsonDataBean", "返回的数据信息:" + jsonDataBean.getHome_shopline());
jsonpuInfoEntityList = jsonDataBean.getHome_shoplist();
PuListAdapter puListAdapter = new PuListAdapter(TwoActivity.this, jsonpuInfoEntityList);
lv_data_list.setAdapter(puListAdapter);
}
}
@Override
public String getToken() {
return et_token.getText().toString();
}
@Override
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
ITwoAPresenter
public class TwoAPresenterImpl implements ITwoAPresenter {
private ITwoAView mITwoAView;
private ITwoAModel mITwoAModel;
public TwoAPresenterImpl(ITwoAView aITwoAView) {
mITwoAView = aITwoAView;
mITwoAModel = new TwoAModelImpl();
}
@Override
public void getData() {
mITwoAModel.getData(mITwoAView.getToken(), new CallBack() {
@Override
public void onSuccess(Object response) {
mITwoAView.response(response, IMainAView.RESPONSE_ONE);
mITwoAView.showToast("数据请求成功");
}
@Override
public void onError(String t) {
mITwoAView.response(mITwoAModel, IMainAView.RESPONSE_TWO);
mITwoAView.showToast(t);
}
});
}
}
ITwoAModel
public interface ITwoAModel {
/*请求数据*/
void getData(String token, CallBack callBack);
}
TwoAModelImpl
public class TwoAModelImpl implements ITwoAModel {
JsonDataBean jsondatabean;
@Override
public void getData(String token, final CallBack callBack) {
/*进行网络请求,获取数据*/
// 方式二:使用静态方式创建并显示,这种进度条只能是圆条,设置title和Message提示内容
if (token.equals("")) {
} else {
RequestQueue mQueue = Volley.newRequestQueue(AppApplication.getmContext());
StringRequest stringRequest = new StringRequest(Request.Method.POST, "http://www.mockhttp.cn/mock/upzl-android-home2", new Response.Listener() {
@Override
public void onResponse(String s) {
Log.e("login", "-------获取到的idjson--------" + s.toString());
Log.e("login", "-------JSON.parseObject(json).data--------" + JSON.parseObject(s.toString()).getString("data"));
jsondatabean = JSON.parseObject(JSON.parseObject(s.toString()).getString("data"), JsonDataBean.class);
//成功之后,传递出jsondatabean
if (jsondatabean != null) {//获取到了数据
callBack.onSuccess(jsondatabean);
} else {
callBack.onError(s);//获取失败信息
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
}) {
@Override
protected Map getParams() throws AuthFailureError {
Map map = new HashMap();
return map;
}
};
/*设置请求一次*/
stringRequest.setRetryPolicy(
new DefaultRetryPolicy(
500000,//默认超时时间,应设置一个稍微大点儿的,例如本处的500000
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,//默认最大尝试次数
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
);
mQueue.add(stringRequest);/*请求数据*/
}
}
}
这里的请求和解析json是用的Volley和fastjson
JsonDataBean,这里也是用插件自动生成的,GsonFormat,只要把后台给你返回的json字符放进去,自己生成实体类,AS开发还是可以的。
public class JsonDataBean {
public JsonDataBean() {
}
public JsonDataBean(String home_shopnewnum, String home_shopline, String home_people, List home_imgurl, List home_h5url, List home_news, List home_shoplist) {
this.home_shopnewnum = home_shopnewnum;
this.home_shopline = home_shopline;
this.home_people = home_people;
this.home_imgurl = home_imgurl;
this.home_h5url = home_h5url;
this.home_news = home_news;
this.home_shoplist = home_shoplist;
}
/**
* home_imgurl : [{"imgId":1,"imgUrl":"http://img4.duitang.com/uploads/item/201403/27/20140327114737_w3uA3.jpeg"},{"imgId":1,"imgUrl":"http://img4.duitang.com/uploads/item/201403/27/20140327114737_w3uA3.jpeg"},{"imgId":1,"imgUrl":"http://img4.duitang.com/uploads/item/201403/27/20140327114737_w3uA3.jpeg"},{"imgId":1,"imgUrl":"http://img4.duitang.com/uploads/item/201403/27/20140327114737_w3uA3.jpeg"}]
* home_h5url : [{"url":"暂定"}]
* home_news : [{"newsId":1,"newUrl":"http://192.168.1.197/web/upH5/consult.html?id=123&url=2"},{"newsId":1,"newUrl":"http://192.168.1.197/web/upH5/consult.html?id=123&url=2"},{"newsId":1,"newUrl":"http://192.168.1.197/web/upH5/consult.html?id=123&url=2"}]
* home_shoplist : [{"shopId":1,"shopImgUrl":"http://up.enterdesk.com/edpic_source/e2/37/f1/e237f1f737e24dc0dbd6030a22b72005.jpg","shopName":"朝阳-双井|100㎡","shopAddress":"广平门黄平路平米","shopTags":[{"tag":"随便四字"},{"tag":"临近地铁"},{"tag":"最多四字"}],"shopMonery":"8000","shopMoneryUnit":"元/月"},{"shopId":1,"shopImgUrl":"http://up.enterdesk.com/edpic_source/e2/37/f1/e237f1f737e24dc0dbd6030a22b72005.jpg","shopName":"朝阳-双井|100㎡","shopAddress":"广平门黄平路平米","shopTags":[{"tag":"随便四字"},{"tag":"临近地铁"},{"tag":"最多四字"}],"shopMonery":"5.5","shopMoneryUnit":"万/月"}]
* home_shopnewnum : 814
* home_shopline : 77847
* home_people : 20173
*/
private String home_shopnewnum;
private String home_shopline;
private String home_people;
private List home_imgurl;
private List home_h5url;
private List home_news;
private List home_shoplist;
public String getHome_shopnewnum() {
return home_shopnewnum;
}
public void setHome_shopnewnum(String home_shopnewnum) {
this.home_shopnewnum = home_shopnewnum;
}
public String getHome_shopline() {
return home_shopline;
}
public void setHome_shopline(String home_shopline) {
this.home_shopline = home_shopline;
}
public String getHome_people() {
return home_people;
}
public void setHome_people(String home_people) {
this.home_people = home_people;
}
public List getHome_imgurl() {
return home_imgurl;
}
public void setHome_imgurl(List home_imgurl) {
this.home_imgurl = home_imgurl;
}
public List getHome_h5url() {
return home_h5url;
}
public void setHome_h5url(List home_h5url) {
this.home_h5url = home_h5url;
}
public List getHome_news() {
return home_news;
}
public void setHome_news(List home_news) {
this.home_news = home_news;
}
public List getHome_shoplist() {
return home_shoplist;
}
public void setHome_shoplist(List home_shoplist) {
this.home_shoplist = home_shoplist;
}
public static class HomeImgurlBean {
/**
* imgId : 1
* imgUrl : http://img4.duitang.com/uploads/item/201403/27/20140327114737_w3uA3.jpeg
*/
private int imgId;
private String imgUrl;
public int getImgId() {
return imgId;
}
public void setImgId(int imgId) {
this.imgId = imgId;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
}
public static class HomeH5urlBean {
/**
* url : 暂定
*/
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
public static class HomeNewsBean {
/**
* newsId : 1
* newUrl : http://192.168.1.197/web/upH5/consult.html?id=123&url=2
*/
private int newsId;
private String newUrl;
private String newMsg;
public String getNewMsg() {
return newMsg;
}
public void setNewMsg(String newMsg) {
this.newMsg = newMsg;
}
public int getNewsId() {
return newsId;
}
public void setNewsId(int newsId) {
this.newsId = newsId;
}
public String getNewUrl() {
return newUrl;
}
public void setNewUrl(String newUrl) {
this.newUrl = newUrl;
}
}
public static class HomeShoplistBean {
/**
* shopId : 1
* shopImgUrl : http://up.enterdesk.com/edpic_source/e2/37/f1/e237f1f737e24dc0dbd6030a22b72005.jpg
* shopName : 朝阳-双井|100㎡
* shopAddress : 广平门黄平路平米
* shopTags : [{"tag":"随便四字"},{"tag":"临近地铁"},{"tag":"最多四字"}]
* shopMonery : 8000
* shopMoneryUnit : 元/月
*/
private int shopId;
private String shopImgUrl;
private String shopName;
private String shopAddress;
private String shopMonery;
private String shopMoneryUnit;
private List shopTags;
public int getShopId() {
return shopId;
}
public void setShopId(int shopId) {
this.shopId = shopId;
}
public String getShopImgUrl() {
return shopImgUrl;
}
public void setShopImgUrl(String shopImgUrl) {
this.shopImgUrl = shopImgUrl;
}
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public String getShopAddress() {
return shopAddress;
}
public void setShopAddress(String shopAddress) {
this.shopAddress = shopAddress;
}
public String getShopMonery() {
return shopMonery;
}
public void setShopMonery(String shopMonery) {
this.shopMonery = shopMonery;
}
public String getShopMoneryUnit() {
return shopMoneryUnit;
}
public void setShopMoneryUnit(String shopMoneryUnit) {
this.shopMoneryUnit = shopMoneryUnit;
}
public List getShopTags() {
return shopTags;
}
public void setShopTags(List shopTags) {
this.shopTags = shopTags;
}
public static class ShopTagsBean {
/**
* tag : 随便四字
*/
private String tag;
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
}
}
}
主要的功能代码就是这些,一会我会给源码链接,MVP现在怎么也是Android主流的框架,学习掌握一下还是不错的。
云盘链接如果失效或有问题联系dingchao7323@qq.com
欢迎指出不足和缺点!