Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> MVP架構實踐

MVP架構實踐

編輯:關於Android編程

一.MVP理論簡介

1.為何要在android中引入MVP

??在Android項目中,Activity和Fragment占據了大部分的開發工作。而MVP設計模式可以優化Activity和Fragment的代碼。

??相信很多人閱讀代碼的時候,都是從Activity開始的,對著一個1000+行代碼的Activity,看了都覺得難受。

??使用MVP之後,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其他的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,而且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,以後要調整業務、刪減功能也就變得簡單許多。

MVP模式優點:

分離了視圖邏輯和業務邏輯,降低了耦合 Activity只處理生命周期的任務,代碼變得更加簡潔 視圖邏輯和業務邏輯分別抽象到了View和Presenter的接口中去,提高代碼的可閱讀性 Presenter被抽象成接口,可以有多種具體的實現,所以方便進行單元測試 把業務邏輯抽到Presenter中去,避免後台線程引用著Activity導致Activity的資源無法被系統回收從而引起內存洩露和OOM

2.MVP是什麼

\vcq9tcS8r7rPoaM8YnIgLz4NClByZXNlbnRlciC4utTwzeqzyVZpZXfT2k1vZGVsvOS1xL27u6WhozwvcD4NCjxwPk1WULvhveKz/VZpZXe6zU1vZGVstcTx7rrPo6zNrMqx09a0+MC0wcvBvLrDtcS/ycCp1bnQ1KGiv8my4srU0NSjrLGj1qTBy8+1zbO1xNX7veDQ1LrNwem77tDUoaM8L3A+DQo8cD5NVlC/ydLUt9bA68/Uyr6y47rNwt+8rbLjo6zL+8PH1q685M2ouf2907/avfjQ0M2o0MWjrLz1tc3x7rrPoaPA7c/ru6+1xE1WUMSjyr2/ydLUyrXP1s2s0ru33cLfvK20+sLrtO7F5LK7zay1xM/Uyr6958Pmo6zS8s6qy/vDx9auvOSyorK70sDAtb7fzOWjrLb4ysfSwMC109qz6c/zoaPV4sq5tcNQcmVzZW50ZXK/ydLU1MvTw9PayM66zsq1z9bBy1ZpZXfC37ytvdO/2rXEVUmjrMq51q6+39PQuPy547e6tcTKytPD0NSjrLGj1qTBy8Hpu+7Q1KGjPC9wPg0KPGgyIGlkPQ=="3mvp與mvc的區別">3.MVP與MVC的區別

\

??其實最明顯的區別就是,MVC中是允許Model和View進行交互的,而MVP中很明顯,Model與View之間的交互由Presenter完成。還有一點就是Presenter與View之間的交互是通過接口的(代碼中會體現)。

4.android中的MVP實現方案

方案1:MVP把Activity中的UI邏輯抽象成View接口,把業務邏輯抽象成Presenter接口,Model類還是原來的Model。這就是MVP模式,現在這樣的話,Activity的工作的簡單了,只用來響應生命周期,其他工作都丟到Presenter中去完成。

方案2:Activity作為Presenter,也有這種方案,不常見,也不易理解,這裡不講解。以下代碼也主要以方案1為主。

二.android MVP實戰demo

1.入門demo

public class WeatherEntity {

    /**
     * error : 0
     * status : success
     * date : 2016-07-18
     * results : [{"currentCity":"花都","pm25":"","index":[{"title":"穿衣","zs":"炎熱","tipt":"穿衣指數","des":"天氣炎熱,建議著短衫、短裙、短褲、薄型T恤衫等清涼夏季服裝。"},{"title":"洗車","zs":"較適宜","tipt":"洗車指數","des":"較適宜洗車,未來一天無雨,風力較小,擦洗一新的汽車至少能保持一天。"},{"title":"旅游","zs":"一般","tipt":"旅游指數","des":"天氣較好,同時有微風相伴,但溫度較高,天氣熱,請盡量避免高溫時段外出,若外出請注意防暑降溫和防曬。"},{"title":"感冒","zs":"少發","tipt":"感冒指數","des":"各項氣象條件適宜,發生感冒機率較低。但請避免長期處於空調房間中,以防感冒。"},{"title":"運動","zs":"較適宜","tipt":"運動指數","des":"天氣較好,戶外運動請注意防曬。推薦您進行室內運動。"},{"title":"紫外線強度","zs":"中等","tipt":"紫外線強度指數","des":"屬中等強度紫外線輻射天氣,外出時建議塗擦SPF高於15、PA+的防曬護膚品,戴帽子、太陽鏡。"}],"weather_data":[{"date":"周一 07月18日 (實時:33℃)","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/qing.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/duoyun.png","weather":"晴轉多雲","wind":"微風","temperature":"35 ~ 27℃"},{"date":"周二","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/duoyun.png","weather":"多雲","wind":"微風","temperature":"35 ~ 27℃"},{"date":"周三","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/duoyun.png","weather":"多雲","wind":"微風","temperature":"35 ~ 27℃"},{"date":"周四","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/qing.png","weather":"多雲轉晴","wind":"微風","temperature":"35 ~ 27℃"}]}]
     */

    private int error;
    private String status;
    private String date;
    /**
     * currentCity : 花都
     * pm25 :
     * index : [{"title":"穿衣","zs":"炎熱","tipt":"穿衣指數","des":"天氣炎熱,建議著短衫、短裙、短褲、薄型T恤衫等清涼夏季服裝。"},{"title":"洗車","zs":"較適宜","tipt":"洗車指數","des":"較適宜洗車,未來一天無雨,風力較小,擦洗一新的汽車至少能保持一天。"},{"title":"旅游","zs":"一般","tipt":"旅游指數","des":"天氣較好,同時有微風相伴,但溫度較高,天氣熱,請盡量避免高溫時段外出,若外出請注意防暑降溫和防曬。"},{"title":"感冒","zs":"少發","tipt":"感冒指數","des":"各項氣象條件適宜,發生感冒機率較低。但請避免長期處於空調房間中,以防感冒。"},{"title":"運動","zs":"較適宜","tipt":"運動指數","des":"天氣較好,戶外運動請注意防曬。推薦您進行室內運動。"},{"title":"紫外線強度","zs":"中等","tipt":"紫外線強度指數","des":"屬中等強度紫外線輻射天氣,外出時建議塗擦SPF高於15、PA+的防曬護膚品,戴帽子、太陽鏡。"}]
     * weather_data : [{"date":"周一 07月18日 (實時:33℃)","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/qing.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/duoyun.png","weather":"晴轉多雲","wind":"微風","temperature":"35 ~ 27℃"},{"date":"周二","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/duoyun.png","weather":"多雲","wind":"微風","temperature":"35 ~ 27℃"},{"date":"周三","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/duoyun.png","weather":"多雲","wind":"微風","temperature":"35 ~ 27℃"},{"date":"周四","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/qing.png","weather":"多雲轉晴","wind":"微風","temperature":"35 ~ 27℃"}]
     */

    private List results;

    public void setError(int error) {
        this.error = error;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public void setResults(List results) {
        this.results = results;
    }

    public int getError() {
        return error;
    }

    public String getStatus() {
        return status;
    }

    public String getDate() {
        return date;
    }

    public List getResults() {
        return results;
    }

    public static class ResultsEntity {
        private String currentCity;
        private String pm25;
        /**
         * title : 穿衣
         * zs : 炎熱
         * tipt : 穿衣指數
         * des : 天氣炎熱,建議著短衫、短裙、短褲、薄型T恤衫等清涼夏季服裝。
         */

        private List index;
        /**
         * date : 周一 07月18日 (實時:33℃)
         * dayPictureUrl : http://api.map.baidu.com/images/weather/day/qing.png
         * nightPictureUrl : http://api.map.baidu.com/images/weather/night/duoyun.png
         * weather : 晴轉多雲
         * wind : 微風
         * temperature : 35 ~ 27℃
         */

        private List weather_data;

        public void setCurrentCity(String currentCity) {
            this.currentCity = currentCity;
        }

        public void setPm25(String pm25) {
            this.pm25 = pm25;
        }

        public void setIndex(List index) {
            this.index = index;
        }

        public void setWeather_data(List weather_data) {
            this.weather_data = weather_data;
        }

        public String getCurrentCity() {
            return currentCity;
        }

        public String getPm25() {
            return pm25;
        }

        public List getIndex() {
            return index;
        }

        public List getWeather_data() {
            return weather_data;
        }

        public static class IndexEntity {
            private String title;
            private String zs;
            private String tipt;
            private String des;

            public void setTitle(String title) {
                this.title = title;
            }

            public void setZs(String zs) {
                this.zs = zs;
            }

            public void setTipt(String tipt) {
                this.tipt = tipt;
            }

            public void setDes(String des) {
                this.des = des;
            }

            public String getTitle() {
                return title;
            }

            public String getZs() {
                return zs;
            }

            public String getTipt() {
                return tipt;
            }

            public String getDes() {
                return des;
            }
        }

        public static class WeatherDataEntity {
            private String date;
            private String dayPictureUrl;
            private String nightPictureUrl;
            private String weather;
            private String wind;
            private String temperature;

            public void setDate(String date) {
                this.date = date;
            }

            public void setDayPictureUrl(String dayPictureUrl) {
                this.dayPictureUrl = dayPictureUrl;
            }

            public void setNightPictureUrl(String nightPictureUrl) {
                this.nightPictureUrl = nightPictureUrl;
            }

            public void setWeather(String weather) {
                this.weather = weather;
            }

            public void setWind(String wind) {
                this.wind = wind;
            }

            public void setTemperature(String temperature) {
                this.temperature = temperature;
            }

            public String getDate() {
                return date;
            }

            public String getDayPictureUrl() {
                return dayPictureUrl;
            }

            public String getNightPictureUrl() {
                return nightPictureUrl;
            }

            public String getWeather() {
                return weather;
            }

            public String getWind() {
                return wind;
            }

            public String getTemperature() {
                return temperature;
            }
        }
    }
}

public abstract class BasePresenter {

    protected Reference mViewRef;

    public void attachView(T view) {
        mViewRef = new WeakReference(view);
    }

    public boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }

    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
            mViewRef = null;
        }
    }

}

public class WeatherPresenter extends BasePresenter {

    public void fetchList() {
        mViewRef.get().onShowLoading();
        VolleyNetHelper.getInstance().doPost(new BaseRequest() {
            @Override
            public String getMobileApi() {
                return "http://api.map.baidu.com/telematics/v3/weather?location=guangzhou&output=json&ak=B95329fb7fdda1e32ba3e3a245193146";
            }

            @Override
            public Map getParams() {
                return null;
            }
        }, new BaseResponse() {
            @Override
            public void onSuccess(WeatherEntity o) {
                mViewRef.get().onHideLoading();
                mViewRef.get().onFetchDataSuccess(o);
            }

            @Override
            public void onError(String msg) {
                mViewRef.get().onHideLoading();
                mViewRef.get().onFetchDataError(msg);
            }

            @Override
            public Class getResponseClass() {
                return WeatherEntity.class;
            }
        });


    }


}
public interface WeatherView {

    void onFetchDataSuccess(WeatherEntity entity);

    void onShowLoading();

    void onHideLoading();

    void onFetchDataError(String msg);
}
public class WeahterActivity extends BaseActivity implements WeatherView {

    @InjectView(R.id.weather_tv)
    TextView mWeatherTv;
    private WeatherPresenter mWeatherPresenter;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_weather;
    }

    @Override
    protected void init(Bundle savedInstanceState) {
        mWeatherPresenter = new WeatherPresenter();
        mWeatherPresenter.attachView(this);
        mWeatherPresenter.fetchList();
    }

    @Override
    public void onFetchDataSuccess(WeatherEntity entity) {
        mWeatherTv.setText(entity.getDate());
    }

    @Override
    public void onShowLoading() {
        super.mLoadingDialog.showLoading(LoadingDialog.NETWORK_LOADING);
    }

    @Override
    public void onHideLoading() {
        super.mLoadingDialog.hideLoading();
    }

    @Override
    public void onFetchDataError(String msg) {
        ToastUtils.longShow(msg);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        mWeatherPresenter.detachView();
    }
}

2.項目級demo

https://github.com/maoruibin/GankDaily

這裡個人感覺TLint的MVP實踐要更好一些。有需要的可以直接去看源碼。

三.一些問題和難點

這裡針對方案1:

例如當應用進入後台且內存不足的時候,系統是會回收這個Activity的。通常我們都知道要用OnSaveInstanceState()去保存狀態,用OnRestoreInstanceState()去恢復狀態。 但是在我們的MVP中,View層是不應該去直接操作Model的,這樣做不合理,同時也增大了M與V的耦合。 界面復用問題。通常我們在APP最初版本中是無法預料到以後會有什麼變動的,例如我們最初使用一個Fragment去作為界面的顯示,後來在版本變動中發現這個Fragment越來越龐大,而Fragment的生命周期又太過復雜造成很多難以理解的BUG,我們需要把這個界面放到一個Activity中實現。這時候就麻煩了,要把Fragment轉成Activity,這可不僅僅是改改類名的問題,更多的是一大堆生命周期需要去修改。例如參考文章2中的譯者就遇到過這樣的問題。 Activity本身就是Android中的一個Context。不論怎麼去封裝,都難以避免將業務邏輯代碼寫入到其中。 如何防止activity等等內存洩露。這個問題非常嚴重,好好想一下有無好的方案。 V,P對應的比例關系。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved