轉載請註明出處:https://www.cnblogs.com/dingxiansen/html
丁先森 博客園android
在MVP 架構中跟MVC相似的是一樣也分爲三層。web
Activity 和Fragment 視爲View層,負責處理 UI。json
Presenter 爲業務處理層,既能調用UI邏輯,又能請求數據,該層爲純Java類,不涉及任何Android API。安全
Model 層中包含着具體的數據請求,數據源。網絡
三層之間調用順序爲view->presenter->model,爲了調用安全着想不可反向調用!不可跨級調用!架構
那Model 層如何反饋給Presenter 層的呢?Presenter 又是如何操控View 層呢?看圖!框架
圖是我借來的😏異步
上圖中說明了低層的不會直接給上一層作反饋,而是經過 View 、 Callback 爲上級作出了反饋,這樣就解決了請求數據與更新界面的異步操做。上圖中 View 和 Callback 都是以接口的形式存在的,其中 View 是經典 MVP 架構中定義的,Callback 是我本身加的。ide
View 中定義了 Activity 的具體操做,主要是些將請求到的數據在界面中更新之類的。
Callback 中定義了請求數據時反饋的各類狀態:成功、失敗、異常等。
MVP模式的核心思想:
MVP把Activity中的UI邏輯抽象成View接口,把業務邏輯抽象成Presenter接口,Model類仍是原來的Model。
分離了視圖邏輯和業務邏輯,下降了耦合
Activity只處理生命週期的任務,代碼變得更加簡潔
視圖邏輯和業務邏輯分別抽象到了View和Presenter的接口中去,提升代碼的可閱讀性
Presenter被抽象成接口,能夠有多種具體的實現,因此方便進行單元測試
把業務邏輯抽到Presenter中去,避免後臺線程引用着Activity致使Activity的資源沒法被系統回收從而引發內存泄露和OOM
其中最重要的有三點:
相信不少人閱讀代碼的時候,都是從Activity開始的,對着一個1000+行代碼的Activity,看了都以爲難受。
使用MVP以後,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其餘的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,並且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,之後要調整業務、刪減功能也就變得簡單許多。
通常單元測試都是用來測試某些新加的業務邏輯有沒有問題,若是採用傳統的代碼風格(習慣性上叫作MV模式,少了P),咱們可能要先在Activity裏寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,這時若是發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧從新寫吧……
MVP中,因爲業務邏輯都在Presenter裏,咱們徹底能夠寫一個PresenterTest的實現類繼承Presenter的接口,如今只要在Activity裏把Presenter的建立換成PresenterTest,就能進行單元測試了,測試完再換回來便可。萬一發現還得進行測試,那就再換成PresenterTest吧。
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
歡迎指出不足和缺點!