Android MVP开发模式有案例和源码,反正我能看懂的MVP
转载请注明出处: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模式的核心思想:
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> T request(int requestFlag); <T> 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<JsonDataBean.HomeShoplistBean> 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> T request(int requestFlag) { return null; } @Override public <T> 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 interface ITwoAPresenter { void getData(); }
TwoAPresenterImpl
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<String>() { @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<String, String> getParams() throws AuthFailureError { Map<String, String> map = new HashMap<String, String>(); 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<HomeImgurlBean> home_imgurl, List<HomeH5urlBean> home_h5url, List<HomeNewsBean> home_news, List<HomeShoplistBean> 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<HomeImgurlBean> home_imgurl; private List<HomeH5urlBean> home_h5url; private List<HomeNewsBean> home_news; private List<HomeShoplistBean> 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<HomeImgurlBean> getHome_imgurl() { return home_imgurl; } public void setHome_imgurl(List<HomeImgurlBean> home_imgurl) { this.home_imgurl = home_imgurl; } public List<HomeH5urlBean> getHome_h5url() { return home_h5url; } public void setHome_h5url(List<HomeH5urlBean> home_h5url) { this.home_h5url = home_h5url; } public List<HomeNewsBean> getHome_news() { return home_news; } public void setHome_news(List<HomeNewsBean> home_news) { this.home_news = home_news; } public List<HomeShoplistBean> getHome_shoplist() { return home_shoplist; } public void setHome_shoplist(List<HomeShoplistBean> 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<ShopTagsBean> 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<ShopTagsBean> getShopTags() { return shopTags; } public void setShopTags(List<ShopTagsBean> 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主流的框架,学习掌握一下还是不错的。
代码下载:链接:https://pan.baidu.com/s/1DVZ73LHg7KwGVU0Zp6HAqA 密码:pqdc
云盘链接如果失效或有问题联系dingchao7323@qq.com
欢迎指出不足和缺点!