天天看點

Android MVP設計模式執行個體詳解

1.MVP設計模式簡介

相信很多開發者對MVP設計模式都有比較深刻的了解,這種設計模式運用非常廣。在實際的Android項目開發中,MVP設計模式深受開發者的喜愛,因為MVP将前背景完全分離,降低了Model和View的耦合。

MVP,全程:Model-View-Presenter,即模型-視圖-層現器。其中,将Activity視為View層,Presenter負責完成View與Model的互動,Model依然是業務邏輯和實體模型。下面用一張圖了解資料與視圖之間的互動。

Android MVP設計模式執行個體詳解

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核心優點。

Android MVP設計模式執行個體詳解

3.MVP與MVC模式差別

Android MVP設計模式執行個體詳解

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.代碼實作

  1. 代碼檔案結構
    Android MVP設計模式執行個體詳解
  2. 建立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;
        }
    }
               
    擷取的手機資訊包括手機号碼、省份、營運商和所屬營運商
  3. 實作MvpMainView接口,用于抽象出View顯示方法
    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();
    
    }
               
    兩個方法一個為顯示資訊,另一個用于更新UI。對于繼承的MVPLoadingView中兩個方法用于顯示和隐藏Dialog。
  4. 實作MainPresenter類。主要是擷取網絡資料以及資料的解析等操作

    判斷手機号的合法性

    public void searchPhoneInfo(String phone){
            if (phone.length() != 11){
                mvpMainView.showToash("請輸入正确的手機号");
                return;
            }
            mvpMainView.showLoading();
    
        }
               
    處理網絡資料
    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);
        }
               
    JsonObject方法解析資料:
    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;
        }
               
    Gson方法解析資料:
    public Phone parseModelWithGson(String json) {
    
            Gson gson = new Gson();
            Phone phone = gson.fromJson(json, Phone.class);
    
            return phone;
        }
               
    FastJson方法解析資料:
    public Phone parseModelWithFastJson(String json) {
            Phone phone = JSONObject.parseObject(json, Phone.class);
    
            return phone;
        }
               
    此處為MainPresenter.class完整代碼
    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;
        }
    }
    
    
    
    
    
    
    
    
    
    
    
    
               
  5. 網絡請求處理

    get方法

    public void sendGetHttp(String url, Map<String, String>param){
            sendHttp(url, param, false);
        }
               
    post方法
    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;
        }
    
    }
    
    
    
    
    
    
    
    
    
    
    
               
  6. MainActivity實作相關UI的顯示及接口的處理

    擷取資料後更新界面

    @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);
           }
    
        }
               
    實作MVPLoadingView中方法,顯示加載彈窗與隐藏彈窗
    @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();
            }
        }
               
  7. 實作效果圖
    Android MVP設計模式執行個體詳解

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