使用前需要学习retrofit2 和rxjava等相关知识,总结一下比较好的文章
retrofit
你真的会用Retrofit2吗?Retrofit2完全教程
Retrofit解析2之使用简介
Retrofit 2.0 自定义Converter
作者介绍Retrofit 2.0
Retrofit源码分析(超详细)
rxjava
RxJava + Retrofit 的实际应用场景
RxJava 1.x详解
这可能是最好的RxJava 2.x 教程(完结版)
专栏
专栏-Android RxJava之网络处理
专栏-Retrofit+RxJava+Okhttp +Rx 实践及源码大全
专栏-Retrofit 2.0 超能实践系列
错误异常处理
Rxjava、Retrofit返回json数据解析异常处理
Retrofit+RxJava 优雅的处理服务器返回异常、错误
Gson
HTTP基础
你应该知道的HTTP基础知识
ps:怪盗kidou的系列文章都值的一看
其他
Android Studio支持Java8方法支持lambda
rxjava的ObserveOn和SubscribeOn的一些结论
RxJava线程变换之observeOn与subscribeOn
一、依赖引入
依赖引入的过程中,会遇到一些冲突问题 具体参考 RxAndroid2+RxLifecycle2+Retrofit2 依赖引入和冲突分析
最后依赖
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
//add on 2017.10.23
//retrofit
// compile "com.squareup.retrofit2:retrofit:2.3.0" //adapter-rxjava2和converter-gson都引入了retrofit2,这个可以去掉
compile"com.squareup.retrofit2:adapter-rxjava2:2.3.0" //连接器 自带retrofit:2.3.0
compile"com.squareup.retrofit2:converter-gson:2.3.0" //解析器 主要处理具体对象、jsonObject、jsonArray 包含okhttp、gson等 自带retrofit:2.3.0
compile"com.squareup.retrofit2:converter-scalars:2.3.0" //解析器 处理String boolean 等基本类型 包含okhttp、gson等 自带retrofit:2.3.0
//rxjava rxandroid
compile "io.reactivex.rxjava2:rxjava:2.1.5"
compile "io.reactivex.rxjava2:rxandroid:2.0.1" //本身也自带rxjava
//rxlifecycle
// compile "com.trello.rxlifecycle2:rxlifecycle:2.1.0" //rxlifecycle-components已经引入了,这个可以去掉
compile "com.trello.rxlifecycle2:rxlifecycle-components:2.1.0" //自带rxlifecycle:2.1.0
//dagger2
compile 'com.google.dagger:dagger:2.12'
annotationProcessor 'com.google.dagger:dagger-compiler:2.12'
}
其中dagger如果不使用可以去掉
二、java8的引入
java引入也会有些问题
具体参考Android Studio支持Java8方法支持lambda
java8缺点:
java8的编译速度很慢,正常java7跑几秒 java8要跑1分钟。。无奈暂时弃用。如果你机器快建议使用java8 因为lambda会使你的代码更加简洁
三、框架设计
参考很多网上的文章,很多框架都觉得很蛋疼、Retofit每添加一个接口就得在service文件写一个接口定义类
参考了浅谈Retrofit封装-让框架更加简洁易用
文章只用了retrofit,我们把rxjava 也加上
按常用做法每个接口请求需要在接口上添加对应的方法
修改前:
public interface RetrofitHttpService {
@GET("/xxx/xxxx")
Observable<BaseResult<BaseModel>> login(@Query("name") String name);
}
修改后:
public interface RetrofitHttpService {
@GET()
Observable<Response<ResponseBody>> get(@Url String url, @QueryMap Map<String, String> params, @HeaderMap Map<String, String> headers);
}
这样就可以统一一个方法。只返回最基本的类型,然后在调用这个方法在统一进行解析
注意:其中Response是retrofit2的, ResponseBody是okhttp3,这点容易搞错
参考:Retrofit源码分析
简单看一下调用方法
HttpConfig.getService().get(checkUrl(mUrl), interceptParams(mParams), interceptHeaders(mHeaders))
.compose(RxUtils.<T>translate(typeOfT))
.subscribe(new SampleProgressObserver<T>() { //subscribe订阅里-添加观察者Observer
@Override
public void onSubscribe(Disposable d) {
Log.i("lch1", "onSubscribe" + " => " + Thread.currentThread().getName());
}
@Override
public void onNext(T response) {
Log.i("lch", "onNext:" + " => " + Thread.currentThread().getName());
mCallback.onSuccess(response);
}
@Override
public void onError(ApiException e) {
Log.i("lch1", "onError" + " => " + Thread.currentThread().getName());
Log.i("lch1", "onError---- " + e.code + ":" + e.msg);
e.printStackTrace();
}
@Override
public void onComplete() {
Log.i("lch1", "onComplete" + " => " + Thread.currentThread().getName());
}
});
其中使用RxUtils进行数据解析、错误处理、和io main线程切换
public class RxUtils {
/**
* ObservableTransformer 作用于整个流,Func是一个操作符,作用于数据项
* @param typeOfT
* @param <T>
* @return
*/
public static <T> ObservableTransformer<Response<ResponseBody>, T> translate(final Type typeOfT) {
return new ObservableTransformer<Response<ResponseBody>, T>() {
@Override
public ObservableSource apply(Observable upstream) {
return upstream
.map(new ServerResponseTranslateFunc<T>(typeOfT)) //进行数据转换
.onErrorResumeNext(new HttpResponseFunc()) //拦截服务器返回的错误
.subscribeOn(Schedulers.io()) // subscribeOn() 指定的就是发射事件的线程
.unsubscribeOn(Schedulers.io()) //取消订阅制定线程 // TODO: 2017/11/7 测试
.observeOn(AndroidSchedulers.mainThread()); //observerOn 指定的就是订阅者接收事件的线程。
}
};
}
/**
* 对服务器返回的数据进行解析,返回结构是BaseResult<T>
*
* @param <T>
*/
private static class ServerResponseTranslateFunc<T> implements Function<Response<ResponseBody>, T> {
private Type mTypeOfT;
public ServerResponseTranslateFunc(Type typeOfT) {
mTypeOfT = typeOfT;
}
@Override
public T apply(Response<ResponseBody> response) throws Exception {
Log.i("lch1", "ServerResponseTranslateFunc" + " => " + Thread.currentThread().getName());
if (response != null && response.isSuccessful()) {
BaseResult<T> result = HttpConfig.getTransformer().transformer(response, mTypeOfT); //调用解析器,外部可以设置,可以使用gson或者其他
if (result != null) {
//判断跟服务器定义的code是否一样
if (result.isSuccess()) {
return result.data;
} else {
throw new ApiException(result.code, result.msg); //返回自定义ApiException-服务器提示
}
} else {
throw new ApiException(HttpCode.ServerError.ERROR_TRANSFORM); //返回自定义ApiException
}
} else {
throw new HttpException(response); //返回HttpException
}
}
}
/**
* 错误处理fun,
* 对所有错误进行统一解析
*/
private static class HttpResponseFunc implements Function<Throwable, Observable> {
@Override
public Observable apply(Throwable throwable) throws Exception {
Log.i("lch1", "HttpResponseFunc" + " => " + Thread.currentThread().getName());
return Observable.error(ExceptionHandler.handleException(throwable));
}
}
}
1.异常处理可以参考参考:Retrofit+RxJava 优雅的处理服务器返回异常、错误
2.ObservableTransformer 作用于整个流,它跟fun很像,但Fun是一个操作符,作用于数据项
参考:Retrofit+RxJava错误预处理
3.其中线程切换参考:
rxjava的ObserveOn和SubscribeOn的一些结论
RxJava线程变换之observeOn与subscribeOn
其中目前传递进来的是Type类型 这样就可以传Type 或者Class类型了 因为class继承Type接口
HttpUtils.request(url, params).get(new TypeToken<BaseResult<BaseInfo>>(){}.getType())
HttpUtils.request(url, params).get(TestResult.class )
以上两种方式都可以。只是TestResult就需要多一个类去继承BaseResult,这样接口多的时候就会比较蛋疼
如果Type想使用泛型,不想每次都new TypeToken<BaseResult<BaseInfo>>(){}.getType() 可以参考
搞定Gson泛型封装
不过如果是List<T> 那就又要在包装。感觉还是直接new TypeToken算了。同时还可以支持直接CLASS
最后使用方法是
String url = "http://demo.phalapi.net/?service=User.getBaseInfo&user_id=1";
Map<String, String> params = new HashMap<>();
params.put("key", "value");
HttpUtils.request(url, params)
.callback(new APICallback<BaseInfo>() {
@Override
public void onSuccess(BaseInfo data) {
Log.e("lch1", "name---------:" + data.info.name);
}
@Override
public void onFailed(APIStatus status) {
Log.e("lch1", "getMessage---------:" + status.getMessage());
}
}).get(new TypeToken<BaseResult<BaseInfo>>(){}.getType());
}
四、为Retrofit添加重试机制
参考文章:
All RxJava - 为Retrofit添加重试
RxJava与Retrofit的封装
public class RetryWhenNetworkException implements Function<Observable<? extends Throwable>, Observable<?>> {
/**
* retry次数
*/
private int mMaxRetryCount = 2;
/**
* 延迟
*/
private long mDelay = 1000;
/**
* 叠加延迟
*/
private long mIncreaseDelay = 1000;
public RetryWhenNetworkException() {
}
public RetryWhenNetworkException(int maxRetryCount) {
mMaxRetryCount = maxRetryCount;
}
public RetryWhenNetworkException(int maxRetryCount, long delay) {
mMaxRetryCount = maxRetryCount;
mDelay = delay;
}
public RetryWhenNetworkException(int maxRetryCount, long delay, long increaseDelay) {
mMaxRetryCount = maxRetryCount;
mDelay = delay;
mIncreaseDelay = increaseDelay;
}
@Override
public Observable<?> apply(Observable<? extends Throwable> observable) throws Exception {
return observable
.zipWith(Observable.range(1, mMaxRetryCount + 1), new BiFunction<Throwable, Integer, ThrowableWrapper>() {
@Override
public ThrowableWrapper apply(Throwable throwable, Integer curRetryCount) {
return new ThrowableWrapper(throwable, curRetryCount);
}
}).flatMap(new Function<ThrowableWrapper, Observable<?>>() {
@Override
public Observable<?> apply(ThrowableWrapper wrapper) {
//遭遇了IOException等时才重试网络请求,IllegalStateException,NullPointerException或者当你使用gson来解析json时还可能出现的JsonParseException等非I/O异常均不在重试的范围内。
if ((wrapper.throwable instanceof ConnectException
|| wrapper.throwable instanceof SocketTimeoutException
|| wrapper.throwable instanceof TimeoutException
|| wrapper.throwable instanceof HttpException
)) {
//如果超出重试次数也抛出错误,否则默认是会进入onCompleted
if (wrapper.curRetryCount <= mMaxRetryCount) {
Log.i("lch1", "网络错误,重试次数:" + wrapper.curRetryCount);
long delayTime = mDelay + (wrapper.curRetryCount - 1) * mIncreaseDelay; //使用二进制指数退避算法,每次都比上次长时间
return Observable.timer(delayTime, TimeUnit.MILLISECONDS, Schedulers.trampoline());
}
}
return Observable.error(wrapper.throwable);
}
});
}
private class ThrowableWrapper {
/**
* 当前重试次数
*/
private int curRetryCount;
/**
* 抛出的异常
*/
private Throwable throwable;
public ThrowableWrapper(Throwable throwable, int curRetryCount) {
this.curRetryCount = curRetryCount;
this.throwable = throwable;
}
}
}
最后调用
public static <T> ObservableTransformer<Response<ResponseBody>, T> translate(final Type typeOfT) {
return new ObservableTransformer<Response<ResponseBody>, T>() {
@Override
public ObservableSource apply(Observable upstream) {
return upstream
.map(new ServerResponseTranslateFunc<T>(typeOfT)) //进行数据转换
.retryWhen(new RetryWhenNetworkException()) //失败后的retry配置, 放到onErrorResumeNext前面,不然onErrorResumeNext会重新定义Exception
.onErrorResumeNext(new HttpResponseFunc()) //拦截服务器返回的错误
.subscribeOn(Schedulers.io()) // subscribeOn() 指定的就是发射事件的线程
.unsubscribeOn(Schedulers.io()) //取消订阅制定线程 // TODO: 2017/11/7 测试
.observeOn(AndroidSchedulers.mainThread()); //observerOn 指定的就是订阅者接收事件的线程。
}
};
}
注意:
1、RetryWhenNetworkException配置, 放到onErrorResumeNext前面,不然onErrorResumeNext会重新定义Exception
2、如果RetryWhenNetworkException放到onErrorResumeNext后面也可以, return new ThrowableWrapper(throwable, curRetryCount);的throwable修改成throwable.getCause() 里面这里重新定义Exception,变成ApiException 所以需要getCause()才能获取真正的错误
五、retrofit接口封装
参考文章:
你真的会用Retrofit2吗?Retrofit2完全教程
Retrofit解析2之使用简介
你应该知道的HTTP基础知识
以下内容待优化
/**
* 1、Map用来组合复杂的参数,并且对于FieldMap,HeaderMap,PartMap,QueryMap这四种作用方法的注解,其参数类型必须为Map实例,且key的类型必须为String类型,否则抛出异常。
* QueryMap作用于Get方法,形成url后面的参数键值对如:name=张三&name=李四&name=王五。同时进行url编码,把中文和特殊字符进行编码
* 2、Query、QueryMap与Field、FieldMap功能一样,生成的数据形式一样;Query、QueryMap的数据体现在Url上;Field、FieldMap的数据是请求体
* 3、@FormUrlEncoded 注解和@Multipart 注解不能同时使用,否则会抛出methodError(“Only one encoding annotation is allowed.”),可在ServiceMethod类中parseMethodAnnotation()方法中找到不能同时使用的具体原因。
* 4、使用@Body 注解的参数不能使用form 或multi-part编码,即如果为方法使用了FormUrlEncoded或Multipart注解,则方法的参数中不能使用@Body 注解,否则会抛出异常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)
* 参考:http://www.jianshu.com/p/345304325511
* http://www.jianshu.com/p/308f3c54abdd
*/
public interface RetrofitHttpService {
/**
* @return Response是retrofit2的, ResponseBody是okhttp3 参考http://www.jianshu.com/p/097947afddaf 这个可以做学习点
*/
@GET()
Observable<Response<ResponseBody>> get(@Url String url, @QueryMap Map<String, Object> params, @HeaderMap Map<String, String> headers);
/**
* 标准普通的post请求
*
* FormUrlEncoded:用于修饰Fiedl注解 和FileldMap注解
* 使用该注解,表示请求正文将使用表单网址编码
* 使用@FormUrlEncoded 注解的请求将具有"application/x-www-form-urlencoded" MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码。
* Content-Type: application/x-www-form-urlencoded;charset=utf-8
* @return
*/
@FormUrlEncoded
@POST()
Observable<Response<ResponseBody>> post(@Url String url, @FieldMap Map<String, Object> params, @HeaderMap Map<String, String> headers);
/**
* post-json格式请求
*
* @Body 注解定义的参数不能为null
* 当你发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,
* 使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化的结果直接作为请求体发送出去。
* @param url
* @param body body 可以传所有对象 map object 都可以 retrofit会通过convert把实体序列化
* 并自动指定Content-Type: application/json;charset=UTF-8,或者自己添加头部也行@Headers({"Content-Type: application/json","Accept: application/json"})//需要添加头
* 还能传RequestBody 作为body,这个就比较麻烦 可以参考:http://www.jianshu.com/p/32bfd5fd8b48
* @param headers
* @return
*/
@POST
Observable<Response<ResponseBody>> postJson(@Url String url, @Body Object body, @HeaderMap Map<String, String> headers);
/**
* 增加QueryMap 对path增加参数
*/
@POST
Observable<Response<ResponseBody>> postJson(@Url String url, @Body Object body, @HeaderMap Map<String, String> headers, @QueryMap Map<String, String> pathParams);
/**
* 使用 Multipart : 标记一个请求是Content-Type:multipart/form-data类型,需要和 @retrofit2.http.POST 一同使用,并且方法参数必须是 @retrofit2.http.Part 注解
* @return
*/
@Multipart
@POST()
Observable<Response<ResponseBody>> postMultipart(@Url String url, @PartMap Map<String, RequestBody> params, @HeaderMap Map<String, String> headers);
//--------------上传文件方法---------------------------------------------
/**
*
* http://www.jianshu.com/p/3f1cc5a7bf8c
* 为什么可以这样写:
* 1、 Retrofit会判断@Body的参数类型,如果参数类型为okhttp3.RequestBody,则Retrofit不做包装处理,直接丢给okhttp3处理。而MultipartBody是继承RequestBody,因此Retrofit不会自动包装这个对象。
* 2、同理,Retrofit会判断@Part的参数类型,如果参数类型为okhttp3.MultipartBody.Part,则Retrofit会把RequestBody封装成MultipartBody,再把Part添加到MultipartBody。
*/
/**
* 通过 MultipartBody和@body作为参数来上传
* 注意1:必须使用@POST,使用@Body注解参数,则不能使用@Multipart注解方法了
* 直接将所有的MultipartBody.Part合并到一个MultipartBody中
*/
@POST()
Observable<Response<ResponseBody>> postMultipart(@Url String url, @Body MultipartBody body, @HeaderMap Map<String, String> headers);
/**
*
* 通过 List<MultipartBody.Part> 传入多个part实现多文件上传
*
*
* 使用 Multipart : 标记一个请求是Content-Type:multipart/form-data类型,需要和 @retrofit2.http.POST 一同使用,并且方法参数必须是 @retrofit2.http.Part 注解
*
* 注意1:必须使用@POST注解为post请求
* 注意:使用@Multipart注解方法,必须使用@Part
* -@PartMap注解其参数
* 本接口中将文本数据和文件数据分为了两个参数,是为了方便将封装
* MultipartBody.Part的代码抽取到工具类中
* 也可以合并成一个 @Part参数
*
* @param params 用于封装文本数据
* @param parts 用于封装文件数据
* @return BaseResp为服务器返回的基本Json数据的Model类
*/
@Multipart
@POST()
Observable<Response<ResponseBody>> postMultipart(@Url String url, @PartMap Map<String, RequestBody> params, @Part List<MultipartBody.Part> parts, @HeaderMap Map<String, String> headers);
}