古语有云,没有规矩,就不成方圆。其实做什么事都一样,做什么事都要有自己熟悉且大家都认同的一套规矩,这样既能提高自己的做事效率,也方便他人的理解。
在移动开发中,mvp是新兴的一种软件开发模式,是经过时间的考验并且大家都认同的解耦框架。它不仅能让我们的代码逻辑更加清晰,不同层间分工不同又相互协作,服务于我们的项目。mode层负责本地数据和网络数据的处理,presenter层负责业务逻辑的处理,view层负责ui界面的展示。减少了view层与model层的直接交互,而是通过presenter实现中间代理的交互逻辑。
rxjava+retrofit是最近很火的网络层框架,两者完美协作客户端与服务器的数据交互,并且rxjava是响应式编程代表,在retrofit提供网络服务的时候方便的切换处理线程,大大方便了客户端网络层的开发。
dagger2是android端的依赖注入框架,做过ssh的同学可能不会陌生,有了依赖注入的思想,不用我们自己通过new的方式去创建对象了,而是通过注入,将对象托管权交出来由容器统一管理,而当我们需要的时候直接从容器中去取(如果用传统的方式通过new的方式创建实例对象,当类构造函数或内部发生改变后,每个new的地方都需要去改变,工作量可见之大。而通过依赖注入的方式管理后,只需要很小的开销就能实现)。
磨刀不误砍柴工,在对rxjava+retrofit+mvp+dagger2进行了简单的了解之后,开始我们的基层框架搭建吧。
既然这是一个大量信息交互的时代,我们就从网络层框架开始搭建吧。实现rxjava+retrofit的网络层服务。
首先看一下最后的框架结构:
在gradle中需要添加如下依赖:
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.0'
compile 'com.github.d-max:spots-dialog:0.7@aar'
接下来我们一次介绍每层的功能和实现原理:api层是进行网络请求的接口封装层,可以将网络请求的借口和参数等放入commonapi中进行统一管理,当我们通过retrofit创建出commonapi实例后,就可以对所有网络请求进行访问(当然也可以根据自己的需求和爱好进行分类)。看一下我的commonapi简单实现:根据page路径参数获取当前页的ip列表(get注解后为相对地址路径,baseurl在创建retrofit时指定)
public interface CommonApi {
@GET("allip/{page}")
Observable<TableIp> userLogin(@Path("page")int page);
}
接下来是exception包下面的内容,它主要处理网络请求过程中的异常。
(1)ApiError类是处理数据请求异常(如请求地址,参数等错误)时,对错误信息的封装。它根据服务器返回的不同的errorbody而异,本例中包含code和displayMessage两个字段,如下:
/*错误码*/
private int code;
/*显示的信息*/
private String displayMessage;
public ApiError(Throwable e) {
super(e);
}
public ApiError(Throwable cause,@CodeException.CodeEp int code, String showMsg) {
super(showMsg, cause);
setCode(code);
setDisplayMessage(showMsg);
}
// @CodeException.CodeEp
public int getCode() {
return code;
}
//@CodeException.CodeEp
public void setCode( int code) {
this.code = code;
}
public String getDisplayMessage() {
return displayMessage;
}
public void setDisplayMessage(String displayMessage) {
this.displayMessage = displayMessage;
}
(2)CodeException是请求失败时不同的errorcode(错误码)。
(3)HttpTimeException是对项目出现runtimeexception(运行时异常)时数据的封装,方便向用户展示错误信息,如下所示:
/*未知错误*/
public static final int UNKOWN_ERROR = 0x1002;
/*本地无缓存错误*/
public static final int NO_CHACHE_ERROR = 0x1003;
/*缓存过时错误*/
public static final int CHACHE_TIMEOUT_ERROR = 0x1004;
public HttpTimeException(int resultCode) {
this(getApiExceptionMessage(resultCode));
}
public HttpTimeException(String detailMessage) {
super(detailMessage);
}
/**
* 转换错误数据
*
* @param code
* @return
*/
private static String getApiExceptionMessage(int code) {
switch (code) {
case UNKOWN_ERROR:
return "错误:网络错误";
case NO_CHACHE_ERROR:
return "错误:无缓存数据";
case CHACHE_TIMEOUT_ERROR:
return "错误:缓存数据过期";
default:
return "错误:未知错误";
}
}
(4)rertyWhenNetWorkError是网络异常时的重连机制,它主要负责在第一次网络访问失败后,间歇性的重新连接服务器,保障服务的正常使用,定义如下:
/* retry次数*/
private int count = 3;
/*延迟*/
private long delay = 3000;
/*叠加延迟*/
private long increaseDelay = 3000;
public rertyWhenNetWorkError() {
}
public rertyWhenNetWorkError(int count, long delay) {
this.count = count;
this.delay = delay;
}
public rertyWhenNetWorkError(int count, long delay, long increaseDelay) {
this.count = count;
this.delay = delay;
this.increaseDelay = increaseDelay;
}
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable
.zipWith(Observable.range(1, count + 1), new Func2<Throwable, Integer, Wrapper>() {
@Override
public Wrapper call(Throwable throwable, Integer integer) {
return new Wrapper(throwable, integer);
}
}).flatMap(new Func1<Wrapper, Observable<?>>() {
@Override
public Observable<?> call(Wrapper wrapper) {
if ((wrapper.throwable instanceof ConnectException
|| wrapper.throwable instanceof SocketTimeoutException
|| wrapper.throwable instanceof TimeoutException)
&& wrapper.index < count + 1) { //如果超出重试次数也抛出错误,否则默认是会进入onCompleted
return Observable.timer(delay + (wrapper.index - 1) * increaseDelay, TimeUnit.MILLISECONDS);
}
return Observable.error(wrapper.throwable);
}
});
}
private class Wrapper {
private int index;
private Throwable throwable;
public Wrapper(Throwable throwable, int index) {
this.index = index;
this.throwable = throwable;
}
}
以上时对retrofit进行网络请求时可能出现的异常进行的总结与处理,主要是捕获到异常,进行统一的封装处理,方便后面统一的向用户展示异常信息。
介绍完一场处理机制后,接下来就该主角(工厂类)出场了,也就是factory包下的内容,包括retrofit工厂,异常处理工厂和json转换工厂类,接下来我们一次介绍用途和实现。
(1)ApiErrorAwareConverterFactory是对json转换错误时进行处理的工厂类,它的主要实现方法是responseBodyConverter方法。它通过EmptyJsonLenientConverterFactory从retrofit中获取json数据转换成指定Type类型,转换失败就反回null,成功我们就能拿到指定类型的数据了:
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegateConverter =
mEmptyJsonLenientConverterFactory.responseBodyConverter(type, annotations,
retrofit);
return new Converter<ResponseBody, Object>() {
@Override
public Object convert(ResponseBody value) throws IOException {
ResponseBody clone = RealResponseBody.create(value.contentType(), value.contentLength(),
value.source().buffer().clone());
if (delegateConverter != null)
return delegateConverter.convert(value);
return null;
}
};
}
(2)接下来是上面提到的EmptyJsonLenientConverterFactory工厂类,它通过GsonConverterFactory尝试将获取的数据进行类型转换,主要是对空json字符串进行过滤处理,与上面的工厂类协同对空json和异常json进行统一管理。
(3)FactoryException工厂是对所有异常统一转换成apierror类型,方便对异常信息的统一管理和展示。
(4)接下来是网络访问最重要的RetrofitFactory工厂,它负责生成retrofit对象,通过retrofit创建comminapi,提供给我们访问网络的接口,主要实现如下:
private static volatile RetrofitFactory mInstanse=null;
public CommonApi mApi;
public RetrofitFactory() {
mApi = provideHotApi();
}
private OkHttpClient provideOkHttpClient() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(Constants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(Constants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(new AuthInterceptor())
.addInterceptor(new ResponseInterceptor())
.build();
return okHttpClient;
}
public CommonApi provideHotApi() {
Retrofit retrofit1 = new Retrofit.Builder()
.baseUrl(Constants.Base_Url)
.client(provideOkHttpClient())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(new ApiErrorAwareConverterFactory(new EmptyJsonLenientConverterFactory(GsonConverterFactory.create())))
//.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit1.create(CommonApi.class);
}
public static RetrofitFactory getmInstanse() {
if(mInstanse==null){
synchronized (RetrofitFactory.class){
if(mInstanse==null){
mInstanse = new RetrofitFactory();
}
}
}
return mInstanse;
}
由于retrofit内部默认使用okhttp进行网络访问,而且把okhttp的管理权限交给了开发者,因此在使用它的时候,我们可以自己根据需求定制okhttp,然后交给retrofit进行网络请求。在栗子中我们使用到了HttpLoggingInterceptor
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
是为了方便网络请求的日志的输出,提高我们调试和开发的效率。
同时我们给okhttp设置了连接超时参数,请求超时参数,请求拦截器(AuthInterceptor)和响应拦截器(ResponseInterceptor),它们的实现和用途会在接下来进一步说明。
接下来就是创建retrofit对象:
Retrofit retrofit1 = new Retrofit.Builder()
.baseUrl(Constants.Base_Url)
.client(provideOkHttpClient())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(new ApiErrorAwareConverterFactory(new EmptyJsonLenientConverterFactory(GsonConverterFactory.create())))
//.addConverterFactory(GsonConverterFactory.create())
.build();
在创建Retrofit时,指定访问主机地址(baseurl)参数,同时将我们封装好的okhttp和各种工厂类添加进去,就能获取到Retrofit实例对象。最后就能由Retrofit创建出CommonApi对象,供我们进行网络访问了。
由于上面用到了请求拦截器(AuthInterceptor)和响应拦截器(ResponseInterceptor),实际上有种spring中面向切面的意思,即对每次发器网络请求前和响应成功后的统一处理,一旦配置了它,每次请求都将经过它们的拦截处理。AuthInterceptor常用于在发器请求前向header头部写入参数或修改参数类型,ResponseInterceptor通常用于对请求成功后的header数据进行统一保存或处理。如修改header中content-type:
@Override
public Response intercept(Chain chain) throws IOException {
Request origin = chain.request();
//原始请求header
Headers originHeaders = origin.headers();
//创建需要的请求header
Headers.Builder newHeaders = new Headers.Builder();
//向请求头写入Content-Type参数,设置参数搁置为application/json
newHeaders.add("Content-Type", "application/json;charset=UTF-8");
Request.Builder newRequest = origin.newBuilder()
.headers(newHeaders.build());
return chain.proceed(newRequest.build());
}
ResponseInterceptor存储成功响应后header中的cookies值:
/*
此interceptor用于处理每次请求中header中数据问题
本例用于获取header中Set-Cookie参数值
cookie未更新,导致注销登录时,再次登录还以原始用户
*/
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
//判断请求header中是否有page参数,且值为Login,可以通过在登录的接口上通过header注解添加page参数,用于判断在登录成功后获取cookie值
// 如@Headers({"Auth-Type:Basic", "Page:Login"})
// @POST("api/login")
// Observable<BaseDownBean> userLogin(@Body RequestBody requestBody);
// if (request.header("Page") != null && TextUtils.equals(request.header("Page"), "Login")) {
// if (!TextUtils.isEmpty(response.header("Set-Cookie"))) {
//存储登录成功后的cookie值
// ShareFeranceUtil.getmInstance().putString(App.getAppContext(), Constants.Set_Cookie, response.header("Set-Cookie"));
// }
// }
return response;
}
到这里retrofit相关知识就差不多结束了,可以直接从retrofit工厂类拿到CommonApi获取数据,但是得将CommonApi方法的返回值换为Call的范型类型,如下:
@GET("allip/{page}")
Call<TableIp> getip(@Path("page")int page);
在需要的地方调用如下代码即可实现对响应成功和失败的数据进行处理了:
RetrofitFactory.getmInstanse().mApi.getip(1).enqueue(new Callback<TableIp>() {
@Override
public void onResponse(Call<TableIp> call, Response<TableIp> response) {
}
@Override
public void onFailure(Call<TableIp> call, Throwable t) {
}
});
但是这并不是我们的终极目标,我们需要的是干练,简单,清晰的处理逻辑。这时就该rxjava出场了,rxjava完美支持retrofit,大大简化了我们对网络请求时的逻辑处理,并且可快速在网络访问的子线程和ui线程间切换。要使用也很简单:
将CommonApi方法的返回值换为Observable的范型类型,如下:
@GET("allip/{page}")
Observable<TableIp> userLogin(@Path("page")int page);
这样我们进行网络访问时反回的类型就变成Observable类型了,这时我们需要在subscribe()方法中用一个Subscriber对数据进行处理。Subscriber用于对整个响应的生命周期进行管理,其中主要包含onstart,onNext,onCompleted,onError方法,对每一次开始响应到响应结束的整个周期进行管理。
onstart:在请求前执行的方法,由于是网络请求,经常需要向用户展示加载对话框,表示请求正在进行。因此可以在onstart中统一显示对话框。
onNext:在网络请求成功后对成功响应体的处理。
onError:在网络请求出现异常时统一在这个方法中对异常进行处理。
onCompleted:一次响应结束时执行的方法,经常在方法内关闭对话框,对前面使用的资源关闭等。
由于每个函数功能明确,我们经常也只需要对请求成功的数据进行处理,对异常可以进行统一处理,因此我们可以对Subscriber进行简单封装,对onstart,onError,onCompleted进行统一处理,只暴露出onNext方法每次进行不同的处理就ok了。因此就出现了下面的接口设计:
public interface onRequestListener<T> {
void onSuccess(T t);
void onHttpError(ApiError error);
}
每次只需实现onSuccess方法对响应成功数据进行处理就好了,有时可能也需要拿到错误信息,因此在这里给出了onHttpError方法。接口主要还是根据自己的需求去设计。
最后就是我们封装的Subscriber类了,它实现了onRequestListener接口,负责在每次请求前显示加载矿,请求结束后让加载框消失,并且对请求时的异常统一在errorDo方法中处理,实现如下:
public abstract class HttpObserver<T> extends Subscriber<T> implements onRequestListener<T> {
// 弱引用防止内存泄露
private WeakReference<Context> mActivity;
// 加载框可自己定义
private SpotsDialog mDialog = null;
public HttpObserver(WeakReference<Context> mActivity) {
this.mActivity = mActivity;
initProgressDialog();
}
@Override
public void onStart() {
showProgressDialog();
}
@Override
public void onCompleted() {
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
/*需要緩存并且本地有缓存才返回*/
errorDo(e);
dismissProgressDialog();
}
@Override
public void onHttpError(ApiError error) {
//这里可以做错误处理,也可以在调用的时候重写error方法,对错误信息进行处理
ApiError error1 = FactoryException.analysisExcetpion(error);
ToastUtil.showToastLong(mActivity.get(),error.getDisplayMessage());
}
/**
* 错误统一处理
*
* @param e
*/
private void errorDo(Throwable e) {
Context context = mActivity.get();
//if (context == null) return;
if (e instanceof ApiError) {
onError((ApiError) e);
} else if (e instanceof HttpTimeException) {
HttpTimeException exception=(HttpTimeException)e;
onError(new ApiError(exception, CodeException.RUNTIME_ERROR,exception.getMessage()));
} else {
onError(new ApiError(e, CodeException.UNKNOWN_ERROR,e.getMessage()));
}
/*可以在这里统一处理错误处理-可自由扩展*/
}
@Override
public void onNext(T t) {
onSuccess(t);
}
private void initProgressDialog() {
Context context = mActivity.get();
if (mDialog == null && context != null) {
mDialog = new SpotsDialog(context,"正在加载中...");
}
}
private void showProgressDialog() {
Context context = mActivity.get();
if (mDialog == null || context == null) return;
if (!mDialog.isShowing()) {
mDialog.show();
}
}
private void dismissProgressDialog() {
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
最后SchedulersCompat类主要是网络请求时的线程管理,让请求时在子线程,处理结果是回到ui线程。实现如下:
private final static Observable.Transformer ioTransformer =new Observable.Transformer() {
@Override
public Object call(Object o) {
return ((Observable)o).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
public static <T> Observable.Transformer<T, T> applyIoSchedulers() {
return (Observable.Transformer<T, T>) ioTransformer;
}
至此,我们retrofit+rxjava进行网络请求的框架就完成了。在进行网络访问时的代码就更加简单清晰了,只需要实现onSuccess方法对结果处理即可:
RetrofitFactory.getmInstanse().mApi.userLogin(1).subscribe(new HttpObserver<TableIp>() {
@Override
public void onSuccess(TableIp tableIp) {
}
});
以后每次要访问网络时,都只需要这么简单的一段代码,就可以进行一次完美的数据获取,而且还能对异常信息统一处理,是不是感觉很轻松啊。。如果感觉讲解不太清晰,可以上github查看完整rxjava+retrofit+mvp+dagger2整合框架,传送门–>rxjava+retrofit+mvp+dagger2整合框架
如果对你有帮助,欢迎提出宝贵意见和star。