Retrofit源碼分析
Retrofit簡介
是Square公司基於Okhttp封裝的一款網絡開源框架,簡化了對網絡的請求。
以下基於Retrofit2.1.0版本的分析,本文仿寫 碼老闆的博客https://zhuanlan.zhihu.com/p/35121326關於“Retrofit原理解析最簡潔的思路”。。
Retrofit使用
定義接口請求參數
public interface ApiService {
@GET("app/{volumeId}/updateDefaultVolume")
Call<ResponseBody> updateDefaultVolume(@Path("volumeId") String volumeId);
}
通過以上方式簡化了網絡請求,代碼也更直觀
實例化Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("xxx")
.build();
以上是最簡單的Retrofit初始化方式,採用了鏈式調用的設計
獲取接口實現類
ApiService apiService = retrofit.create(ApiService.class);
Call<ResponseBody> call = apiService.updateDefaultVolume("oo121o");
通過retrofit的create方法,獲取到接口的實現類,接下來再調用自己定義的方法,進行網絡請求。但是我們只定義了一個接口,並沒有方法體,請求方式和參數都還是註解,那麼方法體它是怎麼生成的,接下來源碼會具體進行講解
進行網絡請求
//同步網絡請求
Request request = call.request();
//異步網絡請求
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
以上就是retrofit的最簡單的網絡請求方法,由於是基於Okhttp的封裝,其網絡請求變得簡潔。接下來具體分析起原理
Retrofit原理分析
retrofit初始化
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.build();
首先看Builder()源碼
public Builder() {
this(Platform.get());
}
this表示其調用自己的構造方法,如下
Builder(Platform platform) {
this.platform = platform; //====1
// 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()); //====2
}
1處就是簡單的賦值,2處是轉換器工廠添加各種類型轉換器(暫時是這樣理解),
點開上面的Platform.get()方法,
private static final Platform PLATFORM = findPlatform();//2
static Platform get() {
return PLATFORM; //1
}
private static Platform findPlatform() {//3
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) {
}
try {
Class.forName("org.robovm.apple.foundation.NSObject");
return new IOS();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
從上述代碼可以發現get()方法是典型的餓漢式單例,這樣寫的好處是簡單、線程安全、效率高、不會生成多個實例。1和2構成了典型的餓漢式單例。3就是判斷系統,根據系統然後實例化不同的平臺對象。這裏跟我們相關的只有實例化Android這塊,但是,在這不進行深究,略過。。。
baseUrl(“https://api.github.com”)源碼分析
點開baseUrl(“https://api.github.com”)方法,查看其實現
/**
* Set the API base URL.
*
* @see #baseUrl(HttpUrl)
*/
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null"); //1
HttpUrl httpUrl = HttpUrl.parse(baseUrl); //2
if (httpUrl == null) {
throw new IllegalArgumentException("Illegal URL: " + baseUrl);
}
return baseUrl(httpUrl); //3
}
1處是對baseUrl進行判空處理,這個沒啥可講的。
2處是對baseUrl進行解析,可以進去查看parse方法,裏面基本都是一些對路徑的規範化判定
3處傳入了一個HttpUrl對象,查看其實現,如下
public Builder baseUrl(HttpUrl baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
List<String> pathSegments = baseUrl.pathSegments();//獲取到路徑段列表
if (!"".equals(pathSegments.get(pathSegments.size() - 1))) { //路徑結尾必須以“/”結尾
throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
}
this.baseUrl = baseUrl;
return this;
}
這個方法主要是給baseUrl賦值,同時規範baseUrl的路徑展示形式,結尾必須以“/”結束。否則,會報錯。。
總結,由以上可知,baseUrl方法主要是對路徑進行了規範的判定,同時進行賦值。
build()方法源碼解析
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> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}
這個方法主要是創建Retrofit的一個實例。並初始化一些參數對象。callFactory是okhttp的工廠實例,用於網絡請求的,converterFactories是轉換器工廠集合,adapterFactories是回調適配器工廠集合,callbackExecuto應該是回調接口實例,在這個方法中對這些對象進行了實例化,並最終返回Retrofit的實例。。。
接口實現類源碼分析
ApiService apiService = retrofit.create(ApiService.class);
Call<ResponseBody> call = apiService.updateDefaultVolume("oo121o");
通過調用Retrofit的create方法獲取到接口的實例,那麼爲什麼調用create方法會生成接口實例??點擊打開create方法源碼
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service); //驗證傳入的class是否標準的接口類
if (validateEagerly) {
eagerlyValidateMethods(service); //把接口類裏面的定義方法加入LinkedHashMap集合中
}
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 {
// 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 serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
最後一段代碼Proxy.newProxyInstance…採用了 動態代理 的設計模式,而且這個方法封裝得非常好,我麼只需要傳入相應的接口類,就能獲取到其實例。遵循了 迪米特原則(最少知道原則)。
代理也稱“委託”,分爲靜態代理和動態代理,代理模式也是常用的設計模式之一,具有方法增強、高擴展性的設計優勢。其設計就是限制對象的直接訪問。。動態代理是JDK提供的代理方式且只支持接口,在JVM虛擬機運行時動態生成一系列代理,主要通過Java提供的InvocationHanler類實現。寫一個類實現InvocationHanler接口,實現invoke方法,調用Proxy.newProxyInstance返回實例。
Retrofit在重寫這個方法時,主要做了三件事
1.判斷該接口類是不是一個Object.class,就直接返回方法原有的返回值
2.判斷該方法是否是DefaultMethod,
3.構建一個ServiceMethod對象和OkHttpCall對象,並通過serviceMethod.callAdapter.adapt(okHttpCall)將這兩個對象關聯起來。點開adapt,發現其是CallAdapter接口類的一個方法,如下
<R> T adapt(Call<R> call);
那這個方法是在那裏進行實現的呢,在上面講解build()源碼方法時,即Retrofit實例化時,有下面一段代碼:
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
而adapterFactories其實就是CallAdapter的一個工廠類集,點開defaultCallAdapterFactory方法,如下
CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
if (callbackExecutor != null) {
return new ExecutorCallAdapterFactory(callbackExecutor);
}
return DefaultCallAdapterFactory.INSTANCE; //獲取到默認的CallAdapter的工廠類
}
//接下來打開DefaultCallAdapterFactory類,發現其是繼承自CallAdapter.Factory
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();
@Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public <R> Call<R> adapt(Call<R> call) {
return call; //1在這裏CallAdapter的接口方法adapt被重寫,並返回Call的類型
}
};
}
}
上述代碼看出,在Retrofit初始化實例的時候,CallAdapter接口方法adapt就已經被重寫了,並且 返回的Call的類型。
到這裏爲止,我們就知道了網絡請求結果返回的是Call類型。
請求參數以及請求方式的解析
在我們上面提到的create方法中有這麼一段代碼,如下
ServiceMethod serviceMethod = loadServiceMethod(method);
這段代碼主要就是對接口參數以及請求方式的解析,其中method表示接口方法,點開loadServiceMethod方法,查看其具體實現。
ServiceMethod loadServiceMethod(Method method) {//傳入一個方法體
ServiceMethod result;
synchronized (serviceMethodCache) { //採用同步鎖,保證訪問serviceMethodCache對象的唯一性
result = serviceMethodCache.get(method); //從serviceMethodCache這個hashmap中取method
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
從以上代碼可知,其核心的代碼就是result = new ServiceMethod.Builder(this, method).build()這句,點開Builder(this, method).法,代碼如下
public Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();//接口方法的註解,在Retrofit中爲請求方式
this.parameterTypes = method.getGenericParameterTypes();//參數類型
this.parameterAnnotationsArray = method.getParameterAnnotations();//參數註解數組
}
以上代碼主要是對一些對象進行了實例化,並沒有什麼核心操作,接下來看build()這個方法。代碼如下
public ServiceMethod build() {
callAdapter = createCallAdapter(); //(1)
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw methodError("'"
+ Utils.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
responseConverter = createResponseConverter(); //(2)
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation); //(3)
}
...省略代碼...
//(4)======
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.");
}
//(5)===
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
...省略代碼...
return new ServiceMethod<>(this);
}
從上述代碼,並查看其相關源碼可知,感興趣的可以自行查看,
(1).初始化了一個可用的CallAdapter實例,循環遍歷adapterFactories工廠,取出可用的CallAdapter對象
(2).初始化了一個可用的Converter<ResponseBody, T>實例,循環遍歷converterFactories工廠,取出可用的Converter對象
(3).parseMethodAnnotation(annotation)用來解析註解的請求方式,這裏我們只講解GET請求方式,點開查看其源碼。其它 請求方式相似。如下所示
private void parseMethodAnnotation(Annotation annotation) {
...省略代碼...
if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
}
...省略代碼...
}
打開parseHttpMethodAndPath方法,查看其實現
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
if (this.httpMethod != null) {
throw methodError("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.
//校驗value的值是否合法,規則就是不能有“?”如果有則需要使用@Query註解
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("URL query string \"%s\" must not have replace block. "
+ "For dynamic query parameters use @Query.", queryParams);
}
}
this.relativeUrl = value;//賦值給相對路徑relativeUrl,相當於省略域名的URL
this.relativeUrlParamNames = parsePathParameters(value);//解析路徑參數
//到這裏,我們能得到app/{volumeId}/updateDefaultVolume這樣的一個路徑,大括號裏面的值就是我們需要賦值的參數。
}
(4).先獲取註解數組parameterAnnotationsArray的長度parameterCount,然後遍歷循環parameterAnnotationsArray數組,獲取參數類型以及參數註解。
(5).把上面獲取的參數類型和註解放在同一個方法中進行解析,
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
parameterType:參數類型
parameterAnnotations:參數註解
通過parseParameter方法對parameterType和parameterAnnotations進行解析,並返回ParameterHandler<?>對象,賦值給對應的原始數組對象。那麼parseParameter到底是怎麼解析的呢,我們來打開parseParameter這個方法,
private ParameterHandler<?> parseParameter(
int p, Type parameterType, Annotation[] annotations) {
ParameterHandler<?> result = null;
for (Annotation annotation : annotations) {
ParameterHandler<?> annotationAction = parseParameterAnnotation(
p, parameterType, annotations, annotation);
...省略代碼...
}
...省略代碼...
return result;
}
發現還是遍歷註解,且賦值。繼續打開parseParameterAnnotation方法,當我們查看parseParameterAnnotation( p, parameterType, annotations, annotation)這個方法時,發現其代碼量多達400行左右,且裏面邏輯大多一致,我們就以Path參數註解爲例講解。
private ParameterHandler<?> parseParameterAnnotation(
int p, Type type, Annotation[] annotations, Annotation annotation) {
...省略代碼...
if (annotation instanceof Path) {
...省略代碼...
Path path = (Path) annotation;
String name = path.value(); //獲取到參數名,即獲取@Path("volumeId")中的參數volumeId,
validatePathName(p, name); //驗證參數名是否合法
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Path<>(name, converter, path.encoded());
}
...省略代碼...
return null; // Not a Retrofit annotation.
}
重點關注stringConverter(type, annotations)這個方法,點開stringConverter方法,
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這個數組,以其能從converterFactories中獲取到Converter<?, String>對象,並返回。但是,繼續點開stringConverter(type, annotations, this)這個方法。
public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
你會發現它一直是返回爲null的,所以stringConverter(type, annotations)這個方法會執行接下來的這個方法,(Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE,可以看出它應該調用的是一個靜態對象,點開ToStringConverter類,發現其是一個接口實現類,用來重寫Converter<Object, String>接口的convert方法,並返回一個字符串,這個字符串其實就是參數註解名。
static final class ToStringConverter implements Converter<Object, String> {
static final ToStringConverter INSTANCE = new ToStringConverter();
@Override public String convert(Object value) {
return value.toString();
}
}
那麼Converter<Object, String>這個接口,它是在哪裏進行實現的呢,接下來我們要講new ParameterHandler.Path<>(name, converter, path.encoded())這個方法,而Converter<Object, String>接口的實現就在這個方法中。點開Path,查看其源碼
static final class Path<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
private final boolean encoded;
Path(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, T value) throws IOException {
if (value == null) {
throw new IllegalArgumentException(
"Path parameter \"" + name + "\" value must not be null.");
}
builder.addPathParam(name, valueConverter.convert(value), encoded);
}
}
Path裏面首先市實例化了一些對象:參數名name,轉換器接口實例valueConverter,是否編碼encoded。
接下來重寫了ParameterHandler這個抽象類裏面的apply方法,在這個方法裏面有一個builder.addPathParam(name, valueConverter.convert(value), encoded)方法,其中有傳入一個參數 valueConverter.convert(value),valueConverter它是Converter<Object, String>接口的實例對象,而convert就是接口方法,所以,Converter<Object, String>接口的調用就是在這裏。然後,接下來,我們分析addPathParam這個方法用來幹啥的,點開addPathParam方法,發現它是對路徑進行了一個替換處理,
void addPathParam(String name, String value, boolean encoded) {
if (relativeUrl == null) {
// The relative URL is cleared when the first query parameter is set.
throw new AssertionError();
}
relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded));
}
這個方法把我們傳進來的值value
按照編碼格式轉換,然後替換relativeUrl
中的{name}
,構成一個有效的省略域名的URL。至此,URL的拼接已經完成!
Retrofit網絡請求
Retrofit的網絡請求其實在創建接口實例化的時候,就已經開始了,動態代理創建時,其實現方法invoke裏面會實例一個OkHttpCall對象okHttpCall,點開會發現OkHttpCall這個類是用來實現Call這個接口的,然後重寫了Call裏面的方法。這裏只分析接口Call的enqueue方法。即異步網絡請求方式。
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
這裏只展示enqueue方法
final class OkHttpCall<T> implements Call<T> {
。。。
OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
this.serviceMethod = serviceMethod;
this.args = args;
}
@Override public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
call = rawCall = createRawCall();
} catch (Throwable t) {
failure = creationFailure = t;
}
}
}
。。。
}
}
這裏關注call的創鍵時機,通過上述代碼可知,createRawCall方法它會返回一個call的對象,打開createRawCall查看其源碼。
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;
}
createRawCall方法中首先,它會創建一個Http Request的請求對象request,接下來它會調用Call.Factory接口中的newCall方法,並返回一個Call對象,注意,Call.Factory接口中方法實現在Retrofit的build()方法中就已經被重寫了,如下build()方法中的一段,初始化OkHttpClient對象。。
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
OkHttpClient重寫了Call.Factory接口
public class OkHttpClient implements Cloneable, Call.Factory {
。。。
}
總結:Retrofit主要是在create方法中採用動態代理模式實現接口方法,這個過程構建了一個ServiceMethod對象,根據方法註解獲取請求方式,參數類型和參數註解拼接請求的鏈接,當一切都準備好之後會把數據添加到Retrofit的RequestBuilder中。然後當我們主動發起網絡請求的時候會調用okhttp發起網絡請求,okhttp的配置包括請求方式,URL等在Retrofit的RequestBuilder的build()方法中實現,併發起真正的網絡請求