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 : 周一 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
- MVP架构实践
- 我实践中的mvp架构
- Google 官方Android MVP架构实践
- RxJava+MVP架构笑话App实践
- MVP架构在Android中的实践
- Android架构(一)MVP架构在Android中的实践
- MVP实践
- MVP实践
- mvp架构初体验之介绍与实践
- MVP架构
- MVP架构
- MVP架构
- MVP架构
- MVP架构
- 架构mvp
- MVP-架构
- Android MVP模式实践
- 类MVP实践报告
- struts2的s:iterator 标签 详解
- 嵌入式就业前景分析-----韦山东嵌入式开发群文件
- 基本的类函数和对象函数
- Struts2 控制标签:<s:if>、<s:elseif>和<s:else>
- TextInputEditText 中文翻译
- MVP架构实践
- PHP文件读写以及本地跑数据一些坑
- 中少图书管理员权限编写
- AJAX文件上传
- Struts2的工作原理(图解)详解
- Android:时间工具类 -- 时间戳转换成日期,日期转换成时间戳
- ActionContextCleanUp的作用
- 通俗易懂求半平面交(转载)
- Cassandra 配置优化(Cassandra.yaml)