通过简单案例来说明MVP的使用,retrofit2+rxjava+mvp
项目地址:www.github.com/jjdxmashl/j…
##前言
###什么是MVP?
MVP模式是一种架构模式,也是一种经典的界面模式。MVP中的M代表Model, V是View, P是Presenter。
Model 一部分是处理业务逻辑,一部分是提供View显示的数据。
View 代表的是一个接口,一个将UI界面提炼而抽象出来的接口。
Presenter Model和View之间的桥梁
###MVP在Android项目中的其中一种体现方式
经过查阅网上一些MVP的文章之后,有部分案例在presenter中实现具体的逻辑或者把Model单纯的看作是具体的Bean,个人觉得是不太准确的,MVX(MVC、MVP和MVVM)中,M的职责都应该包含两部分业务逻辑和提供View显示的数据,而X的部分则是为了实现UI界面和业务逻辑解耦的桥梁,在Android项目中使用MVP架构模式,以下这两种架构方式是我比较能接受和认可的。
按照模块分包
|----包名
| |----base
| | BaseActivity Activity基类
| | BaseMVPActivity MVP Activity基类
| | BaseModel Model基类
| | BaseFragment Fragment基类
| | IBaseDelegate 简化Presenter在Activity的实现
| | IBasePresenter Presenter基类
| | IBaseView View基类
| |----模块名1
| | |----model 业务逻辑和bean
| | | xxxModel
| | | xxxBean
| | |----presenter 连接View和Model的桥梁
| | | xxxPresenter
| | |----ui UI界面相关的类
| | | xxxActivity
| | | xxxFragment
| | |----view UI界面提炼出来的接口
| | | xxxView
| |----模块名2复制代码
按照功能分包
|----包名
| |----activity 具体Activity
| | xxxActivity
| |----adapter 具体Adapter
| | xxxAdapter
| |----base
| | BaseActivity Activity基类
| | BaseMVPActivity MVP Activity基类
| | BaseModel Model基类
| | BaseFragment Fragment基类
| | IBaseDelegate 简化Presenter在Activity的实现
| | IBasePresenter Presenter基类
| | IBaseView View基类
| |----fragment 具体Fragment
| | xxxFragment
| |----hodler 具体Holder
| | xxxHodler
| |----model 业务逻辑和bean
| | xxxModel
| | xxxBean
| |----presenter 连接View和Model的桥梁
| | xxxPresenter
| |----view UI界面提炼出来的接口
| | xxxView
| |----widget复制代码
##前期准备
这里使用聚合数据提供的免费API来实现两个具体的功能历史上的今天和笑话大全,注册并实名为聚合数据的用户后,生成属于自己的用户key即可。
##快速开始
###step1 添加所需的依赖和权限
新建一个项目,在根目录的build.gradle的dependencies节点中添加,用于注解
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'复制代码
如图
主程序app module中build.gradle的第二行添加,用于注解
apply plugin: 'com.neenbedankt.android-apt'复制代码
dependencies节点中添加
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:design:24.2.1'
//布局注解
apt 'com.jakewharton:butterknife-compiler:8.0.1'
compile 'com.jakewharton:butterknife:8.0.1'
//响应式编程
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
//联网类库
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
compile 'com.dou361.retrofit2:jjdxm-retrofit-converter-fastjson:1.0.0'
compile 'com.squareup.okhttp3:okhttp:3.3.0'
//自定义view
compile 'com.dou361.customui:jjdxm-customui:1.0.9'
//recyclerview基类
compile('com.dou361.recyclerview:jjdxm-recyclerview:1.0.2') {
exclude group: 'com.android.support', module: 'design'
}复制代码
如图
清单文件AndroidManifest.xml中,添加权限
"android.permission.INTERNET"/>复制代码
###step2
先写好两个网络请求方法,Observable searchHistory(String month, String day)和Observable loadJoke(String page)分别是查询历史今天方法和加载笑话列表
网络接口请求服务类
public interface IApiService {
/** 查询历史的今天 */
@GET("/japi/toh")
Observable searchHistory(@QueryMap Map<String, String> map);
/** 加载笑话列表 */
@GET("/joke/content/list.from")
Observable loadJoke(@QueryMap Map<String, String> map);
}复制代码
网络接口请求基类
public class ApiBase {
/**历史上的今天 http://api.juheapi.com/japi/toh?key=7ac7e02ff7f1f8f1ccdc2f9e5dddb6be&v=1
* .0&month=11&day=1*/
/** 笑话大全 http://japi.juhe.cn/joke/content/list
* .from?key=d796a03545bddee0b56d913111f5f199&page=2&pagesize=10&sort=asc&time=1418745237 */
protected static IApiService getService() {
return getService(null);
}
protected static IApiService getService(String ip) {
return getService(ip, 0, 0);
}
protected static IApiService getService(String ip, long readTime, long connectTime) {
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(readTime <= 0 ? 30 : readTime, TimeUnit.SECONDS)
.connectTimeout(connectTime <= 0 ? 30 : connectTime, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ip == null ? "http://api.juheapi.com" : ip)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(FastJsonConverterFactory.create())
.build();
return retrofit.create(IApiService.class);
}
}复制代码
网络接口请求工具类
public class ApiUtils extends ApiBase {
public static Observable searchHistory(String month, String day) {
/**key=7ac7e02ff7f1f8f1ccdc2f9e5dddb6be&v=1.0&month=11&day=1*/
Map<String, String> map = new HashMap<>();
map.put("key", "7ac7e02ff7f1f8f1ccdc2f9e5dddb6be");
map.put("v", "1.0");
map.put("month", month);
map.put("day", day);
return getService().searchHistory(map);
}
public static Observable loadJoke(String page) {
/**key=d796a03545bddee0b56d913111f5f199&page=2&pagesize=10&sort=asc&time=1418745237*/
Map<String, String> map = new HashMap<>();
map.put("key", "d796a03545bddee0b56d913111f5f199");
map.put("sort", "asc");
map.put("time", "1418745237");
map.put("page", page);
map.put("pagesize", "10");
return getService().loadJoke(map);
}
}复制代码
如图
###step3
开始架构MVP模式使用到的基类,这里没有使用网上所说的契约类xxxContract把View和Presenter写在一个类中维护,而是分开出来,主要看个人喜好,如图
UI界面抽象出来的接口
public interface IBaseView {
/**
* 显示加载
*/
void showLoading();
/**
* 完成加载
*/
void dismiss();
}复制代码
业务逻辑实现的基类
public abstract class BaseModel<SubP> {
protected SubP mPresenter;
public BaseModel(SubP presenter) {
this.mPresenter = presenter;
}
}复制代码
连接Model和View的桥梁的基类
public interface IBasePresenter {
/**绑定接口*/
void attachView(V view);
/**释放接口*/
void detachView();
}复制代码
persenter和activity绑定
public interface IBaseDelegate> {
/**初始化presenter*/
@NonNull
P createPresenter();
/**获取presenter*/
@NonNull
P getPresenter();
}复制代码
最后是Activity的基类
public abstract class BaseActivity extends
AppCompatActivity {
protected void startActivity(Class> clz) {
Intent intent = new Intent(this, clz);
startActivity(intent);
}
}复制代码
MVP的Activity基类
public abstract class BaseMVPActivity<V extends IBaseView, P extends IBasePresenter<V>> extends
BaseActivity implements IBaseDelegate<V, P> {
protected P mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();
}
@NonNull
@Override
public P getPresenter() {
return mPresenter;
}
@Override
protected void onDestroy() {
mPresenter.detachView();
super.onDestroy();
}
}复制代码
###step4
针对模块用MVP模式去架构大概有一下4个步骤
步骤1:UI实现View方法,引用Presenter
步骤2:Presenter调用Model,走Model具体逻辑
步骤3:Model逻辑实现,回调Presenter方法
步骤4:Presenter回调View,即回到UI,回调View方法复制代码
###step5
具体模块功能的实现,历史的今天模块,先创建一个HistoryActivity继承BaseMVPActivity,新建IHistoryView并实现。
####1.View的接口的抽取
抽象出来三个功能和父类IBaseView的两个方法,分别是显示加载好的数据,显示空白数据提示,检测数据提示,显示加载中提示,隐藏加载中提示。
public interface IHistoryView extends IBaseView {
/**显示数据*/
void showData(List list);
/**无数据*/
void showEmpty();
/**检测数据*/
void showMessage(String msg);
}复制代码
####2.Model的实现
具体的逻辑实现,这里只有一个方法就是查询历史今天
public class HistoryModel extends BaseModel<HistoryPresenter> {
public HistoryModel(HistoryPresenter presenter) {
super(presenter);
}
public void searchHistory(String month, String day) {
if (TextUtils.isEmpty(month)) {
mIPresenter.showMessage("月份不能为空");
return;
}
int iMonth = Integer.valueOf(month).intValue();
if (iMonth <= 0 || iMonth > 12) {
mIPresenter.showMessage("只能输入1-12的月份");
return;
}
if (TextUtils.isEmpty(day)) {
mIPresenter.showMessage("天不能为空");
return;
}
int iDay = Integer.valueOf(day).intValue();
if (iDay <= 0 || iDay > 31) {
mIPresenter.showMessage("只能输入1-31的天");
return;
}
ApiUtils.searchHistory(month, day)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe(new Action1() {
@Override
public void call(RepoHistory repoHistory) {
if (repoHistory == null || repoHistory.getResult() == null
|| repoHistory.getResult().size() <= 0) {
mIPresenter.showEmpty();
} else {
mIPresenter.showData(repoHistory.getResult());
}
}
});
}
}复制代码
####3.Presenter桥梁的实现
public class HistoryPresenter implements IBasePresenter<IHistoryView> {
private IHistoryView mView;
private HistoryModel mModel;
public HistoryPresenter(IHistoryView view) {
attachView(view);
mModel = new HistoryModel(this);
}
@Override
public void attachView(IHistoryView view) {
this.mView = view;
}
@Override
public void detachView() {
this.mView = null;
}
public void showData(List list) {
mView.dismiss();
mView.showData(list);
}
public void showEmpty() {
mView.dismiss();
mView.showEmpty();
}
public void showMessage(String msg) {
mView.showMessage(msg);
}
public void searchHistory(String month, String day) {
mView.showLoading();
mModel.searchHistory(month, day);
}
}复制代码
####4.最后在HistoryActivity里面去建立连接
最后创建的类架构图如下:
编译运行效果图如下
同理笑话大全也一样的创建对应的文件,最后运行如下
项目地址:www.github.com/jjdxmashl/j…