拆輪子系列:Retrofit2
[TOC]
Retrofit本質上是對OkHttpClient網絡交互的封裝,它接管的是網絡請求前和網絡請求后,即HttpRequest封裝,HttpResponse處理,網絡請求過程則交給OkHttpClient。Retrofit采用了一套非常好的設計思想,使得其提供的各部分功能擴展性強,耦合度低。
Retrofit設計流程如下:
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的解析轉換等