拆輪子系列:Retrofit2
拆輪子系列:Retrofit2
[TOC]
Retrofit本質上是對OkHttpClient網路互動的封裝,它接管的是網路請求前和網路請求後,即HttpRequest封裝,HttpResponse處理,網路請求過程則交給OkHttpClient。Retrofit採用了一套非常好的設計思想,使得其提供的各部分功能擴充套件性強,耦合度低。
Retrofit設計流程如下:

image
Retrofit詳細過程梳理如下:
定義網路介面
ResultType 為原始的 retrofit2.Call:
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }
ResultType為RxJava Observable:
public interface StudioApiService { @GET("/studio/doctors/{docId}/clinics/{clinicId}") Observable<UserClinic> getClinicItem( @Path("docId") String docId, @Path("clinicId") String clinicId); @PUT("/studio/doctors/{docId}/clinics/{clinicId}") Observable<UserClinic> putClinic( @Path("docId") String docId, @Path("clinicId") String clinicId, @Body UserClinic clinic); @DELETE("/studio/doctors/{docId}/clinics/{clinicId}") Observable<Void> deleteClinic( @Path("docId") String docId, @Path("clinicId") String clinicId); @POST("/studio/doctors/{docId}/clinics") Observable<UserClinic> postClinic(@Path("docId") String docId, @Body UserClinic clinic); }
建立Retrofit例項
new Retrofit.Builder() .baseUrl(url) .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) .addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create(context)) .client(setupClient()) .build();
API 介面的呼叫
API介面的呼叫過程:
StudioAPiService apiService = retrofit.create(StudioAPiService.class);
Create過程採用的是動態代理方式,Retrofit為介面類生成一個動態代理,通過這種方式呼叫介面時Retrofit自動接管了呼叫的過程。程式碼如下,其中Proxy.newProxyInstance就是一個標準的動態代理過程
public <T> T create(final Class<T> service) { ... return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
呼叫過程中有關鍵的三個步驟。
ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall);
這是Retrofit呼叫的關鍵三步:
- 建立ServiceMethod:根據傳入的method建立,這一步涉及到Annotation的解析等,後面會詳解;
- 建立OkHttpCall:根據ServiceMethod建立;
- 執行OkHttpCall進行網路請求,涉及到CallAdapter切換
ServiceMethod
看看 ServiceMethod 的建構函式:
ServiceMethod(Builder<R, T> builder) { this.callFactory = builder.retrofit.callFactory(); this.callAdapter = builder.callAdapter; this.baseUrl = builder.retrofit.baseUrl(); this.responseConverter = builder.responseConverter; this.httpMethod = builder.httpMethod; this.relativeUrl = builder.relativeUrl; this.headers = builder.headers; this.contentType = builder.contentType; this.hasBody = builder.hasBody; this.isFormEncoded = builder.isFormEncoded; this.isMultipart = builder.isMultipart; this.parameterHandlers = builder.parameterHandlers; }
成員很多,但這裡我們重點關注四個成員:callFactory,callAdapter, responseConverter 和 parameterHandlers。
- callFactory 負責建立 HTTP 請求,HTTP 請求被抽象為了 okhttp3.Call 類,它表示一個已經準備好,可以隨時執行的 HTTP 請求;
- callAdapter 把 retrofit2.Call<T> 轉為 T(注意和 okhttp3.Call 區分開來,retrofit2.Call<T> 表示的是對一個 Retrofit 方法的呼叫),這個過程會發送一個 HTTP 請求,拿到伺服器返回的資料(通過 okhttp3.Call 實現),並把資料轉換為宣告的 T 型別物件(通過 Converter<F, T> 實現);
- responseConverter 是 Converter<ResponseBody, T> 型別,負責把伺服器返回的資料(JSON、XML、二進位制或者其他格式,由 ResponseBody 封裝)轉化為 T 型別的物件;
- parameterHandlers 則負責解析 API 定義時每個方法的引數,並在構造 HTTP 請求時設定引數;
callFactory
this.callFactory = builder.retrofit.callFactory(),所以 callFactory 實際上由 Retrofit 類提供,而我們在構造 Retrofit 物件時,可以指定 callFactory,如果不指定,將預設設定為一個 okhttp3.OkHttpClient。
okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); }
callFactory具體在什麼時候使用呢?
private okhttp3.Call createRawCall() throws IOException { Request request = serviceMethod.toRequest(args); okhttp3.Call call = serviceMethod.callFactory.newCall(request); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call; }
serviceMethod.callFactory.newCall(request);這就是她的作用,用來建立一個 okhttp3.Call物件,然後通過這個call物件發起網路請求。
callAdapter
關於Retrofit中的call,涉及到以下三個:
retrofit2.Call介面如下:
public interface Call<T> extends Cloneable { Response<T> execute() throws IOException; void enqueue(Callback<T> callback); boolean isExecuted(); void cancel(); boolean isCanceled(); Call<T> clone(); Request request(); }
retrofit2.OkHttpCall類,這是對retrofit2.Call的實現。Retrofit下的網路請求會被轉換成retrofit2.OkHttpCall,其背後則是轉換成okhttp3.Call,執行,這裡就把具體的網路請求委託給了OkHttpClient執行了。
final class OkHttpCall<T> implements Call<T> { @Override public void enqueue(final Callback<T> callback) { checkNotNull(callback, "callback == null"); okhttp3.Call call; Throwable failure; ... call.enqueue(new okhttp3.Callback() { @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) throws IOException { Response<T> response; try { response = parseResponse(rawResponse); } catch (Throwable e) { callFailure(e); return; } callSuccess(response); } @Override public void onFailure(okhttp3.Call call, IOException e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { t.printStackTrace(); } } private void callFailure(Throwable e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { t.printStackTrace(); } } private void callSuccess(Response<T> response) { try { callback.onResponse(OkHttpCall.this, response); } catch (Throwable t) { t.printStackTrace(); } } }); } }
下面再來講講serviceMethod.callAdapter.adapt(okHttpCall),以RxJavaCallAdapterFactory中的RxJavaCallAdapter,前者是後者的工廠類。
下來看一下RxJavaCallAdapterFactory的構造方法,注意到isAsync,預設是false,即網路請求預設是同步的。
private RxJavaCallAdapterFactory(@Nullable Scheduler scheduler, boolean isAsync) { this.scheduler = scheduler; this.isAsync = isAsync; }
再來看一下RxJavaCallAdapter究竟如何實現Call物件的adapter(), 很簡單,為call物件建立一個RxJava的Observable。其中CallEnqueueOnSubscribe、CallExecuteOnSubscribe最終呼叫的又是OkHttpCall中的相應的enqueue()、execute()方法
@Override public Object adapt(Call<R> call) { OnSubscribe<Response<R>> callFunc = isAsync ? new CallEnqueueOnSubscribe<>(call) : new CallExecuteOnSubscribe<>(call); OnSubscribe<?> func; if (isResult) { func = new ResultOnSubscribe<>(callFunc); } else if (isBody) { func = new BodyOnSubscribe<>(callFunc); } else { func = callFunc; } Observable<?> observable = Observable.create(func); if (scheduler != null) { observable = observable.subscribeOn(scheduler); } if (isSingle) { return observable.toSingle(); } if (isCompletable) { return observable.toCompletable(); } return observable; }
responseConverter
這個很好理解,對Response的解析,將Response轉換成最終物件,跟蹤程式碼可以看到。在OkHttpCall中拿到最終的response之後,會呼叫
T body = serviceMethod.toResponse(catchingBody); R toResponse(ResponseBody body) throws IOException { return responseConverter.convert(body); } 以GsonConverterFactory提供的GsonResponseBodyConverter為例: @Override public T convert(ResponseBody value) throws IOException { JsonReader jsonReader = gson.newJsonReader(value.charStream()); try { return adapter.read(jsonReader); } finally { value.close(); } }
parameterHandlers
每個引數都會有一個 ParameterHandler,由 ServiceMethod#parseParameter 方法負責建立,其主要內容就是解析每個引數使用的註解型別(諸如 Path,Query,Field 等),對每種型別進行單獨的處理。構造 HTTP 請求時,我們傳遞的引數都是字串,那 Retrofit 是如何把我們傳遞的各種引數都轉化為 String 的呢?還是由 Retrofit 類提供 converter!
其關係流程為:
API Method--(ParameterHandler)--> retrofit2.RequestBuilder --> okhttp3.Request
先來看看ServiceMethod中的toRequest方法:
Request toRequest(@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types. ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers; int argumentCount = args != null ? args.length : 0; if (argumentCount != handlers.length) { throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")"); } for (int p = 0; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); } return requestBuilder.build(); }
再來看看RequestBuilder.build() 方法:
Request build() { HttpUrl url; HttpUrl.Builder urlBuilder = this.urlBuilder; if (urlBuilder != null) { url = urlBuilder.build(); } else { // No query parameters triggered builder creation, just combine the relative URL and base URL. //noinspection ConstantConditions Non-null if urlBuilder is null. url = baseUrl.resolve(relativeUrl); if (url == null) { throw new IllegalArgumentException( "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl); } } RequestBody body = this.body; if (body == null) { // Try to pull from one of the builders. if (formBuilder != null) { body = formBuilder.build(); } else if (multipartBuilder != null) { body = multipartBuilder.build(); } else if (hasBody) { // Body is absent, make an empty body. body = RequestBody.create(null, new byte[0]); } } MediaType contentType = this.contentType; if (contentType != null) { if (body != null) { body = new ContentTypeOverridingRequestBody(body, contentType); } else { requestBuilder.addHeader("Content-Type", contentType.toString()); } } return requestBuilder .url(url) .method(method, body) .build(); }
Multipart 處理
@Multipart 是Retrofit2對多檔案上傳處理的封裝,本質上是對multipart/form-data的封裝,這個封裝依賴於OkHttp中提供的MultipartBody。先來看一下@Multipart的使用方式:
public interface UploadService { /** * @Multipart 這個標記很重要,Retrofit會判斷是否有這個標記來對引數重新封裝 * * 針對介面2,引數為MultipartBody, 它本身就是一個RequestBody,Retrofit據此判斷不進行再次封裝 */ @Multipart @POST("/upload") Call<Void> uploadImages(@Part() List<MultipartBody.Part> parts); @Multipart @POST("/upload") Call<Void> uploadImages(@Part() MultipartBody.Part part, @Part("description") RequestBody description); @POST("/upload") Call<Void> uploadImages(@Body MultipartBody body); }
其中,可以將MultipartBody.Part、MultipartBody的建立封裝成通用的兩個方法,它們的建立方式都是由OKHttp提供的,目的都是將之轉換成OKHttp中的MultipartBody。如下:
public class FileToHttpBody { public static MultipartBody filesToMultiBody(List<File> files) { MultipartBody.Builder builder = new MultipartBody.Builder(); for (File file: files) { RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file); builder.addFormDataPart("file", file.getName(), requestBody); } builder.setType(MultipartBody.FORM); MultipartBody multipartBody = builder.build(); return multipartBody; } public static List<MultipartBody.Part> filesToMultiParts(List<File> files) { List<MultipartBody.Part> parts = new ArrayList<>(); for (File file: files) { RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file); parts.add(MultipartBody.Part.createFormData("file", file.getName(), requestBody)); } return parts; } }
下面我們再來看一下Retrofit對@Multipart、@Part這兩個標籤分別幹了什麼。
@Multipart
private void parseMethodAnnotation(Annotation annotation) { ... } else if (annotation instanceof Multipart) { if (isFormEncoded) { throw methodError("Only one encoding annotation is allowed."); } isMultipart = true; } ... }
Retrofit根據@Multipart打上標記isMultipart,後續依此封裝成MultipartBody。
@Part
private ParameterHandler<?> parseParameterAnnotation( int p, Type type, Annotation[] annotations, Annotation annotation) { if (annotation instanceof Part) { if (!isMultipart) { throw parameterError(p, "@Part parameters can only be used with multipart encoding."); } Part part = (Part) annotation; gotPart = true; String partName = part.value(); Class<?> rawParameterType = Utils.getRawType(type); if (partName.isEmpty()) { if (Iterable.class.isAssignableFrom(rawParameterType)) { ... else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) { return ParameterHandler.RawPart.INSTANCE; } else { throw parameterError(p, "@Part annotation must supply a name or use MultipartBody.Part parameter type."); } } else { Headers headers = Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"", "Content-Transfer-Encoding", part.encoding()); ... Converter<?, RequestBody> converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations); return new ParameterHandler.Part<>(headers, converter); } } } }
這裡,提到了兩個ParameterHandler:ParameterHandler.RawPart, ParameterHandler.Part。這兩個ParameterHandler直接呼叫了MultipartBody
中的addPart()方法,其原始碼如下:
static final class RawPart extends ParameterHandler<MultipartBody.Part> { static final RawPart INSTANCE = new RawPart(); private RawPart() { } @Override void apply(RequestBuilder builder, @Nullable MultipartBody.Part value) throws IOException { if (value != null) { // Skip null values. builder.addPart(value); } } }
static final class Part<T> extends ParameterHandler<T> { private final Headers headers; private final Converter<T, RequestBody> converter; Part(Headers headers, Converter<T, RequestBody> converter) { this.headers = headers; this.converter = converter; } @Override void apply(RequestBuilder builder, @Nullable T value) { if (value == null) return; // Skip null values. RequestBody body; try { body = converter.convert(value); } catch (IOException e) { throw new RuntimeException("Unable to convert " + value + " to RequestBody", e); } builder.addPart(headers, body); } }
總結
retrofit2一個將工廠模式發揮到極致的優秀庫,值得細細體味。它的整個過程可以總結為以下幾個步驟:
- 對網路請求方式的封裝,直接以介面呈現;
- 對介面的解析,轉換成RequestBuilder,最終轉成為okhttp3.Request;
- 將Request構造成retrofit2.OkHttpCall,轉換成okhttp3.Call執行;
- 通過CallAdapter接管Call執行回撥,回撥中涉及到Response的解析轉換等