在开始讲正文之前,我首先认为你已经大概分别了解了MVP、RXjava、Retrofit的知识;如果对这些知识还很不熟的话,建议先了解完这些知识在说。
本文也是简单讲MVP+rxjava+retrofit进行结合,便于初学者上手,大神勿喷,并请指正,谢谢
我知道这些文章想一下全部吸收太难(特别是RXJAVA,扔物线大神的RXjava我至少看了4遍,然后再写这边文章的时候也会对照文章不懂的地方马上翻阅),在分别了解了这些知识之后才打算将这三者结合一起,同时在github上下载了很大代码去学习,发现有的讲的太深奥初学者看不懂,有的写的太随意虽然将三者进行结合了但是结合之后的优点却没有展示出来。
在看到浅谈Android MVP设计模式(简单结合RxJava+Retrofit) 这篇文章才开始觉悟,并参考其代码也做了简单修改,谢谢这位贡献者。
效果图
先看效果图,一个简单的获取天气信息的功能。
接口数据结构
可通过数据源查看完整json数据。
项目结构图
- 1:Retrofit的接口service定义。
- 2:定义了一些基类,处理公共的方法(个人觉得这样很nice,真是学到了)。
- 3:MVP中model。
- 4:MVP中的presenter。
- 5:MVP中的view。
开发步骤
一:添加依赖
在build.gradle中添加rxjava、retrofit等依赖
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile 'com.jakewharton:butterknife:7.0.0'
/** rxjava **/
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
/** retrofit **/
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
/** okhttp **/
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
}
注意:rxjava|rxandroid版本需要一致、retrofit|rxjava版本一致;如果不一致会报错。
二:新建mvp中的v
我们知道从视图view的角度上看一次完整的请求过程包括:显示加载框–>加载数据成功(加载失败)–>更新UI(提示用户)–>关闭正在加载的框。
而这几个步骤我们可以认为大部分请求都是这样的,那么我们就可以将他封装成一个基类视图IBaseView
-
定义基类视图IBaseView
package bule.souv.com.mvp_rxjava_retrofittest_master.base; /** * 描述:视图基类 * 作者:dc on 2017/2/16 10:59 * 邮箱:597210600@qq.com */ public interface IBaseView<T> { /** * @descriptoin 请求前加载progress * @author dc * @date 2017/2/16 11:00 */ void showProgress(); /** * @descriptoin 请求结束之后隐藏progress * @author dc * @date 2017/2/16 11:01 */ void disimissProgress(); /** * @descriptoin 请求数据成功 * @author dc * @param tData 数据类型 * @date 2017/2/16 11:01 */ void loadDataSuccess(T tData); /** * @descriptoin 请求数据错误 * @author dc * @param throwable 异常类型 * @date 2017/2/16 11:01 */ void loadDataError(Throwable throwable); }
将这几个请求步骤也成公共方法,然后子类继承这个基类就能获取到这几个公共方法,避免了每个请求的子view都重复定义
-
定义WeatherView子类视图
package bule.souv.com.mvp_rxjava_retrofittest_master.view; import bule.souv.com.mvp_rxjava_retrofittest_master.base.IBaseView; import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean; /** * 描述:天气信息的mvp中的视图V * 作者:dc on 2017/2/16 10:59 * 邮箱:597210600@qq.com */ public interface WeatherView extends IBaseView<WeatherInfoBean> { }
有的好事者就问了,你这个子类怎么啥都没做;对,就是啥都没做,任性。因为基类视图已经全部帮我实现了呀,但是有的需求可能只针对某一个功能,不能写在基类上,那么我们就在子类上实现这个方法,比如获取明天的天气,这个只属于天气功能,所以我们可以写在WeatherView这个子类里面。但是这个demo只有一个天气获取的功能,所以......
三:新建MVP中的model
M在MVP中主要处理业务数据逻辑;如:网络请求数据、数据库获取数据等等。
我们在此就就是通过网络数据天气数据
- WeatherModel的接口定义
package bule.souv.com.mvp_rxjava_retrofittest_master.model;
import bule.souv.com.mvp_rxjava_retrofittest_master.base.IBaseRequestCallBack;
/**
* 描述:MVP中的M;处理获取网络天气数据
* 作者:dc on 2017/2/16 11:03
* 邮箱:597210600@qq.com
*/
public interface WeatherModel<T> {
/**
* @descriptoin 获取网络数据
* @author dc
* @param city 城市
*@param key key
* @param iBaseRequestCallBack 数据的回调接口
* @date 2017/2/17 19:01
*/
void loadWeather(String city,String key, IBaseRequestCallBack<T> iBaseRequestCallBack);
/**
* @descriptoin 注销subscribe
* @author
* @param
* @date 2017/2/17 19:02
* @return
*/
void onUnsubscribe();
}
主要提供两个方法,一个通过city、key来获取数据,IBaseRequestCallBack为数据回调接口,这个在下面会讲到。
- 定义数据的回调接口IBaseRequestCallBack
在请求数据之后不管成功or失败,我们都需要通知用户(显示UI提示用户),所以在请求数据之后需要提供一个接口反馈到视图V上,在我们做项目的过程中会发现很多数据请求返回的类型都差不多,可能只是返回的bean对象不同而已(天气Bean、班级Bean、新闻Bean),那么我们可不可以也将这些方法写成基类用作回调,然后使用泛型来包装不同的对象就可以了
package bule.souv.com.mvp_rxjava_retrofittest_master.base;
/**
* 描述:请求数据的回调接口
* Presenter用于接受model获取(加载)数据后的回调
* 作者:dc on 2017/2/16 11:22
* 邮箱:597210600@qq.com
*/
public interface IBaseRequestCallBack<T> {
/**
* @descriptoin 请求之前的操作
* @author dc
* @date 2017/2/16 11:34
*/
void beforeRequest();
/**
* @descriptoin 请求异常
* @author dc
* @param throwable 异常类型
* @date 2017/2/16 11:34
*/
void requestError(Throwable throwable);
/**
* @descriptoin 请求完成
* @author dc
* @date 2017/2/16 11:35
*/
void requestComplete();
/**
* @descriptoin 请求成功
* @author dc
* @param callBack 根据业务返回相应的数据
* @date 2017/2/16 11:35
*/
void requestSuccess(T callBack);
}
请求之前的操作一样、用Throwable来处理请求异常信息、请求完成之后的操作一样、只有请求成功返回的Object不一样,在这里使用T泛型来指定不同的object对象。
- 定义retrofit中service的Api
package bule.souv.com.mvp_rxjava_retrofittest_master.api;
import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean;
import retrofit2.http.GET;
import retrofit2.http.Query;
import rx.Observable;
/**
* 描述:retrofit的接口service定义
* 作者:dc on 2017/2/16 11:10
* 邮箱:597210600@qq.com
*/
public interface WeatherServiceApi {
//请求的接口地址:http://api.avatardata.cn/Weather/Query?key=75bfe88f27a34311a41591291b7191ce&cityname=%E9%95%BF%E6%B2%99
@GET("Weather/Query?")
Observable<WeatherInfoBean> loadWeatherInfo(@Query("key") String key,@Query("cityname") String cityname);
}
这里首先需要注意请求的接口地址使用retrofit不要拼接错了。提供了两个参数;
-
定义实现WeathModel的WeathModelImp
package bule.souv.com.mvp_rxjava_retrofittest_master.model; import android.content.Context; import bule.souv.com.mvp_rxjava_retrofittest_master.api.WeatherServiceApi; import bule.souv.com.mvp_rxjava_retrofittest_master.base.BaseModel; import bule.souv.com.mvp_rxjava_retrofittest_master.base.IBaseRequestCallBack; import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; /** * 描述:MVP中的M实现类,处理业务逻辑数据 * 作者:dc on 2017/2/16 11:05 * 邮箱:597210600@qq.com */ public class WeatherModelImp extends BaseModel implements WeatherModel<WeatherInfoBean> { private Context context = null; private WeatherServiceApi weatherServiceApi; private CompositeSubscription mCompositeSubscription; public WeatherModelImp(Context mContext) { super(); context = mContext; weatherServiceApi = retrofitManager.getService(); mCompositeSubscription = new CompositeSubscription(); } @Override public void loadWeather(String key, String city, final IBaseRequestCallBack iBaseRequestCallBack) { mCompositeSubscription.add(weatherServiceApi.loadWeatherInfo(key, city) //将subscribe添加到subscription,用于注销subscribe .observeOn(AndroidSchedulers.mainThread())//指定事件消费线程 .subscribeOn(Schedulers.io()) //指定 subscribe() 发生在 IO 线程 .subscribe(new Subscriber<WeatherInfoBean>() { @Override public void onStart() { super.onStart(); //onStart它总是在 subscribe 所发生的线程被调用 ,如果你的subscribe不是主线程,则会出错,则需要指定线程; iBaseRequestCallBack.beforeRequest(); } @Override public void onCompleted() { //回调接口:请求已完成,可以隐藏progress iBaseRequestCallBack.requestComplete(); } @Override public void onError(Throwable e) { //回调接口:请求异常 iBaseRequestCallBack.requestError(e); } @Override public void onNext(WeatherInfoBean weatherInfoBean) { //回调接口:请求成功,获取实体类对象 iBaseRequestCallBack.requestSuccess(weatherInfoBean); } })); } @Override public void onUnsubscribe() { //判断状态 if(mCompositeSubscription.isUnsubscribed()){ mCompositeSubscription.clear(); //注销 mCompositeSubscription.unsubscribe(); } } }
因为实现了接口WeathModel所以也实现了接口的两个方法。这里有两部分不知道我理解的对不对,还请大神赐教
1:我在loadweather方法第一行使用了CompositeSubscription.add()方法添加subscribe,主要是用来注销subscribe不知道合不合适,或者说怎样处理注销subscribe会更好?
2:onStart,学习rxjava我们知道observe除了提供必须的三个方法onCompleted()、onError()、onNext()之外还提供了一个onStart()方法,主要在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,所以在这里我用来显示progress;但是需要注意onStart它总是在 subscribe 所发生的线程被调用 ,如果你的subscribe不是主线程,则会出错,则需要指定线程。
3:在onUnsubscribe()方法中,先判断状态在注销subscribe。
新建MVP中的P
P在MVP中主要是视图V和模式M之间的桥梁。
-
定义WeathPresenter接口
package bule.souv.com.mvp_rxjava_retrofittest_master.presenter; /** * 描述:MVP中的P接口定义 * 作者:dc on 2017/2/16 15:17 * 邮箱:597210600@qq.com */ public interface WeatherPresenter { /** * @descriptoin 请求天气数据 * @author dc * @param key key * @param city 城市 * @date 2017/2/17 19:36 * @return */ void loadWeather(String key, String city); /** * @descriptoin 注销subscribe * @author dc * @date 2017/2/17 19:36 */ void unSubscribe(); }
定义presenter的基类
上面我们的IBaseCallBack,我们定义了几个回调方法,方法里面都是处理请求之前、之后的结果,这里也可以新建个Presenter的基类用来建立不同的M和V之间的桥梁(因为是公共的方法)只是M和V不同而已。也因为不同的M和V对象,同样我们也用泛型来代替
package bule.souv.com.mvp_rxjava_retrofittest_master.base;
/**
* 描述:
* * 代理对象的基础实现 : 一些基础的方法
*
* @param <V> 视图接口对象(view) 具体业务各自继承自IBaseView
* @param <T> 业务请求返回的具体对象
* 作者:dc on 2017/2/16 15:07
* 邮箱:597210600@qq.com
*/
public class BasePresenterImp<V extends IBaseView , T> implements IBaseRequestCallBack<T> {
private IBaseView iBaseView = null; //基类视图
/**
* @descriptoin 构造方法
* @author dc
* @param view 具体业务的视图接口对象
* @date 2017/2/16 15:12
*/
public BasePresenterImp(V view) {
this.iBaseView = view;
}
/**
* @descriptoin 请求之前显示progress
* @author dc
* @date 2017/2/16 15:13
*/
@Override
public void beforeRequest() {
iBaseView.showProgress();
}
/**
* @descriptoin 请求异常显示异常信息
* @author dc
* @param throwable 异常信息
* @date 2017/2/16 15:13
*/
@Override
public void requestError(Throwable throwable) {
iBaseView.loadDataError(throwable);
iBaseView.disimissProgress(); //请求错误,提示错误信息之后隐藏progress
}
/**
* @descriptoin 请求完成之后隐藏progress
* @author dc
* @date 2017/2/16 15:14
*/
@Override
public void requestComplete() {
iBaseView.disimissProgress();
}
/**
* @descriptoin 请求成功获取成功之后的数据信息
* @author dc
* @param callBack 回调的数据
* @date 2017/2/16 15:14
*/
@Override
public void requestSuccess(T callBack) {
iBaseView.loadDataSuccess(callBack);
}
}
在代码中我们可以看到,加载前的弹框,加载成功回调给UI,加载失败通知UI错误信息,加载完成关闭弹框等都已经在这里做了一个基础的实现(这就是为什么定义一个基类的原因)。
这里我们通过两个泛型V and T,V表示不同的视图、T表示不同的对象。这样只要在不同的presenter传入不同的V和T就能通过基类来建立不同的视图V和不同的模型M之间的桥梁
- 实现WeatherPresenter接口的WeathPresenterImp
package bule.souv.com.mvp_rxjava_retrofittest_master.presenter;
import android.content.Context;
import bule.souv.com.mvp_rxjava_retrofittest_master.base.BasePresenterImp;
import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean;
import bule.souv.com.mvp_rxjava_retrofittest_master.model.WeatherModelImp;
import bule.souv.com.mvp_rxjava_retrofittest_master.view.WeatherView;
/**
* 描述:MVP中的P实现类
* 作者:dc on 2017/2/16 15:17
* 邮箱:597210600@qq.com
*/
public class WeatherPresenterImp extends BasePresenterImp<WeatherView,WeatherInfoBean> implements WeatherPresenter { //传入泛型V和T分别为WeatherView、WeatherInfoBean表示建立这两者之间的桥梁
private Context context = null;
private WeatherModelImp weatherModelImp = null;
/**
* @descriptoin 构造方法
* @param view 具体业务的视图接口对象
* @author dc
* @date 2017/2/16 15:12
*/
public WeatherPresenterImp(WeatherView view, Context context) {
super(view);
this.context = context;
this.weatherModelImp = new WeatherModelImp(context);
}
@Override
public void loadWeather(String key, String city) {
weatherModelImp.loadWeather(key, city, this);
}
@Override
public void unSubscribe() {
weatherModelImp.onUnsubscribe();
}
}
在类的定义代码中我们继承了基类BasePresenterImp并传入泛型V、T分别为WeatherView、WeatherInfoBean则表示建立这两者之间的桥梁
也因为我们在BasePresenterImp中实现了IBaseCallBack的回调方法,所以程序Model请求之后通过回调接口调用BasePresenterImp中不同的方法,而我们在不同的方法中处理了视图V的界面,从而P就建立了M的数据处理和V视图的显示处理之间的桥梁
Activity或Fragment的实现
这里面的代码处理都很简单,因为MVP的好处讲activity的M和V分离开了的好处。
package bule.souv.com.mvp_rxjava_retrofittest_master;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean;
import bule.souv.com.mvp_rxjava_retrofittest_master.presenter.WeatherPresenterImp;
import bule.souv.com.mvp_rxjava_retrofittest_master.view.WeatherView;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity implements WeatherView{
@Bind(R.id.toolbar)
Toolbar toolbar;
@Bind(R.id.main_getweather_temp_btn)
Button mainGetweatherTempBtn;
@Bind(R.id.main_showweather_temp_tv)
TextView mainShowweatherTempTv;
@Bind(R.id.fab)
FloatingActionButton fab;
/** 对象定义 **/
private WeatherPresenterImp weatherPresenterImp = null;
private ProgressDialog progressDialog = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
init();
}
/**
* @descriptoin 点击获取温度
* @author
* @param
* @date 2017/2/16 15:52
* @return
*/
@OnClick(R.id.main_getweather_temp_btn)
public void setOnClickRequestWeatherBtn(){
weatherPresenterImp.loadWeather("c5bb749112664353af44bc99ed263857", "长沙");
}
/**
* @descriptoin 初始化
* @author dc
* @date 2017/2/16 15:44
*/
private void init(){
weatherPresenterImp = new WeatherPresenterImp(this,this);
progressDialog = new ProgressDialog(MainActivity.this);
// progressDialog.setTitle();
progressDialog.setMessage("正在请求获取数据,请稍等!!!");
}
@Override
public void showProgress() {
if(progressDialog != null && !progressDialog.isShowing()){
progressDialog.show();
}
}
@Override
public void disimissProgress() {
if(progressDialog != null && progressDialog.isShowing()){
progressDialog.dismiss();
}
}
@Override
public void loadDataSuccess(WeatherInfoBean tData) {
String temperature = tData.getResult().getRealtime().getWeather().getTemperature();
mainShowweatherTempTv.setVisibility(View.VISIBLE);
mainShowweatherTempTv.setText("当前的气温为:" + temperature + "℃");
}
@Override
public void loadDataError(Throwable throwable) {
String errorMsg = throwable.getMessage();
mainShowweatherTempTv.setVisibility(View.VISIBLE);
mainShowweatherTempTv.setText(errorMsg);
}
@Override
protected void onStop() {
super.onStop();
//在退出之后注销subscribe
weatherPresenterImp.unSubscribe();
}
}
总结
通过上面的案例我想对mvp+rxjava+retrofit的结合使用一些项目应该有一些了解了吧。
MVP的好处将M和V分离,使我们的activity或fragment更简洁,整个的功能逻辑处理很明了。
一些公共的功能的方法,我们可以通过定义基类的方法实现,用我们的子类继承基类(获取公共方法)、定义本身方法。会使我们少些很多重复的代码。
RXjava + retrofit响应式使我们处理功能的代码逻辑很简洁,你会发现这个功能连Handler都没用到。
当然Rxjava的强大远不如此,后期会根据实际开发情况来更深入的了解rxjava。
retrofit中的网络请求部分是可以在继续封装的,这个我们看一些github项目就能发现,后期会模仿这项开源项目强大我们的Okhttp的网络封装。