HTTP Client新宠Retrofit和ReactiveX结婚以后,新的挑战来了

我后知后觉,就很少细细说Retrofit了,比较好的介绍在用 Retrofit 2 简化 HTTP 请求java

大体提及来,只要用纯Java代码结合Annotation定义一个漂亮干净的API接口,例如android

public static interface SomeService {
        @GET("xxx/yyy")
        Observable<String> someApi(@Query("qqq") String qqq);
    }

而后就可让Retrofit生成一个符合这个接口的Proxy同样的东西(的确,内部用的技术是Java7内置的Class Proxy技术,就是任何method调用都会掉到一个共通的方法里,里面可以知道method名,参数),git

SomeService serviceInvoker = new Retrofit.Builder()
                .baseUrl("http://some_web_site.com")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build()
                .create(SomeService.class);

拿这这个东西(例子里的serviceInvoker),就能够像普通API调用同样使用了,不用写什么HttpClient关联的代码了,数据类型也自动搞定。github

Observable<String> rx = serviceInvoker.someApi("oooops");
        rx.subscribe(
                result -> println(result),
                error -> println("error: " + error));

除了Observable<...>影响心情外,其它的都感受良好。web

这个Observable就是Retrofit的小老婆ReactiveX了,又称为RxJava什么的。关于这个我之前写过RxJava的一些大白话。简而言之,ReactiveX不是那么直观,想要干什么总要委婉一点优雅一点才能通的过。网络

RxJava的做者估计也是个追求完美的设计家,硬生生地从各类看似分散的事儿里理出了这么一套模式还打出了一片天地,可以异步回调,可以像stream同样变形串起来操做,再加上一些暧昧的名词,看起来显得高大上,可是因此理解起来就得发挥十分的想象力,怎么爽就怎么想才行。异步

烦恼每每就出在小老婆ReactiveX身上 (大老婆是那个Call<?>的)。ide

此次的挑战是,须要在rx.subscribe(...)以前,加个retryWhen定义,告诉他当出什么样的错误才重试oop

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的做者看见这个代码会大怒,说还有这个那个更好的方法为何不用?哎,谁让你作的那么深奥呢。

完整的代码在 https://github.com/sjitech/Retrofit_Test

在stackoverflow网站上android - How to retry HTTP requests with OkHttp/Retrofit? - Stack Overflow ,有人回答用OkHttpClient的Interceptor来作,但是网络压根连不上的时候那个方法就没用。从方法论上说,就应该和那种http client无关。

相关文章
相关标签/搜索