Retrofit
的使用從很早之前就已經開始了, 但是一直沒有深入研究為什么使用Retrofit
只要定義一個接口, 同時在接口的方法上和方法的參數上加上一些注解就可以完成Http
請求了, 也沒有研究請求參數和請求結果是如何進行封裝的, 所以使用Retrofit
一直是處于一知半解的狀態, 不知道其內部的原理, 因此花了一點時間看了Retrofit
的源碼, 對Retrofit
的整個請求流程有了一定的理解.
Retrofit核心源碼解讀
1. 創建Retrofit
對象
創建Retrofit
對象的時候使用的是Builder
模式, 可以在創建Retrofit
對象的時候設置Retrofit
的baseURL
, 添加自己的converterFactory
例子:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(GITHUB_API)
.addConverterFactory(GsonConverterFactory.create())
.build();
注意點:
-
在設置
baseUrl
的時候和Retrofit 1.x
版本不同的是url
必須以'/'
結尾源碼:
// Retrofit.java#baseUrl method public Builder baseUrl(HttpUrl baseUrl) { .... // pathSegments() 返回的是url的查找路徑List(查找路徑是以'/'分割的字符串, 所以只要split("/")就可以了) List<String> pathSegments = baseUrl.pathSegments(); // 如果List的最后一個元素不是(""), 則說明查找路徑不是以'/'結尾的字符串 if (!"".equals(pathSegments.get(pathSegments.size() - 1))) { throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl); } .... }
-
如果不添加自己的轉換器, 則返回值只能封裝成
ResponseBody
類型或者沒有返回值源碼:
// Retrofit.java#Builder Builder(Platform platform) { this.platform = platform; // 添加默認的BuiltInConverter converterFactories.add(new BuiltInConverters()); } // BuiltInConverters.java#responseBodyConverter method public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { // 如果返回值是ResponseBody類型 if (type == ResponseBody.class) { return Utils.isAnnotationPresent(annotations, Streaming.class) ? StreamingResponseBodyConverter.INSTANCE : BufferingResponseBodyConverter.INSTANCE; } // 如果返回值類型是Void類型 if (type == Void.class) { return VoidResponseBodyConverter.INSTANCE; } // 如果返回值是其他的類型 return null; }
-
創建
Retrofit
對象時使用了Builder
模式, 而使用Builder
模式的好處就是創建的對象是不可變對象(因為屬性都是private
的且沒有setter
方法), 這樣在使用的使用就不用擔心對象中屬性是否被修改或者不小心修改了對象中的屬性了. 如果存在有些請求不能和其他請求共用同一個Retrofit
對象, 但是大部分的屬性都一樣只有少部分的屬性不一致, 那么可以用一個Retrofit
對象為模版來創建一個新的Retrofit
對象源碼:
// Retrofit.java#Builder Builder(Retrofit retrofit) { // 以retrofit對象為模版生成一個新的Retrofit對象 this.platform = Platform.get(); callFactory = retrofit.callFactory; baseUrl = retrofit.baseUrl; converterFactories.addAll(retrofit.converterFactories); adapterFactories.addAll(retrofit.adapterFactories); // Remove the default, platform-aware call adapter added by build(). adapterFactories.remove(adapterFactories.size() - 1); callbackExecutor = retrofit.callbackExecutor; validateEagerly = retrofit.validateEagerly; }
-
這里要額外提一下. 因為
Retrofit
是支持多個平臺(jvm, android, ios)
, 那么Retrofit
是如何分辨當前引用的是什么平臺的呢, 實際上Retrofit
使用的方法就是查看當前的classpath
中是否存在相應平臺特有的class
來判斷當前是在哪個平臺. 這個和Spring的@ConditionalOnClass(ClassName.class)
來決定是否初始化注解所修飾的bean有異曲同工的味道關鍵代碼:
// Platform.java#findPlatform method // 查找當前所在的平臺 private static Platform findPlatform() { try { // 如果存在"android.os.Build"則是android平臺 Class.forName("android.os.Build"); if (Build.VERSION.SDK_INT != 0) { return new Android(); } } catch (ClassNotFoundException ignored) { } try { // 如果存在"java.util.Optional"則是java8平臺 Class.forName("java.util.Optional"); return new Java8(); } catch (ClassNotFoundException ignored) { } try { // 如果存在"org.robovm.apple.foundation.NSObject"則是ios平臺 Class.forName("org.robovm.apple.foundation.NSObject"); return new IOS(); } catch (ClassNotFoundException ignored) { } // 默認平臺 return new Platform(); }
2. 創建發送請求的接口
包含發送請求方法必須是一個接口, 同時這個接口是不能繼承其他的接口的
例子:
public interface GitApi {
@GET("/users/{user}")
Call<GitModel> getFeed(@Path("user") String user);
}
注意點:
-
包含請求方法必須是一個接口同時不能繼承其他的接口的
源碼:
static <T> void validateServiceInterface(Class<T> service) { // class必須是一個接口 if (!service.isInterface()) { throw new IllegalArgumentException("API declarations must be interfaces."); } // 接口不能繼承其他的接口 if (service.getInterfaces().length > 0) { throw new IllegalArgumentException("API interfaces must not extend other interfaces."); } }
-
為什么只要定一個接口和一個請求方法,
Retrofit
就可以發送Http
請求了呢? 因為Retrofit
使用了代理, 其實在創建接口的對象的時候返回的是一個代理對象, 這個代理其實也是Retrofit
的核心.源碼:
// 創建接口的實現對象, 返回的是GitApi接口的代理對象 GitApi git = retrofit.create(GitApi.class); // 關鍵代碼: create方法 public <T> T create(final Class<T> service) { // 校驗service是否是一個沒有繼承其他接口的接口 Utils.validateServiceInterface(service); // 如果validateEagerly==true, 則先將接口中的方法全部都放到serviceMethodCache中, // 這樣在之后的調用過程中就不需要走loadServiceMethod的流程, 而是直接走的緩存, 這樣子可以加快訪問的速度, // 但是這樣子也存在一定的壞處, 因為會造成內存占用量變大而且可能有些方法不會被調用都被放到緩存中了 if (validateEagerly) { eagerlyValidateMethods(service); } // 代理模式, // 使用代理攔截接口中所有的方法, 從而解析方法上的注解, 方法參數上的注解, 返回值等, 這也是retrofit可以實現面向接口和注解編程的關鍵 // service: 真實對象, proxy: 代理對象, method: 調用的方法, args: 方法參數 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, Object... args) throws Throwable { // 如果調用的是Object對象中方法則直接返回調用結果 if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } // 如果調用的是接口中的默認方法則直接返回對應平臺對調用默認方法的處理 if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } // 獲取或者創建ServiceMethod對象 ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); // 創建OkHttpCall對象用于發送請求和解析結果 OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
上面很明顯可以看到
Retrofit
實際上使用了jdk
的Proxy
動態代理技術使得可以定義一個接口加上注解就完成http
請求. 由于使用了Proxy
所以必須定義成一個接口而不是一個抽象類的原因也就顯而易見了, 因為jdk
的proxy
只能生成接口的代理對象. 如果這里使用的是cglib
庫的話那也可以定義成抽象類
3. 發送Http
請求
要發送http
請求就離不開ServiceMethod
類和OkHttpCall
類, 他們是發送Http
請求的核心類. 首先會從Map
中獲取和當前請求方法相關聯的ServiceMethod
, 如果找到了(說明之前這個方法已經被調用過了)就直接使用找到的ServiceMethod
, 如果沒有找到, 則創建一個新的ServiceMethod
并且和當前的請求方法相關聯put
到Map
中. 最后調用OkHttpCall.execute
發送請求.
源碼:
// 查找或者判斷和請求的方法相關聯的ServiceMethod
ServiceMethod<?, ?> loadServiceMethod(Method method) {
// 緩存技術
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
// 使用了兩次判斷(兩段鎖)
synchronized (serviceMethodCache) {
// 這一次從緩存中取是有必要的且非常重要, 如果沒有這一次則有可能下面的代碼會被重復執行, 同一個key也可能被重復賦值
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
- 創建
ServiceMethod
也使用了Build
模式, 在build方法中對請求的方法上的注解, 方法參數, 返回值進行了解析
源碼:
public ServiceMethod build() {
// 創建callAdapter對象(默認使用的是DefaultCallAdapterFactory), callAdapter對象用于.
// callAdapter對象的主要作用就是返回一個CallAdapter接口的實例用于調用底層okhttp的方法發送請求和解析返回值(DefaultCallAdapterFactory中使用OkHttpCall.execute來發送請求和解析結果).
// 默認的請求方法返回值都是Call<T>, 可以繼承CallAdapter.Factory來實現自定義的返回值類型
callAdapter = createCallAdapter();
...
other code
...
// 創建返回值解析器(默認只有BuiltInConverters), 可以在創建Retrofit的時候使用addConverterFactory加入其他的converters
responseConverter = createResponseConverter();
// 解析方法上的注解
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
...
other code
...
// 解析參數
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
...
一些錯誤情況的處理
...
return new ServiceMethod<>(this);
}
- 在
Retrofit.java#create
的proxy
中調用請求方法最后返回的是return serviceMethod.callAdapter.adapt(okHttpCall);
, 默認情況下返回的就是OkHttpCall
, 所以最后調用的就是OkHttpCall.execute()
方法發送請求和解析返回結果
關鍵代碼:
@Override public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
...
call = rawCall;
if (call == null) {
try {
// 創建Okhttp的call方法
call = rawCall = createRawCall();
} catch (IOException | RuntimeException e) {
...
}
}
}
....
// call.execute: 調用okhttp發送真正的請求
// parseResponse: 對返回的結果進行解析, 方法就是調用創建retrofit時加入的所有converterFactory, 知道找到一個converter可以處理返回值
return parseResponse(call.execute());
}
總結
至此, Retrofit
發送請求的整個流程就已經講解完畢了, 實際上整個流程中關鍵點就只有幾個, 比如:
Retrofit.java#create
方法, 這個方法的作用就是使用Proxy創建用戶定義的接口的實現, 從而實現使用時只要創建接口, 創建方法, 添加相關注釋三個步驟就可以完成一個http請求的關鍵ServiceMethod.java#build
方法, 這個方法的作用是對請求方法上的注解, 方法的參數, 方法的返回值進行解析, 同時創建callAdapter對象, 這是Retrofit留下的一個'插口', 只要我們實現CallAdapter.Factory
就可以處理自定義的方法返回值類型, 方法不一定要返回Call<T>類型OkHttpCall.java#execute
方法, 這個方法的作用是調用底層的okhttp
發送真正的http
請求 , 然后對返回結果進行解析. 解析返回結果是的方法就是遍歷Retrofit
中的converterFactories
, 知道找到一個可以解析返回結果的對象. 這事Retrofit
留下的另一個'插口', 只要我們實現Converter.Factory
就可以處理自定義的返回結果, 而不一定只能是Call<ResponseBody>
類型
另外, Retrofit
提供了
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
這寫轉換器, 應該可以滿足日常開發需要了.
從上面可以看到, Retrofit
可以允許我們自定義返回類型, 返回結果同時屏蔽掉了底層的OkHttp
的復雜性, 使得我們只要定義一個接口就完成Http
請求的發送, 這對于使用者來說是非常友好的. 易于上手和高度的定制性是現如今Retrofit
如此流行的關鍵