Retrofit 自定義返回型別
相信現在大家都已近在使用Retrofit +RxJava 框架進行開發,我們也不例外,這裡我們不會講如何使用這套框架,而是會講述我在開發過程中遇到的一個優化需求:自定義 Retrofit 的請求介面返回型別,即下面 GitHubService 介面中 listRepos 的返回型別
//官方示例 public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }
現有的使用方式
在我們的程式碼中現在是這樣來定義的:
public interface RestClientV1_0 { @GET("insurance/month_card/") Flowable<ResponseBody> getInsuranceCard(); }
ResponseBody是我們和 API 約定好的資料結構,大概是這種形式:
public class ResponseBody { //定義業務成功或者失敗 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); } });
在這裡我們不關注 Retrofit 和 RxJava 的使用,可以看見在onSuccess 回撥方法之前,我們宣告的泛型型別全部為ResponseBody 型別,在onSuccess 回撥中我們將content 這個 Json 字串解析成InsuranceCard 物件。
現存問題
由上可知我們現在的使用方式存在兩種問題:
- 在定義介面方法的時候,全部宣告為ResponseBody 型別,實際上 Api Response 會被解析成什麼型別,無法從程式碼宣告中得知,而需要去查閱 API 文件
- 對 Response 的解析是放在onSuccess() 方法中的,然而我們大部分的onSuccess() 方法回撥都是在主執行緒執行,當解析資料比較大的時候就會造成卡頓
解決思路
最終我們期望對於這款框架的使用變成如下這種形式:
//介面方法的定義 @GET("insurance/month_card/") DadaFlowable<InsuranceCard> getInsuranceCard(); //介面方法的呼叫 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); } });
- ResponseBody 對使用者隱藏,只需要看到具體的業務型別
- 資料的解析應該放在子執行緒中
實際做了哪些
-
自定義
DadaFlowable<T>
在定義介面方法時替代Flowable<T>
型別 -
重新定義
ApiResponse<T>
用來替代原先的ResponseBody
型別 -
自定義
Converter
用來將 API 返回的 Response 轉換成我們需要的ApiResponse<T>
型別 -
自定義
CallAdapter
來提取ApiResponse<T>
中實際的業務型別T
大致通過以上四步 就可以實現我們的需求,下面我們來具體看一看這四步分別都做了些什麼
自定義DadaFlowable<T>
由於現階段我們只會對新的介面採用這種新的方式,原有的Flowable<ResponseBody>
的形式仍然保留,因此我們需要自定義一個DadaFlowable<T>
物件,其內部仍然是生成一個Flowable<T>
物件,如果依然在定義介面方法時使用Flowable<T>
型別的話,它將會匹配到官方的RxJava2CallAdapter
(有關於 Retrofit 如何選擇 CallAdapter 以及 Convert 請自行閱讀 ServiceMethod 類的原始碼,這裡我也附上一篇 Retrofit 非常好的原始碼解析ofollow,noindex">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 響應狀態ok 標識成功 */ private String status; /** * api 業務資料 */ 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 的作用比較簡單,我們可以認為是它將介面返回的資料 解析成我們需要的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); } } }
我們需要關注的是其中的convert(ResponseBody value)
方法,它會將介面返回的 Response 解析成ApiResponse<T>
物件並返回,之後我們會講述在何處使用到了這個返回物件
自定義CallAdapter
相關
我們定義了一個用於生產 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 就是我們宣告的業務型別 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 之後,將會呼叫 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); }
在執行 retrofit 中的 call 物件(實際上是 OkHttpCall 物件)的execute
方法的時候,實際上最終它會呼叫的 okhttp3.Call 物件的 execute 方法幫我們執行網路請求,並且呼叫parseResponse
方法對返回的 response 進行解析,最終呼叫到的是我們上面自定義 Converter 物件的convert
方法,返回了具體的ApiResponse<T>
物件(這裡是對上面介紹自定義 Converter 的應用
)。
由此可知Response<T> response = call.execute();
中的 response 物件其實就是Response<ApiResponse<某種業務型別>>
物件
在獲取到 response 物件之後,我們將呼叫observer.onNext(response);
方法
自定義DadaBodyObservable<T>
由DadaRxJava2CallAdapter
的 adapt 方法可知,我們實際上是用DadaBodyObservable
來構造出一個DadaFlowable
物件並且返回的,DadaBodyObservable 的程式碼很簡單,它其實就一個代理,當我們在最外層使用 DadaFlowable.toFlowable()...這一套呼叫流程的時候會先呼叫 DadaBodyObservable 的subscribeActual
方法,然後將該方法傳入的引數(實際上就是在上面解決思路段落
中的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()) { //業務 OK observer.onNext(apiResponse.getContent()); } else { String apiErrorCode = apiResponse.getErrorCode(); String apiErrorMessage = apiResponse.getErrorMsg(); //業務失敗 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)); } } } }
我們僅關注 onNext 方法,程式碼比較簡單,對介面的請求狀態和業務狀態進行狀態,然後回撥給最外外外層 的 Subscriber 物件實際上是 DadaProgressSubscriber 物件的 onNext 或者 onError 方法
總結
其實你只要能瞭解的 RxJava2 的使用,並且閱讀掌握 Retrofit 當中關於型別轉換的原始碼,就可以實現這個定製的功能。