查看: 2125|回复: 0

[手机开发] Android MVP开发模式有案例和源码,反正我能看懂的MVP

发表于 2018-4-11 08:00:00

转载请注明出处:https://www.cnblogs.com/dingxiansen/

  丁先森 博客园
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模式的核心思想:

  1. MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。
复制代码

使用MVP的优点

  1. 分离了视图逻辑和业务逻辑,降低了耦合
  2. Activity只处理生命周期的任务,代码变得更加简洁
  3. 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
  4. Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
  5. 把业务逻辑抽到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))。

  1. 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

  1. public interface ITwoAView {
  2. //请求标记
  3. int REQUEST_ONE = 0;
  4. int REQUEST_TWO = 1;
  5. int REQUEST_THREE = 2;
  6. //响应标记
  7. int RESPONSE_ONE = 0;
  8. int RESPONSE_TWO = 1;
  9. int RESPONSE_THREE = 2;
  10. <T> T request(int requestFlag);
  11. <T> void response(T response, int responseFlag);
  12. String getToken();
  13. void showToast(String msg);
  14. }
复制代码

大多数都是自动生成的,只需要你自己到时候需要什么参数自己添加一下就行

TwoActivity

  1. /**
  2. * 测试获取数据集合,网络请求拿到集合对象
  3. */
  4. public class TwoActivity extends BaseMvpActivity implements ITwoAView {
  5. private ITwoAPresenter mITwoAPresenter;
  6. private Button btn_getdata;//请求数据按钮
  7. private EditText et_token;//模拟参数
  8. private ListView lv_data_list;//listView
  9. private List<JsonDataBean.HomeShoplistBean> jsonpuInfoEntityList;//商铺集合
  10. private JsonDataBean jsonDataBean;
  11. @Override
  12. protected void onCreate(Bundle savedInstanceState) {
  13. super.onCreate(savedInstanceState);
  14. mITwoAPresenter = new TwoAPresenterImpl(this);
  15. setContentView(R.layout.activity_two);
  16. initViewBind();
  17. }
  18. private void initViewBind() {
  19. btn_getdata = (Button) findViewById(R.id.btn_getdata);
  20. et_token = (EditText) findViewById(R.id.et_token);
  21. lv_data_list = (ListView) findViewById(R.id.lv_data_list);
  22. btn_getdata.setOnClickListener(new View.OnClickListener() {
  23. @Override
  24. public void onClick(View view) {
  25. mITwoAPresenter.getData();
  26. }
  27. });
  28. }
  29. @Override
  30. public <T> T request(int requestFlag) {
  31. return null;
  32. }
  33. @Override
  34. public <T> void response(T response, int responseFlag) {
  35. /*拿到的总的对象*/
  36. if (responseFlag == IMainAView.RESPONSE_ONE) {
  37. jsonDataBean = (JsonDataBean) response;
  38. Log.e("jsonDataBean", "返回的数据信息:" + jsonDataBean.getHome_shopline());
  39. jsonpuInfoEntityList = jsonDataBean.getHome_shoplist();
  40. PuListAdapter puListAdapter = new PuListAdapter(TwoActivity.this, jsonpuInfoEntityList);
  41. lv_data_list.setAdapter(puListAdapter);
  42. }
  43. }
  44. @Override
  45. public String getToken() {
  46. return et_token.getText().toString();
  47. }
  48. @Override
  49. public void showToast(String msg) {
  50. Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
  51. }
  52. }
复制代码

ITwoAPresenter

  1. public class TwoAPresenterImpl implements ITwoAPresenter {
  2. private ITwoAView mITwoAView;
  3. private ITwoAModel mITwoAModel;
  4. public TwoAPresenterImpl(ITwoAView aITwoAView) {
  5. mITwoAView = aITwoAView;
  6. mITwoAModel = new TwoAModelImpl();
  7. }
  8. @Override
  9. public void getData() {
  10. mITwoAModel.getData(mITwoAView.getToken(), new CallBack() {
  11. @Override
  12. public void onSuccess(Object response) {
  13. mITwoAView.response(response, IMainAView.RESPONSE_ONE);
  14. mITwoAView.showToast("数据请求成功");
  15. }
  16. @Override
  17. public void onError(String t) {
  18. mITwoAView.response(mITwoAModel, IMainAView.RESPONSE_TWO);
  19. mITwoAView.showToast(t);
  20. }
  21. });
  22. }
  23. }
复制代码

ITwoAModel

  1. public interface ITwoAModel {
  2. /*请求数据*/
  3. void getData(String token, CallBack callBack);
  4. }
复制代码

TwoAModelImpl

  1. public class TwoAModelImpl implements ITwoAModel {
  2. JsonDataBean jsondatabean;
  3. @Override
  4. public void getData(String token, final CallBack callBack) {
  5. /*进行网络请求,获取数据*/
  6. // 方式二:使用静态方式创建并显示,这种进度条只能是圆条,设置title和Message提示内容
  7. if (token.equals("")) {
  8. } else {
  9. RequestQueue mQueue = Volley.newRequestQueue(AppApplication.getmContext());
  10. StringRequest stringRequest = new StringRequest(Request.Method.POST, "http://www.mockhttp.cn/mock/upzl-android-home2", new Response.Listener<String>() {
  11. @Override
  12. public void onResponse(String s) {
  13. Log.e("login", "-------获取到的idjson--------" + s.toString());
  14. Log.e("login", "-------JSON.parseObject(json).data--------" + JSON.parseObject(s.toString()).getString("data"));
  15. jsondatabean = JSON.parseObject(JSON.parseObject(s.toString()).getString("data"), JsonDataBean.class);
  16. //成功之后,传递出jsondatabean
  17. if (jsondatabean != null) {//获取到了数据
  18. callBack.onSuccess(jsondatabean);
  19. } else {
  20. callBack.onError(s);//获取失败信息
  21. }
  22. }
  23. }, new Response.ErrorListener() {
  24. @Override
  25. public void onErrorResponse(VolleyError volleyError) {
  26. }
  27. }) {
  28. @Override
  29. protected Map<String, String> getParams() throws AuthFailureError {
  30. Map<String, String> map = new HashMap<String, String>();
  31. return map;
  32. }
  33. };
  34. /*设置请求一次*/
  35. stringRequest.setRetryPolicy(
  36. new DefaultRetryPolicy(
  37. 500000,//默认超时时间,应设置一个稍微大点儿的,例如本处的500000
  38. DefaultRetryPolicy.DEFAULT_MAX_RETRIES,//默认最大尝试次数
  39. DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
  40. )
  41. );
  42. mQueue.add(stringRequest);/*请求数据*/
  43. }
  44. }
  45. }
复制代码

这里的请求和解析json是用的Volley和fastjson

JsonDataBean,这里也是用插件自动生成的,GsonFormat,只要把后台给你返回的json字符放进去,自己生成实体类,AS开发还是可以的。

  1. public class JsonDataBean {
  2. public JsonDataBean() {
  3. }
  4. public JsonDataBean(String home_shopnewnum, String home_shopline, String home_people, List<HomeImgurlBean> home_imgurl, List<HomeH5urlBean> home_h5url, List<HomeNewsBean> home_news, List<HomeShoplistBean> home_shoplist) {
  5. this.home_shopnewnum = home_shopnewnum;
  6. this.home_shopline = home_shopline;
  7. this.home_people = home_people;
  8. this.home_imgurl = home_imgurl;
  9. this.home_h5url = home_h5url;
  10. this.home_news = home_news;
  11. this.home_shoplist = home_shoplist;
  12. }
  13. /**
  14. * 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"}]
  15. * home_h5url : [{"url":"暂定"}]
  16. * 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"}]
  17. * 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":"万/月"}]
  18. * home_shopnewnum : 814
  19. * home_shopline : 77847
  20. * home_people : 20173
  21. */
  22. private String home_shopnewnum;
  23. private String home_shopline;
  24. private String home_people;
  25. private List<HomeImgurlBean> home_imgurl;
  26. private List<HomeH5urlBean> home_h5url;
  27. private List<HomeNewsBean> home_news;
  28. private List<HomeShoplistBean> home_shoplist;
  29. public String getHome_shopnewnum() {
  30. return home_shopnewnum;
  31. }
  32. public void setHome_shopnewnum(String home_shopnewnum) {
  33. this.home_shopnewnum = home_shopnewnum;
  34. }
  35. public String getHome_shopline() {
  36. return home_shopline;
  37. }
  38. public void setHome_shopline(String home_shopline) {
  39. this.home_shopline = home_shopline;
  40. }
  41. public String getHome_people() {
  42. return home_people;
  43. }
  44. public void setHome_people(String home_people) {
  45. this.home_people = home_people;
  46. }
  47. public List<HomeImgurlBean> getHome_imgurl() {
  48. return home_imgurl;
  49. }
  50. public void setHome_imgurl(List<HomeImgurlBean> home_imgurl) {
  51. this.home_imgurl = home_imgurl;
  52. }
  53. public List<HomeH5urlBean> getHome_h5url() {
  54. return home_h5url;
  55. }
  56. public void setHome_h5url(List<HomeH5urlBean> home_h5url) {
  57. this.home_h5url = home_h5url;
  58. }
  59. public List<HomeNewsBean> getHome_news() {
  60. return home_news;
  61. }
  62. public void setHome_news(List<HomeNewsBean> home_news) {
  63. this.home_news = home_news;
  64. }
  65. public List<HomeShoplistBean> getHome_shoplist() {
  66. return home_shoplist;
  67. }
  68. public void setHome_shoplist(List<HomeShoplistBean> home_shoplist) {
  69. this.home_shoplist = home_shoplist;
  70. }
  71. public static class HomeImgurlBean {
  72. /**
  73. * imgId : 1
  74. * imgUrl : http://img4.duitang.com/uploads/item/201403/27/20140327114737_w3uA3.jpeg
  75. */
  76. private int imgId;
  77. private String imgUrl;
  78. public int getImgId() {
  79. return imgId;
  80. }
  81. public void setImgId(int imgId) {
  82. this.imgId = imgId;
  83. }
  84. public String getImgUrl() {
  85. return imgUrl;
  86. }
  87. public void setImgUrl(String imgUrl) {
  88. this.imgUrl = imgUrl;
  89. }
  90. }
  91. public static class HomeH5urlBean {
  92. /**
  93. * url : 暂定
  94. */
  95. private String url;
  96. public String getUrl() {
  97. return url;
  98. }
  99. public void setUrl(String url) {
  100. this.url = url;
  101. }
  102. }
  103. public static class HomeNewsBean {
  104. /**
  105. * newsId : 1
  106. * newUrl : http://192.168.1.197/web/upH5/consult.html?id=123&url=2
  107. */
  108. private int newsId;
  109. private String newUrl;
  110. private String newMsg;
  111. public String getNewMsg() {
  112. return newMsg;
  113. }
  114. public void setNewMsg(String newMsg) {
  115. this.newMsg = newMsg;
  116. }
  117. public int getNewsId() {
  118. return newsId;
  119. }
  120. public void setNewsId(int newsId) {
  121. this.newsId = newsId;
  122. }
  123. public String getNewUrl() {
  124. return newUrl;
  125. }
  126. public void setNewUrl(String newUrl) {
  127. this.newUrl = newUrl;
  128. }
  129. }
  130. public static class HomeShoplistBean {
  131. /**
  132. * shopId : 1
  133. * shopImgUrl : http://up.enterdesk.com/edpic_source/e2/37/f1/e237f1f737e24dc0dbd6030a22b72005.jpg
  134. * shopName : 朝阳-双井|100㎡
  135. * shopAddress : 广平门黄平路平米
  136. * shopTags : [{"tag":"随便四字"},{"tag":"临近地铁"},{"tag":"最多四字"}]
  137. * shopMonery : 8000
  138. * shopMoneryUnit : 元/月
  139. */
  140. private int shopId;
  141. private String shopImgUrl;
  142. private String shopName;
  143. private String shopAddress;
  144. private String shopMonery;
  145. private String shopMoneryUnit;
  146. private List<ShopTagsBean> shopTags;
  147. public int getShopId() {
  148. return shopId;
  149. }
  150. public void setShopId(int shopId) {
  151. this.shopId = shopId;
  152. }
  153. public String getShopImgUrl() {
  154. return shopImgUrl;
  155. }
  156. public void setShopImgUrl(String shopImgUrl) {
  157. this.shopImgUrl = shopImgUrl;
  158. }
  159. public String getShopName() {
  160. return shopName;
  161. }
  162. public void setShopName(String shopName) {
  163. this.shopName = shopName;
  164. }
  165. public String getShopAddress() {
  166. return shopAddress;
  167. }
  168. public void setShopAddress(String shopAddress) {
  169. this.shopAddress = shopAddress;
  170. }
  171. public String getShopMonery() {
  172. return shopMonery;
  173. }
  174. public void setShopMonery(String shopMonery) {
  175. this.shopMonery = shopMonery;
  176. }
  177. public String getShopMoneryUnit() {
  178. return shopMoneryUnit;
  179. }
  180. public void setShopMoneryUnit(String shopMoneryUnit) {
  181. this.shopMoneryUnit = shopMoneryUnit;
  182. }
  183. public List<ShopTagsBean> getShopTags() {
  184. return shopTags;
  185. }
  186. public void setShopTags(List<ShopTagsBean> shopTags) {
  187. this.shopTags = shopTags;
  188. }
  189. public static class ShopTagsBean {
  190. /**
  191. * tag : 随便四字
  192. */
  193. private String tag;
  194. public String getTag() {
  195. return tag;
  196. }
  197. public void setTag(String tag) {
  198. this.tag = tag;
  199. }
  200. }
  201. }
  202. }
复制代码

主要的功能代码就是这些,一会我会给源码链接,MVP现在怎么也是Android主流的框架,学习掌握一下还是不错的。

代码下载:链接:https://pan.baidu.com/s/1DVZ73LHg7KwGVU0Zp6HAqA 密码:pqdc

云盘链接如果失效或有问题联系dingchao7323@qq.com

欢迎指出不足和缺点!



回复

使用道具 举报