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