版權宣告:本文為博主原創文章,未經博主允許不得轉載
文章分類:Android知識體系 - 網路程式設計
複製程式碼
一、前言
Retrofit是一個基於OkHttp、遵循RESTful API設計風格的網路請求封裝框架,本文將按照其工作流程逐步分析對應的原始碼(本文使用的Retrofit版本為2.5.0)
二、原始碼分析
1. 請求示例
以下是一次簡單的請求示例,首先我們需要定義一個介面API,並使用註解描述其中的API方法
public interface ExpressService {
@GET("query")
Call<ResponseBody> get(@Query("type") String type, @Query("postid") String postid);
}
複製程式碼
然後是Retrofit的工作流,可以分為三步:
- 構建Retrofit物件
- 載入API方法配置,生成請求執行器Call
- 使用Call物件執行請求,處理響應資料
public void asyncGet() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.kuaidi100.com/")
.build();
ExpressService expressService = retrofit.create(ExpressService.class);
Call<ResponseBody> call = expressService.get("ems", "11111111");
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
複製程式碼
下面我們將按照上述過程,分析對應的原始碼
2. 構建Retrofit物件
本章我們將分析Retrofit物件構建的過程,以下是對應的示例程式碼
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.kuaidi100.com/")
.build();
複製程式碼
2.1 Retrofit類的成員變數
首先來看Retrofit類宣告瞭哪些成員變數
public final class Retrofit {
private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
final okhttp3.Call.Factory callFactory;
final HttpUrl baseUrl;
final List<Converter.Factory> converterFactories;
final List<CallAdapter.Factory> callAdapterFactories;
final @Nullable Executor callbackExecutor;
final boolean validateEagerly;
...
}
複製程式碼
-
serviceMethodCache:serviceMethodCache是一個ConcurrentHashMap型別的Map集合,因此它支援併發操作且執行緒安全,其儲存物件是ServiceMethod。ServiceMethod我們可以理解是API方法的配置器以及代理中轉站,具體內容會在後文進行分析
-
callFactory:callFactory就是生產請求執行器的工廠(Call.Factory),Retrofit中預設的請求執行器工廠是OkHttpClient。如果我們沒有設定自定義的請求執行器工廠,那麼就會在構建Retrofit物件的過程中為我們建立一個OkHttpClient例項
-
baseUrl:API介面基地址的封裝物件,型別為HttpUrl
-
converterFactories:資料轉換器工廠(Converter.Factory)的集合,該工廠的產品資料轉換器(Converter)作用是對請求與響應資料進行序列化和反序列化
-
callAdapterFactories:請求介面卡工廠(CallAdapter.Factory)的集合,該工廠的產品請求介面卡(CallAdapter)用於改變執行請求的方式,例如我們可以通過新增支援RxJava的請求介面卡,將預設執行請求的方式改為RxJava呼叫鏈的方式
-
callbackExecutor:回撥執行器,主要作用是處理請求回撥,例如將回撥所線上程從子執行緒切換至主執行緒
-
validateEagerly:是否提前載入API方法配置的標誌位
2.2 構建Builder
Retrofit物件通過建造者模式進行構建,我們來看下Builder是如何初始化的
public static final class Builder {
private final Platform platform;
...
Builder(Platform platform) {
this.platform = platform;
}
public Builder() {
this(Platform.get());
}
...
}
複製程式碼
我們看見Builder的構造方法需要傳入Platform物件,它的主要作用是根據執行平臺為Retrofit提供預設的配置方法、工廠類或者工廠類的集合。Platform可以通過Platform.get()
方法獲取例項,我們來看下方法相關原始碼
class Platform {
private static final Platform PLATFORM = findPlatform();
static Platform get() {
return PLATFORM;
}
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
...
}
複製程式碼
呼叫Platform.get()
方法獲取的是靜態成員變數PLATFORM,PLATFORM則通過findPlatform()
方法生成
findPlatform()
主要通過是否能查詢到指定的類來判斷當前Retrofit執行的平臺,如果是執行在Android平臺,則返回Platform.Android的例項;如果是Java平臺,則返回Platform.Java8的例項;如果兩者皆非,則返回Platform自身的例項
現在我們是在Android平臺下執行Retrofit,所以繼續往下看Platform.Android的程式碼
// Platform
static class Android extends Platform {
@IgnoreJRERequirement // Guarded by API check.
@Override
boolean isDefaultMethod(Method method) {
if (Build.VERSION.SDK_INT < 24) {
return false;
}
return method.isDefault();
}
@Override
public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}
@Override
List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
@Nullable Executor callbackExecutor) {
if (callbackExecutor == null) throw new AssertionError();
ExecutorCallAdapterFactory executorFactory = new ExecutorCallAdapterFactory(callbackExecutor);
return Build.VERSION.SDK_INT >= 24
? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
: singletonList(executorFactory);
}
@Override
int defaultCallAdapterFactoriesSize() {
return Build.VERSION.SDK_INT >= 24 ? 2 : 1;
}
@Override
List<? extends Converter.Factory> defaultConverterFactories() {
return Build.VERSION.SDK_INT >= 24
? singletonList(OptionalConverterFactory.INSTANCE)
: Collections.<Converter.Factory>emptyList();
}
@Override
int defaultConverterFactoriesSize() {
return Build.VERSION.SDK_INT >= 24 ? 1 : 0;
}
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
}
複製程式碼
Platform.Android中主要定義了以下內容:
- 預設的回撥執行器MainThreadExecutor,負責將請求回撥的執行執行緒切換為主執行緒
- 預設的請求介面卡工廠集合,當Android版本大於等於24時,集合依次新增了兩個介面卡工廠CompletableFutureCallAdapterFactory與ExecutorCallAdapterFactory;當Android版本小於24時,則為只儲存了ExecutorCallAdapterFactory的單元素集合
- 預設的資料轉換器工廠集合,當Android版本大於等於24時,返回的是只儲存了OptionalConverterFactory的單元素集合;當系統版本小於24時,返回的是一個空集合
以上平臺預設的內容都會在Builder呼叫build()
完成構建時用到,具體的我們待會再細說,現在先來看下Builder主要提供了哪些方法
// Retrofit.Builder
public Builder client(OkHttpClient client) {
return callFactory(checkNotNull(client, "client == null"));
}
public Builder callFactory(okhttp3.Call.Factory factory) {
this.callFactory = checkNotNull(factory, "factory == null");
return this;
}
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
return baseUrl(HttpUrl.get(baseUrl));
}
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
callAdapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
public Builder callbackExecutor(Executor executor) {
this.callbackExecutor = checkNotNull(executor, "executor == null");
return this;
}
public Builder validateEagerly(boolean validateEagerly) {
this.validateEagerly = validateEagerly;
return this;
}
複製程式碼
簡單介紹一下這些方法的作用
client()
:新增自定義配置的OkHttpClientcallFactory()
:新增自定義的請求執行器工廠baseUrl()
:新增API介面的基地址addConverterFactory()
:新增自定義的資料轉換器addCallAdapterFactory()
:新增自定義的請求介面卡callbackExecutor()
:新增自定義的回撥執行器validateEagerly()
:設定是否預載入API方法的標誌位
完成Retrofit的構建最終需要呼叫Builder.build()
方法,來看下原始碼
// Retrofit.Builder
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters that consume all types.
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
複製程式碼
build()
方法完成了以下工作:
- 當使用者沒有新增自定義的CallFactory時預設使用OkHttpClient
- 當使用者沒有新增自定義的CallbackExecutor時預設使用
Platform.defaultCallbackExecutor()
,即MainThreadExecutor - 為callAdapterFactories建立一個保護性拷貝,然後將Platform裡定義的所有請求介面卡新增進來
- 為converterFactories建立了一個保護性拷貝,然後依次將內建的轉換器工廠BuiltInConverters、所有自定義的轉換器工廠、Platform裡定義的所有轉換器工廠新增進來
完成上述工作後將相關引數傳入Retrofit的構造方法中即完成構建Retrofit物件的工作。其中需要注意的是converterFactories和callAdapterFactories都變成了不可修改的List集合,這意味著後續我們不可以再更改這兩個集合中的內容了
至此Retrofit物件構建的過程就分析完了,下一章我們將分析API介面方法載入配置以及轉換為請求執行器的過程
2.3 本章小結
這一章我們分析了構建Retrofit物件的過程,過程中主要完成了以下工作:
- 建立內建的成員例項以及工廠集合,用來維持基礎的功能
- 通過構建方法儲存使用者自定義的內容,例如自定義的資料轉換器、請求介面卡、回撥執行器等等,用來執行擴充套件的功能
3. 載入API方法配置
本章我們將分析API方法載入配置的過程,以下是對應的示例程式碼
// 請求示例
ExpressService expressService = retrofit.create(ExpressService.class);
Call<ResponseBody> call = expressService.get("ems", "11111111");
複製程式碼
Retrofit物件構建完畢後,下一步是通過Retrofit.create(Class<T> service)
方法實現API介面,該方法的程式碼如下
// Retrofit
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable
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);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
複製程式碼
該方法主要是通過運用動態代理的方式為請求介面生成一個代理物件,我們對介面所有方法的呼叫都會轉發到代理物件中。現在我們一步步分析是如何完成整個動態代理的過程的
3.1 API方法的校驗和預載入
首先是呼叫Utils.validateServiceInterface(Class<T> service)
對介面類進行校驗
// Utils
static <T> void validateServiceInterface(Class<T> service) {
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
// Prevent API interfaces from extending other interfaces. This not only avoids a bug in
// Android (http://b.android.com/58753) but it forces composition of API declarations which is
// the recommended pattern.
if (service.getInterfaces().length > 0) {
throw new IllegalArgumentException("API interfaces must not extend other interfaces.");
}
}
複製程式碼
這裡規定我們傳入的引數必須是一個介面類,並且該介面不能繼承其他的介面
回到create方法中,接下來會通過標誌位validateEagerly來決定是否提前為API方法載入相應的配置
// Retrofit.create
if (validateEagerly) {
eagerlyValidateMethods(service);
}
複製程式碼
我們知道動態代理是在介面方法被呼叫時才會生效的,這類似於懶載入策略,Retrofit預設採用的就是這種方式,而我們可以通過Retrofit.Builder.validateEagerly()
方法將validateEagerly標誌設定為true,Retrofit就會呼叫eagerlyValidateMethods()
提前為介面方法載入配置
eagerlyValidateMethods()
原始碼如下
// Retrofit
private void eagerlyValidateMethods(Class<?> service) {
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
loadServiceMethod(method);
}
}
}
複製程式碼
具體邏輯為遍歷介面中的方法(method),然後判斷方法是否為預設方法或靜態方法(介面的預設方法和靜態方法都是Java8新增的特性),若不是則呼叫loadServiceMethod()
為介面方法載入相應的配置。loadServiceMethod()
方法的邏輯我們待會再細說,現在繼續分析Retrofit.create()
方法
經過預載入的邏輯後,下一步就是執行動態代理相關的邏輯
3.2 動態代理中的校驗
// Retrofit.create
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable
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);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
複製程式碼
首先依然是對介面方法的校驗,會判斷此次呼叫的方法是否為Object物件的方法(非介面方法),若是則正常呼叫,不進行任何代理操作。然後判斷該方法是否為預設方法,若是則呼叫Platform物件提供的配置方法invokeDefaultMethod()
並返回。invokeDefaultMethod()
在Android平臺下會丟擲UnsupportedOperationException異常,具體程式碼如下
// Platform
@Nullable
Object invokeDefaultMethod(Method method, Class<?> declaringClass, Object object,
@Nullable Object... args) throws Throwable {
throw new UnsupportedOperationException();
}
複製程式碼
執行完所有的校驗工序之後,最終依然是呼叫loadServiceMethod()
開始載入API方法的配置,我們來看下原始碼
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
複製程式碼
方法的邏輯非常簡單,功能可以分為兩部分來看
- 使用retrofit2.ServiceMethod處理介面方法
- 將處理後的結果快取至serviceMethodCache中,這樣下次再呼叫該介面方法時就無需重複處理了
繼續往下看ServiceMethod的程式碼
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract @Nullable T invoke(Object[] args);
}
複製程式碼
ServiceMethod是一個抽象類,它只有兩個方法,一個是invoke()
,這是一個抽象方法,具體的邏輯由ServiceMethod的子類實現,當呼叫介面方法時會被動態代理到這個方法中
另一個方法是parseAnnotations()
,這是一個靜態方法,它的功能如下
- 建立RequestFactory例項,具體方式為呼叫
RequestFactory.parseAnnotations()
方法 - 校驗介面方法的返回型別。Retrofit會判斷該返回型別是否屬於無法處理的型別(包含型別變數、萬用字元的返回型別以及void型別),若接收到這些返回型別時會直接丟擲異常
- 繼續呼叫
HttpServiceMethod.parseAnnotations()
完成介面方法後續的配置載入工作
這一部分最主要的關注點是RequestFactory。RequestFactory是請求體物件(okhttp3.Request)的工廠類,我們可以呼叫RequestFactory.create()
建立一個請求體的例項,下面我們就來詳細分析生成RequestFactory的過程
3.3 生成RequestFactory
以下是與RequestFactory.parseAnnotations()
方法相關的程式碼
final class RequestFactory {
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
...
RequestFactory(Builder builder) {
method = builder.method;
baseUrl = builder.retrofit.baseUrl;
httpMethod = builder.httpMethod;
relativeUrl = builder.relativeUrl;
headers = builder.headers;
contentType = builder.contentType;
hasBody = builder.hasBody;
isFormEncoded = builder.isFormEncoded;
isMultipart = builder.isMultipart;
parameterHandlers = builder.parameterHandlers;
isKotlinSuspendFunction = builder.isKotlinSuspendFunction;
}
...
static final class Builder {
...
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
RequestFactory build() {
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (isMultipart) {
throw methodError(method,
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError(method, "FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
if (relativeUrl == null && !gotUrl) {
throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError(method, "Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError(method, "Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError(method, "Multipart method must contain at least one @Part.");
}
return new RequestFactory(this);
}
...
}
}
複製程式碼
RequestFactory同樣通過建造者模式來構建例項,我們可以看到Builder.build()
方法中有許多狀態位的校驗邏輯,主要作用是當使用者在建立請求介面中錯誤地使用註解或配置引數時,可以丟擲相應的異常告知使用者。這部分的細節就不一一檢視了,感興趣的同學可以自行研究
這裡我們最主要關注的地方有兩點:一是遍歷介面方法的註解,然後通過Builder.parseMethodAnnotation()
方法解析註解的過程;二是遍歷方法的引數,然後通過Builder.parseParameter()
方法解析引數的過程
3.3.1 解析方法註解
首先來看第一點,parseMethodAnnotation()
的程式碼如下
// RequestFactory.Builder
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError(method, "@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
if (isFormEncoded) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}
複製程式碼
這裡有大量的判斷分支語句,我們可以按照註解的功能分成三類來看。第一類註解主要是描述HTTP方法的,例如@DELETE
、@GET
、@POST
等,解析方法為parseHttpMethodAndPath()
,來看下原始碼
// RequestFactory.Builder
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
if (this.httpMethod != null) {
throw methodError(method, "Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
}
this.httpMethod = httpMethod;
this.hasBody = hasBody;
if (value.isEmpty()) {
return;
}
// Get the relative URL path and existing query string, if present.
int question = value.indexOf('?');
if (question != -1 && question < value.length() - 1) {
// Ensure the query string does not have any named parameters.
String queryParams = value.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
if (queryParamMatcher.find()) {
throw methodError(method, "URL query string \"%s\" must not have replace block. "
+ "For dynamic query parameters use @Query.", queryParams);
}
}
this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value);
}
複製程式碼
這個方法有三個引數
- httpMethod:表示描述HTTP方法的註解的名稱
- value:註解包含的內容,例如
@GET("query?type=ems&postid=11111111")
中括號裡的內容實際上就是指服務端介面Url的資源路徑和查詢引數部分。這些內容可以通過呼叫這一系列註解的value()
方法獲取,這是在定義註解時設定的 - hasBody:該HTTP方法是否攜帶請求正文資料
知道了每個引數的含義,後面的程式碼就好理解了,流程是這樣的:首先會對一個介面方法是否同時設定了多個HTTP方法註解進行了校驗;然後校驗value是否為空,為空就不需要繼續解析了,因為這部分內容不是在方法註解裡設定就是在方法引數中設定,這裡沒有,那就直接交給後面解析引數時再去處理;若value不為空,則校驗查詢引數部分是否符合要求,符合要求則繼續解析查詢引數字串得到引數集合,不符合則丟擲異常,這些操作主要通過Java正則解析相關的Matcher類完成的,就不細說了
回到RequestFactory.Builder.parseMethodAnnotation()
,我們來看第二類註解。第二類註解只有@Headers
一個,負責設定請求頭資訊,解析的方法為Builder.parseHeaders()
,這部分內容比較簡單,就不展開了
第三類註解主要負責描述請求報文資料的型別,有@Multipart
和@FormUrlEncoded
兩種,但因為這兩種型別都需要配合方法引數的註解使用,所以這裡的處理過程只是簡單地校驗兩者不重複設定就行
實際上方法註解中還有第四類,那就是@Streaming
,這是用於描述響應正文資料的,因此Retrofit將它的處理過程放到了後面配置響應內容相關的部分再進行
至此解析方法註解的部分我們就分析完了,接下來看解析引數的部分
3.3.2 解析方法引數
解析方法引數對應的方法是Builder.parseParameter()
,程式碼如下
// RequestFactory.Builder
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
ParameterHandler<?> result = null;
if (annotations != null) {
for (Annotation annotation : annotations) {
ParameterHandler<?> annotationAction =
parseParameterAnnotation(p, parameterType, annotations, annotation);
if (annotationAction == null) {
continue;
}
if (result != null) {
throw parameterError(method, p,
"Multiple Retrofit annotations found, only one allowed.");
}
result = annotationAction;
}
}
...
return result;
}
複製程式碼
這裡的核心邏輯是通過parseParameterAnnotation()
方法解析引數註解生成ParameterHandler物件,同時規定一個引數只能設定一個Retrofit定義的註解
ParameterHandler是一個抽象類,它的子類封裝了引數的資料和資料的處理過程,並且和不同註解型別的引數一一對應,例如子類ParameterHandler.Query封裝了@Query
註解的引數,ParameterHandler.Field則封裝了@Field
註解的引數
作為引數的處理器,ParameterHandler可以在構建請求體物件時利用專屬的引數資料轉換器將資料轉換成請求需要的格式,這裡我們以ParameterHandler.Query為例進行分析
// ParameterHandler
static final class Query<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
private final boolean encoded;
Query(String name, Converter<T, String> valueConverter, boolean encoded) {
this.name = checkNotNull(name, "name == null");
this.valueConverter = valueConverter;
this.encoded = encoded;
}
@Override
void apply(RequestBuilder builder, @Nullable T value) throws IOException {
if (value == null) return; // Skip null values.
String queryValue = valueConverter.convert(value);
if (queryValue == null) return; // Skip converted but null values
builder.addQueryParam(name, queryValue, encoded);
}
}
複製程式碼
ParameterHandler.Query有三個成員屬性,其中name和encoded對應了@Query
定義的屬性
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Query {
/** The query parameter name. */
String value();
/**
* Specifies whether the parameter {@linkplain #value() name} and value are already URL encoded.
*/
boolean encoded() default false;
}
複製程式碼
而valueConverter指的就是引數資料轉換器,它可以通過呼叫convert()
方法進行資料轉換。引數處理的過程都放在Query.apply()
方法中,當構建請求體需要此引數的資料時,就會呼叫apply()
方法,然後通過傳入的RequestBuilder引用設定資料
ParameterHandler的分析就到這,其他引數處理器就不一一分析了,因為套路基本上都是一樣的,現在我們繼續分析parseParameterAnnotation()
方法是如何生成ParameterHandler的
// RequestFactory.Builder
@Nullable
private ParameterHandler<?> parseParameterAnnotation(
int p, Type type, Annotation[] annotations, Annotation annotation) {
if (annotation instanceof Url) {
...
return new ParameterHandler.RelativeUrl(method, p);
} else if (annotation instanceof Path) {
...
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Path<>(method, p, name, converter, path.encoded());
} else if (annotation instanceof Query) {
...
if (Iterable.class.isAssignableFrom(rawParameterType)) {
...
Converter<?, String> converter = retrofit.stringConverter(iterableType, annotations);
return new ParameterHandler.Query<>(name, converter, encoded).iterable();
} else if (rawParameterType.isArray()) {
...
Converter<?, String> converter = retrofit.stringConverter(arrayComponentType, annotations);
return new ParameterHandler.Query<>(name, converter, encoded).array();
} else {
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Query<>(name, converter, encoded);
}
} else if (annotation instanceof QueryName) {
...
if (Iterable.class.isAssignableFrom(rawParameterType)) {
...
Converter<?, String> converter = retrofit.stringConverter(iterableType, annotations);
return new ParameterHandler.QueryName<>(converter, encoded).iterable();
} else if (rawParameterType.isArray()) {
...
Converter<?, String> converter = retrofit.stringConverter(arrayComponentType, annotations);
return new ParameterHandler.QueryName<>(converter, encoded).array();
} else {
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.QueryName<>(converter, encoded);
}
} else if (annotation instanceof QueryMap) {
...
Converter<?, String> valueConverter = retrofit.stringConverter(valueType, annotations);
return new ParameterHandler.QueryMap<>(method, p,
valueConverter, ((QueryMap) annotation).encoded());
} else if (annotation instanceof Header) {
...
if (Iterable.class.isAssignableFrom(rawParameterType)) {
...
Converter<?, String> converter = retrofit.stringConverter(iterableType, annotations);
return new ParameterHandler.Header<>(name, converter).iterable();
} else if (rawParameterType.isArray()) {
...
Converter<?, String> converter = retrofit.stringConverter(arrayComponentType, annotations);
return new ParameterHandler.Header<>(name, converter).array();
} else {
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Header<>(name, converter);
}
} else if (annotation instanceof HeaderMap) {
...
Converter<?, String> valueConverter = retrofit.stringConverter(valueType, annotations);
return new ParameterHandler.HeaderMap<>(method, p, valueConverter);
} else if (annotation instanceof Field) {
...
if (Iterable.class.isAssignableFrom(rawParameterType)) {
...
Converter<?, String> converter = retrofit.stringConverter(iterableType, annotations);
return new ParameterHandler.Field<>(name, converter, encoded).iterable();
} else if (rawParameterType.isArray()) {
...
Converter<?, String> converter = retrofit.stringConverter(arrayComponentType, annotations);
return new ParameterHandler.Field<>(name, converter, encoded).array();
} else {
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Field<>(name, converter, encoded);
}
} else if (annotation instanceof FieldMap) {
...
Converter<?, String> valueConverter = retrofit.stringConverter(valueType, annotations);
return new ParameterHandler.FieldMap<>(method, p, valueConverter, ((FieldMap) annotation).encoded());
} else if (annotation instanceof Part) {
...
String partName = part.value();
if (partName.isEmpty()) {
if (Iterable.class.isAssignableFrom(rawParameterType)) {
...
return ParameterHandler.RawPart.INSTANCE.iterable();
} else if (rawParameterType.isArray()) {
...
return ParameterHandler.RawPart.INSTANCE.array();
} else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
return ParameterHandler.RawPart.INSTANCE;
} else {
...
}
} else {
Headers headers =
Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"",
"Content-Transfer-Encoding", part.encoding());
if (Iterable.class.isAssignableFrom(rawParameterType)) {
...
Converter<?, RequestBody> converter =
retrofit.requestBodyConverter(iterableType, annotations, methodAnnotations);
return new ParameterHandler.Part<>(method, p, headers, converter).iterable();
} else if (rawParameterType.isArray()) {
...
Converter<?, RequestBody> converter =
retrofit.requestBodyConverter(arrayComponentType, annotations, methodAnnotations);
return new ParameterHandler.Part<>(method, p, headers, converter).array();
} else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
...
} else {
Converter<?, RequestBody> converter =
retrofit.requestBodyConverter(type, annotations, methodAnnotations);
return new ParameterHandler.Part<>(method, p, headers, converter);
}
}
} else if (annotation instanceof PartMap) {
...
Converter<?, RequestBody> valueConverter =
retrofit.requestBodyConverter(valueType, annotations, methodAnnotations);
return new ParameterHandler.PartMap<>(method, p, valueConverter, partMap.encoding());
} else if (annotation instanceof Body) {
...
Converter<?, RequestBody> converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations);
return new ParameterHandler.Body<>(method, p, converter);
} else if (annotation instanceof Tag) {
...
return new ParameterHandler.Tag<>(tagType);
}
return null; // Not a Retrofit annotation.
}
複製程式碼
由於方法註解很多,parseParameterAnnotation()
程式碼非常的多,因此這裡我只把核心的內容展示給大家。parseParameterAnnotation()
解析每個引數的過程大致可以總結為三步
- 校驗引數是否設定正確,錯誤則丟擲異常告知使用者
- 生成特定的引數資料轉換器
- 返回對應的ParameterHandler例項
校驗過程就不細說了,感興趣的同學可以自行查閱原始碼,我們重點關注引數資料轉換器是如何生成的。從上面的原始碼可以看出生成轉換器的方式主要有兩種,一種是通過Retrofit.stringConverter()
方法生成,這類轉換器轉換出來的資料型別為String;另一種則通過Retrofit.requestBodyConverter()
方法生成,對應的轉換結果就是請求實體型別RequestBody
先來看Retrofit.stringConverter()
// Retrofit
public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) {
checkNotNull(type, "type == null");
checkNotNull(annotations, "annotations == null");
for (int i = 0, count = converterFactories.size(); i < count; i++) {
Converter<?, String> converter =
converterFactories.get(i).stringConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<T, String>) converter;
}
}
// Nothing matched. Resort to default converter which just calls toString().
//noinspection unchecked
return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
}
複製程式碼
這裡的程式碼主要完成了以下工作:按順序遍歷資料轉換器工廠集合converterFactories,到符合要求的轉換器工廠,即在實現介面方法Converter.Factory.stringConverter()
時不能返回null。這裡有一點細節需要注意,在遍歷過程中只要發現符合的轉換器工廠就會返回,因此轉換器工廠新增的順序決定了它們的優先順序,越早新增的優先順序越高,就可以優先被徵調。還記得我們在構建Retrofit物件時新增的轉換器嗎?現在我們重新來回顧一下加深記憶
// Retrofit.Builder.build()
converterFactories.add(new BuiltInConverters());// 新增Retrofit內建的資料轉換器,優先順序最高
converterFactories.addAll(this.converterFactories);// 按順序新增所有自定義的資料轉換器
converterFactories.addAll(platform.defaultConverterFactories());// 新增針對對應執行平臺設定的資料轉換器工廠
複製程式碼
當內建轉換器工廠BuiltInConverters以及集合中其他轉換器工廠都不符合要求時,則返回特定的轉換器BuiltInConverters.ToStringConverter,其程式碼如下
// BuiltInConverters
static final class ToStringConverter implements Converter<Object, String> {
static final ToStringConverter INSTANCE = new ToStringConverter();
@Override
public String convert(Object value) {
return value.toString();
}
}
複製程式碼
程式碼很簡單就不細說了,接下來是Retrofit.requestBodyConverter()
public <T> Converter<T, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {
return nextRequestBodyConverter(null, type, parameterAnnotations, methodAnnotations);
}
public <T> Converter<T, RequestBody> nextRequestBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] parameterAnnotations,
Annotation[] methodAnnotations) {
checkNotNull(type, "type == null");
checkNotNull(parameterAnnotations, "parameterAnnotations == null");
checkNotNull(methodAnnotations, "methodAnnotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter.Factory factory = converterFactories.get(i);
Converter<?, RequestBody> converter =
factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<T, RequestBody>) converter;
}
}
// 沒有找到符合要求的請求資料轉換器,丟擲IllegalArgumentException異常
...
}
複製程式碼
和之前的處理邏輯差別不大,不過這次呼叫的方法為Converter.Factory.requestBodyConverter()
,而且我們發現BuiltInConverters實現了該介面方法,程式碼如下
// BuiltInConverters
@Override
public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if (RequestBody.class.isAssignableFrom(Utils.getRawType(type))) {
return RequestBodyConverter.INSTANCE;
}
return null;
}
複製程式碼
這裡返回了針對RequestBody型別引數的轉換器BuiltInConverters.RequestBodyConverter
// BuiltInConverters
static final class RequestBodyConverter implements Converter<RequestBody, RequestBody> {
static final RequestBodyConverter INSTANCE = new RequestBodyConverter();
@Override
public RequestBody convert(RequestBody value) {
return value;
}
}
複製程式碼
可以看見RequestBodyConverter並沒有對引數資料進行任何處理,因此該轉換器最主要的作用是攔截型別為RequestBody的引數
至此我們就完了解析方法引數的全部工作,也成功生成了請求體物件工廠RequestFactory。
3.4 生成CallAdapter
HttpServiceMethod是ServiceMethod的子類,它的程式碼有點多,我們直接挑重點看。首先是parseAnnotations()
方法
// HttpServiceMethod
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method);
Type responseType = callAdapter.responseType();
...
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
return new HttpServiceMethod<>(requestFactory, callFactory, callAdapter, responseConverter);
}
複製程式碼
這裡主要做了下面這些工作
- 通過
createCallAdapter()
方法生成請求介面卡callAdapter - 通過
callAdapter.responseType()
方法獲取響應資料的型別responseType - 通過
createResponseConverter()
方法生成響應體(Response)的資料轉換器responseConverter - 通過上述例項構建HttpServiceMethod物件
首先我們分析生成請求介面卡CallAdapter的過程,以下是createCallAdapter()
方法的程式碼
// HttpServiceMethod
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method) {
Type returnType = method.getGenericReturnType();
Annotation[] annotations = method.getAnnotations();
try {
//noinspection unchecked
return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create call adapter for %s", returnType);
}
}
複製程式碼
// Retrofit
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
...
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
// 當所有介面卡都不符合要求時丟擲IllegalArgumentException異常
...
}
複製程式碼
可以發現套路基本上是一樣的,具體方式為遍歷callAdapterFactories找到符合要求的請求介面卡後返回,驗證的方法為CallAdapter.Factory.get()
。通過上一章的分析我們知道,除了使用者新增的自定義的請求介面卡工廠以外,Retrofit還內建了兩種預設的介面卡工廠:CompletableFutureCallAdapterFactory(系統版本大於等於24時生效)和ExecutorCallAdapterFactory。這裡我們只以ExecutorCallAdapterFactory為例進行分析,來看下原始碼
final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
final Executor callbackExecutor;
ExecutorCallAdapterFactory(Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
@Override
public @Nullable
CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Object, Call<?>>() {
@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
return new ExecutorCallbackCall<>(callbackExecutor, call);
}
};
}
...
}
複製程式碼
通過上述程式碼我們知道,get()
方法指定ExecutorCallAdapterFactory的適配目標是返回型別為retrofit2.Call的API方法,其最終返回值是一個新的CallAdapter例項。此外,該CallAdapter例項的adapt()
方法生成了retrofit2.Call的子類ExecutorCallbackCall,這實際上是請求執行器的裝飾器,具體內容我們在後面用到時再細說,現在繼續下一步
3.5 生成ResponseConverter
接下來是呼叫HttpServiceMethod.createResponseConverter()
方法生成響應資料轉換器(ResponseConverter),我們來看下相關程式碼
// HttpServiceMethod
private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(
Retrofit retrofit, Method method, Type responseType) {
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.responseBodyConverter(responseType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create converter for %s", responseType);
}
}
複製程式碼
// Retrofit
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
return nextResponseBodyConverter(null, type, annotations);
}
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
checkNotNull(type, "type == null");
checkNotNull(annotations, "annotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
// 無法找到符合要求的ResponseBodyConverter時丟擲IllegalArgumentException異常
...
}
複製程式碼
同樣這裡也是通過遍歷轉換器工廠集合converterFactories找到符合要求的轉換器工廠,驗證方法為Converter.Factory.responseBodyConverter()
。之前講過轉換器工廠優先順序最高的是內建的BuiltInConverters,來看下它的responseBodyConverter()
方法
final class BuiltInConverters extends Converter.Factory {
...
@Override
public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == ResponseBody.class) {
return Utils.isAnnotationPresent(annotations, Streaming.class)
? StreamingResponseBodyConverter.INSTANCE
: BufferingResponseBodyConverter.INSTANCE;
}
if (type == Void.class) {
return VoidResponseBodyConverter.INSTANCE;
}
...
return null;
}
...
}
複製程式碼
這裡指定BuiltInConverters只能處理型別為okhttp3.ResponseBody以及void的響應體資料。當型別為ResponseBody時,還會判斷介面方法是否含有@Streaming
註解,然後提供不同的轉換器例項。下面我們就看下這三個資料轉換器有什麼區別吧
// BuiltInConverters
static final class StreamingResponseBodyConverter implements Converter<ResponseBody, ResponseBody> {
static final StreamingResponseBodyConverter INSTANCE = new StreamingResponseBodyConverter();
@Override
public ResponseBody convert(ResponseBody value) {
return value;
}
}
static final class BufferingResponseBodyConverter implements Converter<ResponseBody, ResponseBody> {
static final BufferingResponseBodyConverter INSTANCE = new BufferingResponseBodyConverter();
@Override
public ResponseBody convert(ResponseBody value) throws IOException {
try {
// Buffer the entire body to avoid future I/O.
return Utils.buffer(value);
} finally {
value.close();
}
}
}
static final class VoidResponseBodyConverter implements Converter<ResponseBody, Void> {
static final VoidResponseBodyConverter INSTANCE = new VoidResponseBodyConverter();
@Override
public Void convert(ResponseBody value) {
value.close();
return null;
}
}
複製程式碼
可以看到這三個轉換器都實現了Converter介面的convert()
方法,這個方法會在後續處理響應資料時呼叫,現在我們先看下這三個轉換器的convert()
方法是如何實現的
- StreamingResponseBodyConverter:會將資料流的連線返回給使用者,那為什麼返回的是資料流的連線而不是資料呢?原因是RessponseBody持有的資料可能會很大,OkHttp並不會將資料直接儲存到記憶體中,實際儲存的是資料流的連線,當使用者需要時再通過連線從服務端獲取資料。因此
@Streaming
註解適合在執行大檔案下載任務的時候使用 - BufferingResponseBodyConverter:先將響應體資料儲存至記憶體緩衝區中,再返回給使用者。這適合在響應體資料較小的場景下使用,也是預設的資料處理方式
- VoidResponseBodyConverter:當設定返回的響應體資料型別為void時,說明使用者無意處理響應的資料,那麼直接關閉釋放資源即可
現在回到一開始的請求示例中,我們定義的介面方法是這樣的
public interface ExpressService {
@GET("query?type=ems&postid=11111111")
Call<ResponseBody> get();
}
複製程式碼
這裡設定的響應資料型別是ResponseBody,即BuiltInConverters可以處理這個API方法,因此遍歷查詢轉換器工廠的過程到了BuiltInConverters處就被攔截了下來,且由於該介面方法並沒有新增@Streaming
註解,所以最終HttpServiceMethod.createResponseConverter()
構建的響應資料轉換器就是BufferingResponseBodyConverter
3.6 完成API方法的動態代理
完成所有前置工作後,我們回到Retrofit.create()
方法,由之前的分析可知API方法會被動態代理到
loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
複製程式碼
而loadServiceMethod()
最終返回的是HttpServiceMethod的例項,因此我們來看下HttpServiceMethod.invoke()
方法的程式碼
// HttpServiceMethod
@Override
ReturnT invoke(Object[] args) {
return callAdapter.adapt(new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
複製程式碼
adapt()
方法的傳參是OkHttpCall的例項,這是Retrofit層的請求執行器,其內部持有真實的請求執行器okhttp3.Call。OkHttpCall作為代理類主要負責Retrofit層與OkHttp層之間互動資料的轉換
此外,從之前的講解中我們知道adapt()
方法的返回結果是ExecutorCallbackCall的例項,因此動態代理API方法最終得到的就是這個例項,其作為OkHttpCall的裝飾器,主要負責協同回撥執行器為OkHttpCall動態增強回撥方面的功能
至此API方法的動態代理和載入配置的過程就分析完了,下一章我們將分析Retrofit是如何發起請求以及處理響應資料的
3.7 本章小結
這一章分析了載入API方法配置的過程,原理是利用動態代理機制,主要完成了以下工作:
- 通過解析方法註解和引數註解生成了【請求物件工廠】以及【引數資料轉換器】
- 通過解析方法返回型別和響應資料型別生成了【請求介面卡工廠】、【響應資料轉換器工廠】、【請求執行器(裝飾器)】
4. 請求與響應
本章我們將分析請求與響應的過程,以下是對應的示例程式碼
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
複製程式碼
4.1 處理回撥
由上一章我們知道,這裡的call實際上是裝飾器ExecutorCallbackCall,我們來看下ExecutorCallbackCall.enqueue()
方法的程式碼
// ExecutorCallAdapterFactory
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override
public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");
delegate.enqueue(new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
@Override
public void run() {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
}
});
}
@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
@Override
public void run() {
callback.onFailure(ExecutorCallbackCall.this, t);
}
});
}
});
}
...
}
複製程式碼
ExecutorCallbackCall有兩個成員變數,callbackExecutor與delegate,通過之前的分析我們知道它們分別對應了MainThreadExecutor和OkHttpCall。那麼先來分析MainThreadExecutor.execute()
方法,該方法在OkHttpCall的請求回撥中被呼叫
// Platform.Android
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
複製程式碼
這裡的功能非常好理解,綜合之前的程式碼來看就是將OkHttpCall的請求回撥結果傳送至主執行緒。再來看OkHttpCall.enqueue()
方法
// OkHttpCall
@Override
public void enqueue(final Callback<T> callback) {
okhttp3.Call call;
call = rawCall = createRawCall();
...
call.enqueue(new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response = parseResponse(rawResponse);
callback.onResponse(OkHttpCall.this, response);
...
}
@Override
public void onFailure(okhttp3.Call call, IOException e) {
callback.onFailure(OkHttpCall.this, e);
}
...
});
}
複製程式碼
這裡我把核心的程式碼摘抄出來進行分析,首先是通過createRawCall()
方法獲取真實的請求執行器
4.2 生成okhttp3.Call
// OkHttpCall
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
複製程式碼
這裡有兩個需要關注的點,一是呼叫callFactory.newCall()
方法獲取真實的請求執行器,callFactory實際上就是OkHttpClient物件的引用,這和我們在OkHttp中生成請求執行器的方法一致;二是通過requestFactory.create()
方法獲取請求體okhttp3.Request的例項,其中args是通過動態代理API方法拿到的傳參。現在我們開始分析生成Request物件的過程
// RequestFactory
okhttp3.Request create(Object[] args) throws IOException {
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
int argumentCount = args.length;
...
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl,
headers, contentType, hasBody, isFormEncoded, isMultipart);
List<Object> argumentList = new ArrayList<>(argumentCount);
for (int p = 0; p < argumentCount; p++) {
argumentList.add(args[p]);
handlers[p].apply(requestBuilder, args[p]);
}
return requestBuilder.get()
.tag(Invocation.class, new Invocation(method, argumentList))
.build();
}
複製程式碼
handlers是引數封裝類ParameterHandler的集合,通過apply()
方法可以處理引數資料,轉換成RequestBuilder需要的格式,然後通過RequestBuilder的引用設定資料,這些資料將在生成Request時用到。先來看下RequestBuilder的構造方法
// RequestBuilder
RequestBuilder(String method, HttpUrl baseUrl,
@Nullable String relativeUrl, @Nullable Headers headers, @Nullable MediaType contentType,
boolean hasBody, boolean isFormEncoded, boolean isMultipart) {
this.method = method;
this.baseUrl = baseUrl;
this.relativeUrl = relativeUrl;
this.requestBuilder = new Request.Builder();
this.contentType = contentType;
this.hasBody = hasBody;
if (headers != null) {
requestBuilder.headers(headers);
}
if (isFormEncoded) {
// Will be set to 'body' in 'build'.
formBuilder = new FormBody.Builder();
} else if (isMultipart) {
// Will be set to 'body' in 'build'.
multipartBuilder = new MultipartBody.Builder();
multipartBuilder.setType(MultipartBody.FORM);
}
}
複製程式碼
可以看到我們之前在解析註解、載入API方法配置的過程中生成的屬性在這都得到了應用。再來看ParameterHandler.apply()
的過程,我們以請求示例中定義的API方法為例,方法引數中使用了@Query
註解,對應的引數封裝類是Query
// ParameterHandler
static final class Query<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
private final boolean encoded;
Query(String name, Converter<T, String> valueConverter, boolean encoded) {
this.name = checkNotNull(name, "name == null");
this.valueConverter = valueConverter;
this.encoded = encoded;
}
@Override
void apply(RequestBuilder builder, @Nullable T value) throws IOException {
if (value == null) return; // Skip null values.
String queryValue = valueConverter.convert(value);
if (queryValue == null) return; // Skip converted but null values
builder.addQueryParam(name, queryValue, encoded);
}
}
複製程式碼
引數資料轉換器在這裡開始發揮作用了,可以通過呼叫Converter.convert()
方法將引數資料轉換成RequestBuilder實際需要的資料,通過3.3.2一節的分析我們知道處理String型別資料的轉換器是ToStringConverter,因此這裡valueConverter就是ToStringConverter例項的引用,重新回顧一下ToStringConverter.convert()
方法
// BuiltInConverters.ToStringConverter
@Override
public String convert(Object value) {
return value.toString();
}
複製程式碼
資料轉換完畢後,繼續呼叫RequestBuilder.addQueryParam()
方法設定Query引數的資料
// RequestBuilder
void addQueryParam(String name, @Nullable String value, boolean encoded) {
if (relativeUrl != null) {
// Do a one-time combination of the built relative URL and the base URL.
urlBuilder = baseUrl.newBuilder(relativeUrl);
...
relativeUrl = null;
}
if (encoded) {
//noinspection ConstantConditions Checked to be non-null by above 'if' block.
urlBuilder.addEncodedQueryParameter(name, value);
} else {
//noinspection ConstantConditions Checked to be non-null by above 'if' block.
urlBuilder.addQueryParameter(name, value);
}
}
複製程式碼
到這裡就都是OkHttp構建Request的方法了,不再展開細說。回到RequestFactory.create()
,繼續往下看Request的構建過程
// RequestFactory.create
return requestBuilder.get()
.tag(Invocation.class, new Invocation(method, argumentList))
.build();
複製程式碼
首先呼叫了RequestBuilder.get()
方法,看下原始碼
Request.Builder get() {
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);
...
}
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);
}
複製程式碼
同樣這裡也都是OkHttp層的呼叫,成員變數requestBuilder對應的型別就是okhttp3.Request.Builder,呼叫build()
方法就可以生成Request物件了
至此OkHttpCall.createRawCall()
成功建立了一個okhttp3.Call的例項,現在我們回到OkHttpCall.enqueue()
方法繼續往下看
4.3 處理okhttp3.Response
得到okhttp3.Call例項後,下一步就是呼叫Call.enqueue()
發起非同步請求,並在回撥中得到響應體物件okhttp3.Response,由於這是OkHttp層的,因此還需要解析轉換成Retrofit層的響應體物件,解析轉換的方法是OkHttpCall.parseResponse()
// OkHttpCall
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
if (code < 200 || code >= 300) {
try {
// Buffer the entire body to avoid future I/O.
ResponseBody bufferedBody = Utils.buffer(rawBody);
return Response.error(bufferedBody, rawResponse);
} finally {
rawBody.close();
}
}
if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
// If the underlying source threw an exception, propagate that rather than indicating it was
// a runtime exception.
catchingBody.throwIfCaught();
throw e;
}
}
複製程式碼
這裡的重點是呼叫ResponseConverter.convert()
轉換響應資料,之前分析過的內建和自定義的響應資料轉換器在這發揮了作用,ResponseBody成功轉換成使用者需要的型別
至此我們完成了okhttp3.Response的處理過程,下一步再將生成的retrofit2.Response物件通過回撥方法返回給使用者,請求與響應的過程以及整個Retrofit工作流程的原始碼分析就完成了
4.4 本章小結
這一章分析了請求與響應的過程,主要完成了以下工作:
- 通過上一章生成的請求執行器代理類發起非同步請求
- 通過RequestFactory構建請求體Request,其中包括使用引數資料轉換器處理請求引數
- 通過CallFactory生成真實請求執行器okhttp3.Call,並呼叫
enqueue()
方法發起真實的非同步請求 - 處理OkHttp層響應物件okhttp3.Response,生成Retrofit層響應物件retrofit2.Response,其中包括使用響應資料轉換器將響應資料轉換成使用者定義的形式
- 在請求執行器代理類中使用回撥執行器將響應結果返還給使用者
三、總結
最後以一張Retrofit的工作流程圖總結之前的分析(ps:為了和流程進行區分,這裡我將一些重要成員抽象成了資料來源)