Android MVP開發模式有案例和源碼,反正我能看懂的MVP

 

轉載請註明出處:https://www.cnblogs.com/dingxiansen/html

  丁先森 博客園android

 

MVP 理論知識

在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。

 

使用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

歡迎指出不足和缺點!

相關文章
相關標籤/搜索