1. 程式人生 > >Retrofit+Rxjava實現巢狀邏輯的鏈式呼叫

Retrofit+Rxjava實現巢狀邏輯的鏈式呼叫

最近做app有一個需求,service的某個介面(B介面)呼叫很慢,所以不能頻繁的呼叫,然後service就想了一個邏輯:先返回一個呼叫速度快的介面(A介面),裡面有一個欄位,一旦這個欄位發生了改變,再去呼叫第二個介面(B介面)。我們app這邊的邏輯也很簡單,先把A介面呼叫返回的值用sharedPreference存到本地,然後每次呼叫A介面的時候都去對比一下本地的值,要是相等就說明不需要呼叫B介面,要是不相等就要呼叫!
然後,我以最快的速度,按照之前的套路寫完了
1,先寫2個model類對應介面A和介面B
2,解析兩個介面的資料。(Gson+okhttpfinal(封裝okhttp))
3,寫邏輯,先呼叫A,裡面巢狀B
然後寫完了,我突然想到,好像可以用Retrofit+Rxjava試試,因為我個人是比較反感巢狀的,尤其是波浪形到嗎,剪不斷理還亂的邏輯。
(友情提示:這篇文章適合已經熟悉簡單的Retrofit+Rxjava模式呼叫介面的同學,要是不熟悉,建議先收藏本文,去熟悉一下,再回來看,你會獲益匪淺。這套流程是我們國外的一個開發寫的,我稍加整理,貢獻給大家。)

第一步 :建立每個介面的model類

名字是
介面A的model:AppdataVersionEntity
介面B的model:AppdataDownLoadEntity
這個地方程式碼就不給了,很簡單,如果使用gson解析json,建議使用GsonFormat外掛,很方便。

第二步 :封裝Retrofit

1、按照套路,建立一個類來對Retrofit進行簡單的封裝

 public RESTClient(Context context) {
        this.context = context;

        Retrofit retrofit = new Retrofit.Builder
() .baseUrl("")//這個地方一般放你的域名,所有的介面都有這種格式 .client(provideOkHttpClient(context))//自定義client,如果不需要可以用預設的。 .addConverterFactory(GsonConverterFactory.create())//這一步就省去了手動解析json資料了。 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build
(); restAPI = retrofit.create(RestAPI.class);//建立 }

2、再來看上面說到的自定義client。

private OkHttpClient provideOkHttpClient(final Context context) {
        //抓log的,要在app的build.gradle的dependencies裡面compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        File httpCacheDirectory = new File(context.getCacheDir(), "responses");//建立快取檔案
        Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);//設定快取10M
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);//log的等級,4種等級,這是最詳細的一種等級
        OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                .connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)//超時時間
                .readTimeout(60 * 1000, TimeUnit.MILLISECONDS)//超時時間
                .addInterceptor(new Interceptor() {//新增攔截器
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request request = chain.request();
                        HttpUrl httpUrl = request.url().newBuilder()
                        //這個地方的addQueryParameter是所有介面都附加的兩個值,因各家app而異,加到這個地方就省去了,在retrofit裡面單獨新增的麻煩。
                                .addQueryParameter("v", "1.0.3")
                                .addQueryParameter("client","1")
                                .build();
                        request = request.newBuilder().url(httpUrl).build();
                        Response response = chain.proceed(request);
                        Log.d("Response Code", response.code() + "");
                        if (response.code() == 401) {//這個地方可以根據返回碼做一些事情。通過sendBroadcast發出去。
                            Intent intent = new Intent("Logout");
                            intent.putExtra("badAuth", true);
                            context.sendBroadcast(intent);
                        }
                        return response;
                    }
                })
                .addInterceptor(loggingInterceptor)//把上面的log攔截器新增進來
                .cache(cache)//新增快取
                .build();//build生效
        return okHttpClient;//返回client
    }

3、在這個類中,新增一個介面,來封裝需要呼叫的介面

public interface RestAPI {
        @FormUrlEncoded
        @POST("介面A")
        Observable<AppdataVersionEntity> getAppdataVersion();
        @FormUrlEncoded
        @POST("介面B")
        Observable<AppdataDownLoadEntity> getAppdataDownLoad();
    }

然後再這個類中建立一個對外開放的方法呼叫介面

   private RestAPI restAPI;
   public RestAPI getRestAPI() {
        return restAPI;
    }

第三步:呼叫介面

經過上面的封裝,我們可以按照邏輯,呼叫介面了。邏輯已經在文章的開頭說過了,這裡再說一遍:有一個需求,service的某個介面(B介面)呼叫很慢,所以不能頻繁的呼叫,然後service就想了一個邏輯:先返回一個呼叫速度快的介面(A介面),裡面有一個欄位,一旦這個欄位發生了改變,再去呼叫第二個介面(B介面)。我們app這邊的邏輯也很簡單,先把A介面呼叫返回的值用sharedPreference存到本地,然後每次呼叫A介面的時候都去對比一下本地的值,要是相等就說明不需要呼叫B介面,要是不相等就要呼叫!

 RESTClient client = new RESTClient(homeActivity);
        client.getRestAPI().getAppdataVersion()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<AppdataVersionEntity>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onNext(AppdataVersionEntity appdataVersionEntity) {//在呼叫第一個介面之後呼叫第二個介面,這裡出現了巢狀
                        version = appdataVersionEntity.getVersion()+"";
                        String localVersion =  getLocalAppVersion(homeActivity);
                        if(){//"本地版本發生變化"
                        //生變化的邏輯,要去呼叫B介面
                            client .getRestAPI().getAppdataDownLoad()
                                    .subscribeOn(Schedulers.io())
                                    .observeOn(AndroidSchedulers.mainThread())
                                    .subscribe(new Observer<AppdataDownLoadEntity>() {
                                        @Override
                                        public void onCompleted() {
                                        }

                                        @Override
                                        public void onError(Throwable e) {
                                        }

                                        @Override
                                        public void onNext(AppdataDownLoadEntity appdataDownLoadEntity) {
                                           //呼叫了B介面,做一些邏輯
                                        }
                                    });
                        }else{
                            //沒發生變化的邏輯
                        }
                    }
                });

上面的虛擬碼大體一看就行,想表達一個意思是,邏輯上要巢狀呼叫介面,這時候Retrofit+Rxjava也出現了巢狀,這種前臺是違背了Jack設計Retrofit的初衷的,肯定是不建議的。怎麼優化?用flatMap!往下看。
(要是你對observeOn和subscribeOn的關係不理解,請看我的這篇文章

 RESTClient client = new RESTClient(homeActivity);
         client .getRestAPI()
                .getAppdataVersion()
                .flatMap(new Func1<AppdataVersionEntity, Observable<AppdataDownLoadEntity>>() {//呼叫第一個介面(A介面)**註釋1**
                    @Override
                    public Observable<AppdataDownLoadEntity> call(AppdataVersionEntity appdataVersionEntity) {
                        if(){
                            //如果發生了改變,就去呼叫介面B
                            return  client.getRestAPI().getAppdataDownLoad();
                        }else{
                            //如果沒發生改變,做一些邏輯
                        }
                        return null;//如果return null就繼續向下執行,並不會crash
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<AppdataDownLoadEntity>() {
                    @Override
                    public void onNext(AppdataDownLoadEntity appdataDownLoadEntity) {
                       //這是執行介面B,做一些邏輯
                    }
                    @Override
                    public void onCompleted() {
                        JLogUtils.i("robin3","onCompleted");
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                });

好了,看看這程式碼,左邊的 ## . ## 完全對其,再也不用看大波浪了,心情愉悅。最重要的是這對後面改這塊程式碼的人很友好。

上面有一個我加粗的註釋1,我想解釋一下,這個地方可能會發生問題,一定要穿對泛型,否則會報錯,這個泛型是什麼意思呢?這要看Fun1函式的引數了,第一個是介面A的model型別(AppdataVersionEntity),第二個引數是你要去請求的B介面 ( Observable)。所以這個地方你應該也就明白了flatMap的作用:進去A,出來B。具體到專案就是,穿進去一個String,出來一個他的hashmap,傳進一個userId,出來一個包含userId的model類,傳一個boolean,出一個Observable。

有說的不對的,不理解的歡迎在下面指出!