我后知后觉,就不多细细说Retrofit了,比较好的介绍在用 Retrofit 2 简化 HTTP 请求。
大致说起来,只要用纯Java代码结合Annotation定义一个漂亮干净的API接口,例如
public static interface SomeService {
@GET("xxx/yyy")
Observable<String> someApi(@Query("qqq") String qqq);
}
然后就可以让Retrofit生成一个符合这个接口的Proxy一样的东西(的确,内部用的技术是Java7内置的Class Proxy技术,就是任何method调用都会掉到一个共通的方法里,里面能够知道method名,参数),
SomeService serviceInvoker = new Retrofit.Builder()
.baseUrl("http://some_web_site.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build()
.create(SomeService.class);
拿这这个东西(例子里的serviceInvoker),就可以像普通API调用一样使用了,不用写什么HttpClient关联的代码了,数据类型也自动搞定。
Observable<String> rx = serviceInvoker.someApi("oooops");
rx.subscribe(
result -> println(result),
error -> println("error: " + error));
除了Observable<...>影响心情外,其它的都感觉良好。
这个Observable就是Retrofit的小老婆ReactiveX了,又称为RxJava什么的。关于这个我以前写过RxJava的一些大白话。简而言之,ReactiveX不是那么直观,想要干什么总要委婉一点优雅一点才能通的过。
RxJava的作者估计也是个追求完美的设计家,硬生生地从各种看似分散的事儿里理出了这么一套模式还打出了一片天地,能够异步回调,能够像stream一样变形串起来操作,再加上一些暧昧的名词,看起来显得高大上,但是所以理解起来就得发挥十分的想象力,怎么爽就怎么想才行。
烦恼往往就出在小老婆ReactiveX身上 (大老婆是那个Call<?>的)。
这次的挑战是,需要在
rx.subscribe(...)
之前,加个retryWhen
定义,告诉他当出什么样的错误才重试 rx.retryWhen(errors -> errors.flatMap(error -> {
boolean needRetry = false;
if (restRetryCount >= 1) {
if (error instanceof IOException) {
needRetry = true;
} ...
}
if (needRetry) {
restRetryCount--;
return Observable.just(null); //means need retry
} else {
return Observable.error(error); //means finish with error
}
}))
.subscribe(...)
代码不是那么简洁可以忍着,但就算一行代码,也肯定不想在每个使用serviceInvoker的地方都这么干的。于是想写在共通里,
可是。。。似乎没地方插手,因为在共通代码
.create(SomeService.class)
附近没有出现这个rx
的身影。
这就是令人为难的地方了,不管三七二十一,先把事儿解决了再说,我就先上一个方法,思路就是找到创建出
rx
的地方,把代码插在那儿。经过跟踪,发现是RxJavaCallAdapterFactory.create()
里,于是换成一个新的newCallAdaptorFactory
来包着他,一旦得到了rx就加上.retryWhen
。 RxJavaCallAdapterFactory originCallAdaptorFactory = RxJavaCallAdapterFactory.create();
CallAdapter.Factory newCallAdaptorFactory = new CallAdapter.Factory() {
@Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
CallAdapter<?> ca = originCallAdaptorFactory.get(returnType, annotations, retrofit);
return new CallAdapter<Observable<?>>() {
@Override
public Type responseType() {
return ca.responseType();
}
int restRetryCount = 3;
@Override
public <R> Observable<?> adapt(Call<R> call) {
Observable<?> rx = (Observable<?>) ca.adapt(call);
return rx.retryWhen(errors -> errors.flatMap(error -> {
boolean needRetry = false;
if (restRetryCount >= 1) {
if (error instanceof IOException) {
needRetry = true;
} else if (error instanceof HttpException) {
if (((HttpException) error).code() > 400) { //please modify this condition
needRetry = true;
}
}
}
if (needRetry) {
println("******* retry *******");
restRetryCount--;
return Observable.just(null);
} else {
return Observable.error(error);
}
}));
}
};
}
};
看看运行结果,的确重试了3次了。至于最后一个错误纯粹表示最终的错误。
20:00:39.453 @main ******* retry *******
20:00:39.464 @main ******* retry *******
20:00:39.464 @main ******* retry *******
20:00:39.497 @main error: java.net.UnknownHostException: some_web_site.com
很怀疑Retrofit的作者看见这个代码会大怒,说还有这个那个更好的方法为什么不用?哎,谁让你做的那么深奥呢。
在stackoverflow网站上android - How to retry HTTP requests with OkHttp/Retrofit? - Stack Overflow,有人回答用OkHttpClient的Interceptor来做,可是网络压根连不上的时候那个方法就没用。从方法论上说,就应该和那种http client无关。