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無關。

相關文章
相關標籤/搜索