Retrofit 自定義返回類型

Demo 地址

相信現(xiàn)在大家都已近在使用 Retrofit + RxJava 框架進行開發(fā),我們也不例外,這里我們不會講如何使用這套框架,而是會講述我在開發(fā)過程中遇到的一個優(yōu)化需求:自定義 Retrofit 的請求接口返回類型,即下面 GitHubService 接口中 listRepos 的返回類型

//官方示例
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

現(xiàn)有的使用方式

在我們的代碼中現(xiàn)在是這樣來定義的:

public interface RestClientV1_0 {
    @GET("insurance/month_card/")
    Flowable<ResponseBody> getInsuranceCard();
}

ResponseBody 是我們和 API 約定好的數(shù)據(jù)結(jié)構(gòu),大概是這種形式:

public class ResponseBody {
    //定義業(yè)務成功或者失敗
    private String status;
    //Json 格式的字符串,可以反序列化成定義的 Java Bean
    private String content;
    private String errorCode;
    private String errorMsg;
    //省略大部分代碼
}

對于 getInsuranceCard() 方法我們使用的形式如下:

 DadaApplication.getInstance().getApiV1().getInsuranceCard()
                .compose(RxSchedulers.<ResponseBody>io_main(getView(), false))
                .as(getView().<ResponseBody>bindAutoDisposable())
                .subscribeWith(new ProgressSubscriber<ResponseBody>(getView()) {
                    @Override
                    public void onSuccess(ResponseBody response) {
                        InsuranceCard insuranceCard = response.getContentAs(InsuranceCard.class);
                        setInsuranceData(insuranceCard);
                    }
                });

在這里我們不關(guān)注 Retrofit 和 RxJava 的使用,可以看見在 onSuccess 回調(diào)方法之前,我們聲明的泛型類型全部為 ResponseBody 類型,在 onSuccess 回調(diào)中我們將 content 這個 Json 字符串解析成 InsuranceCard 對象。

現(xiàn)存問題

由上可知我們現(xiàn)在的使用方式存在兩種問題:

  1. 在定義接口方法的時候,全部聲明為 ResponseBody 類型,實際上 Api Response 會被解析成什么類型,無法從代碼聲明中得知,而需要去查閱 API 文檔
  2. 對 Response 的解析是放在 onSuccess() 方法中的,然而我們大部分的 onSuccess() 方法回調(diào)都是在主線程執(zhí)行,當解析數(shù)據(jù)比較大的時候就會造成卡頓

解決思路

最終我們期望對于這款框架的使用變成如下這種形式:

//接口方法的定義
@GET("insurance/month_card/")
DadaFlowable<InsuranceCard> getInsuranceCard();
    
//接口方法的調(diào)用
DadaApplication.getInstance().getApiV1().getInsuranceCard()
                .toFlowable()
                .compose(RxSchedulers.<InsuranceCard>io_main(getView(), false))
                .as(getView().<InsuranceCard>bindAutoDisposable())
                .subscribeWith(new DadaProgressSubscriber<InsuranceCard>(getView()) {
                    @Override
                    public void onDadaSuccess(InsuranceCard insuranceCard) {
                        setInsuranceData(insuranceCard);
                    }
                });
                
  1. ResponseBody 對使用者隱藏,只需要看到具體的業(yè)務類型
  2. 數(shù)據(jù)的解析應該放在子線程中

實際做了哪些

  1. 自定義 DadaFlowable<T> 在定義接口方法時替代 Flowable<T> 類型
  2. 重新定義 ApiResponse<T> 用來替代原先的 ResponseBody 類型
  3. 自定義 Converter 用來將 API 返回的 Response 轉(zhuǎn)換成我們需要的 ApiResponse<T> 類型
  4. 自定義 CallAdapter 來提取 ApiResponse<T> 中實際的業(yè)務類型 T

大致通過以上四步就可以實現(xiàn)我們的需求,下面我們來具體看一看這四步分別都做了些什么

自定義 DadaFlowable<T>

由于現(xiàn)階段我們只會對新的接口采用這種新的方式,原有的 Flowable<ResponseBody> 的形式仍然保留,因此我們需要自定義一個 DadaFlowable<T> 對象,其內(nèi)部仍然是生成一個 Flowable<T> 對象,如果依然在定義接口方法時使用 Flowable<T> 類型的話,它將會匹配到官方的 RxJava2CallAdapter (有關(guān)于 Retrofit 如何選擇 CallAdapter 以及 Convert 請自行閱讀 ServiceMethod 類的源碼,這里我也附上一篇 Retrofit 非常好的源碼解析 Android:手把手帶你 深入讀懂 Retrofit 2.0 源碼) 而無法匹配到我們接下來自定義的 CallAdapter

DadaFlowable<T> 的代碼目前十分簡單:

public class DadaFlowable<T> {
    private final Flowable<T> flowable;

    public DadaFlowable(Flowable<T> flowable) {
        this.flowable = flowable;
    }

    public Flowable<T> toFlowable() {
        return flowable;
    }
}

自定義 ApiResponse<T>

ApiResponse 的定義就更簡單了,幾乎算是對 ResponseBody 代碼的 Copy,只不過我們不在采用字符串的方式來聲明 content 屬性,而是采用泛型的方式:

public class ApiResponse<T> {
    public static final String OK = "ok";
    private static final String UNKNOWN_ERROR = "unknown_error";

    /**
     * api 響應狀態(tài)  ok 標識成功
     */
    private String status;
    /**
     * api 業(yè)務數(shù)據(jù)
     */
    private T content;
    /**
     * api 響應錯誤碼
     */
    private String errorCode;
    /**
     * errorCode 對應錯誤信息
     */
    private String errorMsg;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public boolean isOk() {
        return OK.equals(status);
    }

    public static <T> ApiResponse<T> unknownError(Throwable error) {
        ApiResponse<T> apiResponse = new ApiResponse<>();
        apiResponse.setStatus(UNKNOWN_ERROR);
        apiResponse.setErrorMsg(error.getMessage());
        return apiResponse;
    }
}

自定義 Converter

converter 的作用比較簡單,我們可以認為是它將接口返回的數(shù)據(jù)解析成我們需要的 Java Bean 對象:

public class FastJsonResponseBodyConverter<T> implements Converter<ResponseBody, ApiResponse<T>> {
    private final Type type;

    public FastJsonResponseBodyConverter(Type type) {
        this.type = type;
    }

    @Override
    public ApiResponse<T> convert(ResponseBody value) throws IOException {
        try {
            ApiResponse apiResponse = JSON.parseObject(value.string(), ApiResponse.class);
            Object content = apiResponse.getContent();
            if (apiResponse.isOk() && JSONObject.class != type && JSONArray.class != type && null != content) {
                apiResponse.setContent(JSON.parseObject(content.toString(), type));
            }
            return apiResponse;
        } catch (Throwable e) {
            e.printStackTrace();
            return ApiResponse.unknownError(e);
        }
    }
}

我們需要關(guān)注的是其中的 convert(ResponseBody value) 方法,它會將接口返回的 Response 解析成 ApiResponse<T>對象并返回,之后我們會講述在何處使用到了這個返回對象

自定義 CallAdapter 相關(guān)

我們定義了一個用于生產(chǎn) CallAdapter 的工廠,我只貼出這個工廠類里面的核心方法:

 @Override
    public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        Class<?> rawType = getRawType(returnType);
        if (rawType != DadaFlowable.class) {
            return null;
        }
        //省略...
        Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
        //省略...
        //一般走到這里 responseType 就是我們聲明的業(yè)務類型
        responseType = observableType;

        return new DadaRxJava2CallAdapter<>(responseType);

    }

這里的代碼也比較簡單,我將官方提供的 RxJava2CallAdapterFactory 代碼進行了一些修改和刪減,只有在聲明返回類型為 DadaFlowable<T> 的時候才會匹配到這個工廠,并且生成對應的 DadaRxJava2CallAdapter 對象:

public class DadaRxJava2CallAdapter<R> implements CallAdapter<ApiResponse<R>, Object> {
    private final Type responseType;

    DadaRxJava2CallAdapter(Type responseType) {
        this.responseType = responseType;
    }

    @Override
    public Type responseType() {
        return responseType;
    }

    @Override
    public Object adapt(Call<ApiResponse<R>> call) {
        Observable<Response<ApiResponse<R>>> responseObservable = new DadaCallExecuteObservable<>(call);
        DadaBodyObservable<R> bodyObservable = new DadaBodyObservable<>(responseObservable);
        return new DadaFlowable<>(bodyObservable.toFlowable(BackpressureStrategy.LATEST));
    }
}

自定義 DadaCallExecuteObservable<T>

DadaCallExecuteObservable 是照搬官方的 CallExecuteObservable 代碼僅僅換了個名字而已,我們主要看它的 subScribeActual 方法:

@Override
    protected void subscribeActual(Observer<? super Response<T>> observer) {
        // Since Call is a one-shot type, clone ait for each new observer.
        Call<T> call = originalCall.clone();
        //省略...
        try {
            Response<T> response = call.execute();
            if (!call.isCanceled()) {
                observer.onNext(response);
            }
            if (!call.isCanceled()) {
                terminated = true;
                observer.onComplete();
            }
        } catch (Throwable t) {
            //省略...
        }
    }

省略了部分代碼,當我們的下游 Subscriber 訂閱了 Observe 之后,將會調(diào)用 subscribeActual 方法,我們來看看該方法中的幾段重要代碼:

@Override public Response<T> execute() throws IOException {
    okhttp3.Call call;
    //省略...
    return parseResponse(call.execute());
  }
  
  
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();
    //省略...
    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      T body = serviceMethod.toResponse(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
    //省略...
    }
  }
  
/** Builds a method return value from an HTTP response body. */
R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
}

在執(zhí)行 retrofit 中的 call 對象(實際上是 OkHttpCall 對象)的 execute 方法的時候,實際上最終它會調(diào)用的 okhttp3.Call 對象的 execute 方法幫我們執(zhí)行網(wǎng)絡請求,并且調(diào)用 parseResponse 方法對返回的 response 進行解析,最終調(diào)用到的是我們上面自定義 Converter 對象的 convert 方法,返回了具體的 ApiResponse<T> 對象(這里是對上面介紹自定義 Converter 的應用)。

由此可知 Response<T> response = call.execute(); 中的 response 對象其實就是 Response<ApiResponse<某種業(yè)務類型>> 對象

在獲取到 response 對象之后,我們將調(diào)用 observer.onNext(response); 方法

自定義 DadaBodyObservable<T>

DadaRxJava2CallAdapter 的 adapt 方法可知,我們實際上是用 DadaBodyObservable 來構(gòu)造出一個 DadaFlowable 對象并且返回的,DadaBodyObservable 的代碼很簡單,它其實就一個代理,當我們在最外層使用 DadaFlowable.toFlowable()...這一套調(diào)用流程的時候會先調(diào)用 DadaBodyObservable 的 subscribeActual 方法,然后將該方法傳入的參數(shù)(實際上就是在上面解決思路段落中的 DadaProgressSubscriber 對象)包裝成 BodyObserver 對象然后對 DadaCallExecuteObservable 進行訂閱,代碼如下:

final class DadaBodyObservable<T> extends Observable<T> {
    private final Observable<Response<ApiResponse<T>>> upstream;

    DadaBodyObservable(Observable<Response<ApiResponse<T>>> upstream) {
        this.upstream = upstream;
    }

    @Override
    protected void subscribeActual(Observer<? super T> observer) {
        upstream.subscribe(new BodyObserver<>(observer));
    }
}

自定義 BodyObserver<R>

在 DadaCallExecuteObservable 中提到的 observer.onNext(response);方法中的 observer 對象實際上就是 BodyObserver 對象,代碼如下:

private static class BodyObserver<R> implements Observer<Response<ApiResponse<R>>> {
        private final Observer<? super R> observer;
        private boolean terminated;

        BodyObserver(Observer<? super R> observer) {
            this.observer = observer;
        }

        @Override
        public void onNext(Response<ApiResponse<R>> response) {
            if (response.isSuccessful()) {
                ApiResponse<R> apiResponse = response.body();
                if (apiResponse.isOk()) {
                    //業(yè)務 OK
                    observer.onNext(apiResponse.getContent());
                } else {
                    String apiErrorCode = apiResponse.getErrorCode();
                    String apiErrorMessage = apiResponse.getErrorMsg();
                    //業(yè)務失敗
                    Throwable t = new DadaThrowable(apiErrorCode, apiErrorMessage);
                    try {
                        observer.onError(t);
                    } catch (Throwable inner) {
                        Exceptions.throwIfFatal(inner);
                        RxJavaPlugins.onError(new CompositeException(t, inner));
                    }
                }
            } else {
                terminated = true;
                Throwable t = new HttpException(response);
                try {
                    observer.onError(t);
                } catch (Throwable inner) {
                    Exceptions.throwIfFatal(inner);
                    RxJavaPlugins.onError(new CompositeException(t, inner));
                }
            }
        }
    }

我們僅關(guān)注 onNext 方法,代碼比較簡單,對接口的請求狀態(tài)和業(yè)務狀態(tài)進行狀態(tài),然后回調(diào)給最外外外層 的 Subscriber 對象實際上是 DadaProgressSubscriber 對象的 onNext 或者 onError 方法

總結(jié)

其實你只要能了解的 RxJava2 的使用,并且閱讀掌握 Retrofit 當中關(guān)于類型轉(zhuǎn)換的源碼,就可以實現(xiàn)這個定制的功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李頭閱讀 3,264評論 5 14
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李頭閱讀 4,105評論 8 19
  • 簡介 剛接觸Retrofit的時候,就寫了一篇簡單的使用介紹:Retrofit 2.0基本使用方法,算是對Retr...
    Whyn閱讀 3,111評論 4 24
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,283評論 25 708
  • 聽歌時的清醒與朦朧 歌聲里揚起詞人的心事 錯開旋律響起自己的心事 聲在腦?;匦?律在耳機回轉(zhuǎn) 心事像馬達在耳機的連...
    金面笑飛俠閱讀 316評論 0 0

友情鏈接更多精彩內(nèi)容