MVP架构实践

来源:互联网 发布:日本人残忍知乎 编辑:程序博客网 时间:2024/06/06 00:19

一.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是什么

View 对应于Activity,负责View的绘制以及与用户交互。
Model 依然是业务逻辑和实体模型。一般封装了数据库DAO或者网络获取数据的角色,或者两种数据获取方式的集合。
Presenter 负责完成View于Model间的交互。

MVP会解除View和Model的耦合,同时又带来了良好的可扩展性、可测试性,保证了系统的整洁性和灵活性。

MVP可以分离显示层和逻辑层,他们之间通过接口进行通信,减低耦合。理想化的MVP模式可以实现同一份逻辑代码搭配不同的显示界面,因为他们之间并不依赖具体,而是依赖于抽象。这使得Presenter可以运用于任何实现了View逻辑接口的UI,使之具有更广泛的适用性,保证了灵活性。

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<ResultsEntity> 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<ResultsEntity> results) {        this.results = results;    }    public int getError() {        return error;    }    public String getStatus() {        return status;    }    public String getDate() {        return date;    }    public List<ResultsEntity> getResults() {        return results;    }    public static class ResultsEntity {        private String currentCity;        private String pm25;        /**         * title : 穿衣         * zs : 炎热         * tipt : 穿衣指数         * des : 天气炎热,建议着短衫、短裙、短裤、薄型T恤衫等清凉夏季服装。         */        private List<IndexEntity> index;        /**         * date : 周一 0718日 (实时: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<WeatherDataEntity> weather_data;        public void setCurrentCity(String currentCity) {            this.currentCity = currentCity;        }        public void setPm25(String pm25) {            this.pm25 = pm25;        }        public void setIndex(List<IndexEntity> index) {            this.index = index;        }        public void setWeather_data(List<WeatherDataEntity> weather_data) {            this.weather_data = weather_data;        }        public String getCurrentCity() {            return currentCity;        }        public String getPm25() {            return pm25;        }        public List<IndexEntity> getIndex() {            return index;        }        public List<WeatherDataEntity> 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<T> {    protected Reference<T> mViewRef;    public void attachView(T view) {        mViewRef = new WeakReference<T>(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<WeatherView> {    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<String, String> getParams() {                return null;            }        }, new BaseResponse<WeatherEntity>() {            @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<WeatherEntity> 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();    }}

其他入门demo
登陆案例:https://segmentfault.com/a/1190000003927200
天气预报案例:http://rocko.xyz/2015/02/06/Android%E4%B8%AD%E7%9A%84MVP/

2.项目级demo

https://github.com/maoruibin/GankDaily
http://p.codekk.com/detail/Android/gzsll/TLint

这里个人感觉TLint的MVP实践要更好一些。有需要的可以直接去看源码。

三.一些问题和难点

这里针对方案1:

  1. 例如当应用进入后台且内存不足的时候,系统是会回收这个Activity的。通常我们都知道要用OnSaveInstanceState()去保存状态,用OnRestoreInstanceState()去恢复状态。 但是在我们的MVP中,View层是不应该去直接操作Model的,这样做不合理,同时也增大了M与V的耦合。
  2. 界面复用问题。通常我们在APP最初版本中是无法预料到以后会有什么变动的,例如我们最初使用一个Fragment去作为界面的显示,后来在版本变动中发现这个Fragment越来越庞大,而Fragment的生命周期又太过复杂造成很多难以理解的BUG,我们需要把这个界面放到一个Activity中实现。这时候就麻烦了,要把Fragment转成Activity,这可不仅仅是改改类名的问题,更多的是一大堆生命周期需要去修改。例如参考文章2中的译者就遇到过这样的问题。
  3. Activity本身就是Android中的一个Context。不论怎么去封装,都难以避免将业务逻辑代码写入到其中。
  4. 如何防止activity等等内存泄露。这个问题非常严重,好好想一下有无好的方案。
  5. V,P对应的比例关系。

四.参考资料

https://segmentfault.com/a/1190000003927200
http://blog.csdn.net/lmj623565791/article/details/46596109
http://www.kymjs.com/code/2015/11/09/01
http://www.jianshu.com/p/9a6845b26856
https://github.com/122627018/BaseMVP
http://m.h5.com.cn/news/anzhuo/19287.html

1 0