1.MVP設計模式簡介
相信很多開發者對MVP設計模式都有比較深刻的了解,這種設計模式運用非常廣。在實際的Android項目開發中,MVP設計模式深受開發者的喜愛,因為MVP将前背景完全分離,降低了Model和View的耦合。
MVP,全程:Model-View-Presenter,即模型-視圖-層現器。其中,将Activity視為View層,Presenter負責完成View與Model的互動,Model依然是業務邏輯和實體模型。下面用一張圖了解資料與視圖之間的互動。
MVP模式通過Presenter實作資料和視圖之間的互動,簡化了Activity的職責。同時即避免了View和Model的直接聯系,又通過Presenter實作兩者之間的溝通。
2.MVP模式架構圖
MVP(Model-View-Presenter)是從MVC的演變過來的,MVP的角色定義:
- 模型層(Model):主要是擷取資料功能,業務邏輯和實體模型。
- 視圖層(View):對應于Activity或Fragment,負責視圖的部分展示和業務邏輯使用者互動
- 控制層(Presenter):負責完成View層與Model層間的互動,通過P層來擷取M層中資料後傳回給V層,使得V層與M層間沒有耦合。
在MVP中 ,Presenter層完全将View層和Model層進行了分離,把主要程式邏輯放在Presenter層實作,Presenter與具體的View層(Activity)是沒有直接的關聯,是通過定義接口來進行互動的,進而使得當View層(Activity)發生改變時,Persenter依然可以保持不變。View層接口類隻應該隻有set/get方法,及一些界面顯示内容和使用者輸入,除此之外不應該有多餘的内容。絕不允許View層直接通路Model層,這是與MVC最大差別之處,也是MVP核心優點。
3.MVP與MVC模式差別
MVC中是允許Model和View進行互動的,而MVP中很明顯,Model與View之間的互動由Presenter完成。還有一點就是Presenter與View之間的互動是通過接口的。
還有一點注意:MVC中V對應的是布局檔案,MVP中V對應的是Activity。
1.Activity職責不同,Activity在MVP中是View層,在MVC中是Controller層,這是MVC和MVP很主要的一個差別,可以說Android從MVC轉向MVP開發也主要是優化Activity的代碼,避免Activity的代碼臃腫龐大。
2.View層不同,MVC的View層指的是XML布局檔案或者是用Java自定義的View,MVP的View層是Activity或者Fragment。使用傳統的MVC,其中的View,對應的是各種Layout布局檔案,但是這些布局檔案中并不像Web端那樣強大,能做的事情非常有限。MVP的View層Activity在實際項目中,随着邏輯的複雜度越來越大,Activity臃腫的缺點仍然展現出來了,因為Activity中還是充滿了大量與View層無關的代碼,比如各種事件的處理派發,就如MVC中的那樣View層和Controller代碼耦合在一起無法自拔。
3.控制層不同,MVC的控制層是Activity,或者是Fragment,Controller對應的是Activity,而Activity中卻又具有操作UI的功能,我們在實際的項目中也會有很多UI操作在這一層,也做了很多View中應該做的事情,當然Controller層Activity中也包含Controller應該做的事情,比如各種事件的派發回調,而且在一層中我們會根據事件再去調用Model層操作資料,是以這種MVC的方式在實際項目中,Activity所在的Controller是非常重的,各層次之間的耦合情況也比較嚴重,不友善單元測試。MVP的控制層是Presenter,裡面沒有很多的實際東西,主要是做Model和View層的互動。
4.關系鍊不同,MVP中Model層與View是沒有關系的,彼此不會通訊和操作,Model與View的通訊都是Presenter層來傳達的。但是在MVC中,Model層和View是曾在互動的。比如我們自定義的View控件裡面肯定是要使用Model的資料的,View也要根據不同的Model資料做出不同的展現!這點尤其是展現在自定義的View中,自定義View需要設定資料,使用者操作了自定義控件需要改變資料,View要操作Model怎麼辦?有人說把Controller傳到自定義的View啊,現實是不可能沒一個自定義View都去持有Controller的引用,其實在MVP中就不會這麼尴尬,接口就可以完成。
5.适用範圍不同,在Android中,MVP和MVC都用自己的适用情況,使用MVP可以更好的解耦三大子產品,子產品之間比較清晰,也很友善使用MVP來元件化架構整體項目。但是MVC也是有用武之地的,在元件化的Module或者中間件我們可以使用MVC來做,Module或者中間件不會存在很複雜的View層,使用MVC可以更加友善我們實作功能。
6.互動方式不同,MVP中通訊互動基本都是通過接口的,MVC中的通訊互動很多時候都是實打實的調用對象的方法,簡單粗暴!
7.實作方法不同 ,MVC和MVP的Model幾乎一樣的,都是處理資料,隻要不在Activity或者Fragment中請求資料,其他的所有控制都放在Activity或者Fragment中,這樣寫就基本是MVC的模式,這樣寫不麻煩,但是很容易把Activity寫出上萬行代碼。用MVP的時候我們需要寫很多View和Presenter接口來實作子產品之間的通訊,會增加很多類。
4.MVP與MVC優缺點對比
(1)相同點:
優點:
1.降低耦合度
2.子產品職責劃分明顯
3.利于測試驅動開發
4.代碼複用
5.隐藏資料
6.代碼靈活性
缺點:
額外的代碼複雜度及學習成本。
(2)不同點:
MVP模式:
1.View不直接與Model互動,而是通過與Presenter互動來與Model間接互動
2.Presenter與View的互動是通過接口來進行的,更有利于添加單元測試
3.通常View與Presenter是一對一的,但複雜的View可能綁定多個Presenter來處理邏輯,業務相似的時候也可以多同個View共享一個Presenter。
MVC模式:
1.View可以與Model直接互動
2.Controller是基于行為的,并且可以被多個View共享
3.Controller可以負責決定顯示哪個View
5.代碼實作
- 代碼檔案結構
- 建立model
擷取的手機資訊包括手機号碼、省份、營運商和所屬營運商package com.mvpdemo.model; /** * Title: MVPDemo * Package name: com.mvpdemo.model * Created by liqh ([email protected]) * Date: 2018/9/28 17:50 * Description: {description} */ public class Phone { private String telString; private String province; private String catName; private String carrier; public String getTelString() { return telString; } public void setTelString(String telString) { this.telString = telString; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCatName() { return catName; } public void setCatName(String catName) { this.catName = catName; } public String getCarrier() { return carrier; } public void setCarrier(String carrier) { this.carrier = carrier; } }
- 實作MvpMainView接口,用于抽象出View顯示方法
兩個方法一個為顯示資訊,另一個用于更新UI。對于繼承的MVPLoadingView中兩個方法用于顯示和隐藏Dialog。package com.mvpdemo.presenter; /** * Title: MVPDemo * Package name: com.mvpdemo.presenter * Created by liqh ([email protected]) * Date: 2018/9/28 17:52 * Description: {description} */ public interface MvpMainView extends MvpLoadingView{ void showToash(String msg); void updateView(); }
-
實作MainPresenter類。主要是擷取網絡資料以及資料的解析等操作
判斷手機号的合法性
處理網絡資料public void searchPhoneInfo(String phone){ if (phone.length() != 11){ mvpMainView.showToash("請輸入正确的手機号"); return; } mvpMainView.showLoading(); }
JsonObject方法解析資料:private void sendHttp(String phone) { Map<String,String> map = new HashMap<String, String>(); map.put("tel", phone); HttpUntil httpUntil = new HttpUntil(new HttpUntil.HttpResonse() { @Override public void onSuccess(Object object) { String json = object.toString(); int index = json.indexOf("{"); json = json.substring(index, json.length()); //JsonObject // mPhone = parseModelWithOrgJson(json); //Gson mPhone = parseModelWithGson(json); //FastJson // mPhone = parseModelWithFastJson(json); mvpMainView.hideLoading(); mvpMainView.updateView(); } @Override public void onFail(String error) { mvpMainView.showToash(error); mvpMainView.hideLoading(); } }); httpUntil.sendGetHttp(mUrl, map); }
Gson方法解析資料:public Phone parseModelWithOrgJson(String json) { Phone phone = new Phone(); try { org.json.JSONObject jsonObject = new org.json.JSONObject(json); String value = jsonObject.getString("telString"); phone.setTelString(value); value = jsonObject.getString("province"); phone.setProvince(value); value = jsonObject.getString("catName"); phone.setCatName(value); value = jsonObject.getString("carrier"); phone.setCarrier(value); } catch (JSONException e) { e.printStackTrace(); } return phone; }
FastJson方法解析資料:public Phone parseModelWithGson(String json) { Gson gson = new Gson(); Phone phone = gson.fromJson(json, Phone.class); return phone; }
此處為MainPresenter.class完整代碼public Phone parseModelWithFastJson(String json) { Phone phone = JSONObject.parseObject(json, Phone.class); return phone; }
package com.mvpdemo.presenter.impl; import com.alibaba.fastjson.JSONObject; import com.google.gson.Gson; import org.json.JSONException; import java.util.HashMap; import java.util.Map; import com.mvpdemo.business.HttpUntil; import com.mvpdemo.model.Phone; import com.mvpdemo.presenter.MvpMainView; /** * Title: MVPDemo * Package name: com.mvpdemo.presenter.impl * Created by liqh ([email protected]) * Date: 2018/9/28 17:57 * Description: {description} */ public class MainPresenter extends BasePresenter{ private MvpMainView mvpMainView; private String mUrl = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm"; private Phone mPhone; public Phone getPhoneInfo(){ return mPhone; } public MainPresenter(MvpMainView mainView){ mvpMainView = mainView; } public void searchPhoneInfo(String phone){ if (phone.length() != 11){ mvpMainView.showToash("請輸入正确的手機号"); return; } mvpMainView.showLoading(); //http請求的處理邏輯 sendHttp(phone); } private void sendHttp(String phone) { Map<String,String> map = new HashMap<String, String>(); map.put("tel", phone); HttpUntil httpUntil = new HttpUntil(new HttpUntil.HttpResonse() { @Override public void onSuccess(Object object) { String json = object.toString(); int index = json.indexOf("{"); json = json.substring(index, json.length()); //JsonObject // mPhone = parseModelWithOrgJson(json); //Gson mPhone = parseModelWithGson(json); //FastJson // mPhone = parseModelWithFastJson(json); mvpMainView.hideLoading(); mvpMainView.updateView(); } @Override public void onFail(String error) { mvpMainView.showToash(error); mvpMainView.hideLoading(); } }); httpUntil.sendGetHttp(mUrl, map); } public Phone parseModelWithGson(String json) { Gson gson = new Gson(); Phone phone = gson.fromJson(json, Phone.class); return phone; } public Phone parseModelWithFastJson(String json) { Phone phone = JSONObject.parseObject(json, Phone.class); return phone; } public Phone parseModelWithOrgJson(String json) { Phone phone = new Phone(); try { org.json.JSONObject jsonObject = new org.json.JSONObject(json); String value = jsonObject.getString("telString"); phone.setTelString(value); value = jsonObject.getString("province"); phone.setProvince(value); value = jsonObject.getString("catName"); phone.setCatName(value); value = jsonObject.getString("carrier"); phone.setCarrier(value); } catch (JSONException e) { e.printStackTrace(); } return phone; } }
-
網絡請求處理
get方法
post方法public void sendGetHttp(String url, Map<String, String>param){ sendHttp(url, param, false); }
建立網絡請求public void sendPostHttp(String url, Map<String, String>param){ sendHttp(url, param, true); }
完整代碼private Request createRequest(boolean isPost){ Request request; if (isPost){ MultipartBody.Builder requestBodyBuilder = new MultipartBody.Builder(); requestBodyBuilder.setType(MultipartBody.FORM); //周遊map請求參數 Iterator<Map.Entry<String, String>> iterator = mParam.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<String, String> entry = iterator.next(); requestBodyBuilder.addFormDataPart(entry.getKey(), entry.getValue()); } request = new okhttp3.Request.Builder().url(mUrl) .post(requestBodyBuilder.build()).build(); }else{ String urlStr = mUrl; //帶參請求 if (mParam != null){ urlStr = mUrl + "?" + MapParamToString(mParam); } request = new Request.Builder().url(urlStr).build(); } return request; }
package com.mvpdemo.business; import android.os.Handler; import android.os.Looper; import java.io.IOException; import java.util.Iterator; import java.util.Map; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; /** * Title: MVPDemo * Package name: com.mvpdemo.business * Created by liqh ([email protected]) * Date: 2018/9/28 18:05 * Description: {description} */ public class HttpUntil { String mUrl; Map<String, String> mParam; HttpResonse mHttpResonse; Handler myHandler = new Handler(Looper.getMainLooper()); private final OkHttpClient client = new OkHttpClient(); public HttpUntil(HttpResonse httpResonse){ mHttpResonse = httpResonse; } public interface HttpResonse{ void onSuccess(Object object); void onFail(String error); } public void sendPostHttp(String url, Map<String, String>param){ sendHttp(url, param, true); } public void sendGetHttp(String url, Map<String, String>param){ sendHttp(url, param, false); } public void sendHttp(String url, Map<String, String>param, boolean isPost){ mUrl = url; mParam = param; run(isPost); } private void run(boolean isPost){ Request request = createRequest(isPost); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { if(mHttpResonse != null){ myHandler.post(new Runnable() { @Override public void run() { mHttpResonse.onFail("Fail"); } }); } } @Override public void onResponse(Call call, final Response response) throws IOException { if(mHttpResonse == null) return; myHandler.post(new Runnable() { @Override public void run() { if (!response.isSuccessful()){ mHttpResonse.onFail("fail,code:" + response); }else{ try { mHttpResonse.onSuccess(response.body().string()); } catch (IOException e) { e.printStackTrace(); mHttpResonse.onFail("轉換失敗"); } } } }); } }); } private Request createRequest(boolean isPost){ Request request; if (isPost){ MultipartBody.Builder requestBodyBuilder = new MultipartBody.Builder(); requestBodyBuilder.setType(MultipartBody.FORM); //周遊map請求參數 Iterator<Map.Entry<String, String>> iterator = mParam.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<String, String> entry = iterator.next(); requestBodyBuilder.addFormDataPart(entry.getKey(), entry.getValue()); } request = new okhttp3.Request.Builder().url(mUrl) .post(requestBodyBuilder.build()).build(); }else{ String urlStr = mUrl; //帶參請求 if (mParam != null){ urlStr = mUrl + "?" + MapParamToString(mParam); } request = new Request.Builder().url(urlStr).build(); } return request; } private String MapParamToString(Map<String, String>param){ StringBuilder stringBuilder = new StringBuilder(); Iterator<Map.Entry<String, String>> iterator = param.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<String, String> entry = iterator.next(); stringBuilder.append(entry.getKey() + "=" + entry.getValue() + "&"); } String str = stringBuilder.toString().substring(0, stringBuilder.length()); return str; } }
-
MainActivity實作相關UI的顯示及接口的處理
擷取資料後更新界面
實作MVPLoadingView中方法,顯示加載彈窗與隐藏彈窗@Override public void updateView() { Phone phone = mainPresenter.getPhoneInfo(); if (phone != null){ mShowInfoLayout.setVisibility(View.VISIBLE); result_phone.setText("手機号:" + phone.getTelString()); result_province.setText("省份:" + phone.getProvince()); result_type.setText("營運商:" + phone.getCatName()); result_carrier.setText("歸屬營運商:" + phone.getCarrier()); }else{ mShowInfoLayout.setVisibility(View.GONE); } }
@Override public void showLoading() { if (progressDialog == null){ progressDialog = ProgressDialog.show(this, "", "正在加載", true,false); }else if (progressDialog.isShowing()){ progressDialog.setTitle(""); progressDialog.setMessage("正在加載..."); } progressDialog.show(); }
@Override public void hideLoading() { if (progressDialog != null && progressDialog.isShowing()){ progressDialog.dismiss(); } }
- 實作效果圖
6.總結
MVP架構模式完全将Model模型和View視圖分離,進而使得代碼的耦合低,利用MVP架構寫項目達到解耦作用。 MVP和MVC最大的差別是:MVC中的V和C關系比較緊密,耦合度太高,從C中通路M擷取資料一定程度上也可以看成從V中通路M。而MVP中M和V完全分離,互相不知道對方的存在,Presenter通過接口通信方式将V和M通信。 在Android中MVP架構 Activity擔當View視圖層,MVC架構模式Activity擔當控制器。
7.源碼下載下傳
下載下傳位址:https://download.csdn.net/download/u012721519/10706788
Good luck!
Write by Jimmy.li