Retrofit原始碼總結
本文總結基於Retrofit 2.4.0
Retrofit 底層是基於OkHttp的進行網路請求的一種封裝庫。
其實各類大佬部落格,公眾號,學習網站對Retrofit框架都有詳細解讀和完美分析了,菜鳥與我,對大佬之敬佩之膜拜,如滔滔江水,延綿不絕,但是,技術是大家的,文章是別人的,帶來的結果就是理解但不深刻,掌握但不全面。所以來個自我總結,與人分享,才能共同進步,系不繫 O(∩_∩)O
分為以下三大步來總結
1 Retrofit的使用方式
2 Retrofit的原始碼總結
3 Retrofit的原理總結
Retrofit的使用方式
直接使用官網例子
第一步:肯定新增一個Retrofit依賴了
compile 'com.squareup.retrofit2:retrofit:2.4.0'
當然也可以直接複製依賴。
第二步:建立一個Retrofit物件,並指定api的域名。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
//這是通過一個Builder模式來構建Retrofit物件的。
//這裡說一下我理解的構造著模式的好處吧,總結兩點:
//1. 將一個物件的構建和它的表示進行分離,比如一個物件很多屬性需要初始化,
//可以通過Builder一鍵配置。鏈式呼叫,方便簡潔。
//2. 直接建立物件,物件屬性依次初始化,比如
//Person person = new Person();
//person.setName("小吳漸漸");
//person.setAge("27");
//出現的問題是物件已經建立,記憶體空間,
//系統資源都已經配置好了,小吳漸漸這個人本來是15歲的小鮮肉,突然變成了27歲的老臘肉了,
//系統就需要把以前的資源擦除,重新配置資源這樣就帶來了效能問題了。
//而 Builder模式是將所有的屬性事先配置好,最後通過build一下,一次性完成所有的資源配置。
第三步:根據api宣告一個java介面,通過java註解來描述這個api
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
//List<Repo>是根據介面返回的json宣告需要的返回值型別
//本來是Response,可是我們需要具體的返回值物件,所以Retrofit支援轉換器工廠配置addConverterFactory()
所以第二步可以這樣寫,這個時候需要新增Gson支援,新增依賴方式參照第一步
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.build();
第四步:通過retrofit建立這個介面物件
GitHubService gitHubService = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = gitHubService.listRepos("octocat");
最後一步:網路請求
//同步請求(在原來的執行緒中執行)
repos.execute();
//非同步請求(在新的執行緒中執行)看字面意思是一個佇列,但多個請求同時請求時,
//並不是排隊進行的,而是並行的。當請求數特別多的時候,考慮網路效能的原因,
//會等待其他網路請求完成時,再去執行。
repos.enqueue(new Callback<List<Repo>>() {...);
Retrofit的原始碼總結
看Retrofit.Builder原始碼
Builder(Retrofit retrofit) {
platform = Platform.get();
callFactory = retrofit.callFactory;
baseUrl = retrofit.baseUrl;
......
callbackExecutor = retrofit.callbackExecutor;
}
//懵逼狀態,也不知道這些初始化變數有啥用,暫時放下Builder
往下看retrofit.create(GitHubService.class);,這個方法建立了介面的例項物件,這裡的疑問就是介面物件是怎麼例項化的,所以下面的程式碼是Retrofit整個結構的核心
public <T> T create(final Class<T> service) {
//驗證傳入的service是否是介面,且是否是原生介面也就是不能是繼承其他介面的介面,
//其實做的事情就是型別校驗
Utils.validateServiceInterface(service);
//對你service中的所有宣告的合法性,是否需要提前驗證,
//這樣做的目的是介面建立的一瞬間進行集中驗證,能夠及早
//發現程式碼錯誤,但是不利於效能,因為每個地方都要驗證的話,會造成卡頓。
//一般是關閉的。
if (validateEagerly) {
eagerlyValidateMethods(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.adapt(okHttpCall);
}
});
}
這行程式碼拎出來就是:
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service }, new InvocationHandler(){...});
這行程式碼有三個引數
1 service.getClassLoader:
建立任何類的時候都需要提供一個類載入器,將這個類載入進來,這裡直接傳入介面所在的類載入器。
2 new Class[] { service }:
提供你所建立的所有介面,new Class[] { service }這個方法的作用就是將你提供的所有介面都實現到一個類中,不過一般只有一個介面,也就是你所宣告的所有API註解描述都在一個Service介面中。耶?關鍵你怎麼就實現到一個類中啊,沒看到啊,關鍵在第三個引數。
3 new InvocationHandler() {...}
第三個引數是一個匿名內部類,實現了invoke回撥方法,這裡的作用是傳入一個InvocationHandler物件,在需要的時候,呼叫invoke方法;
這裡最關鍵的是Proxy.newProxyInstance方法通過這三個引數做哪些事。
第一會建立一個物件,這個物件會實現你的介面,並重寫介面中宣告的抽象方法,同時傳入一個InvocationHandler物件,虛擬碼如下:
public class RetrofitService implements GitHubService {
InvocationHandler mInvocationHandler = new InvocationHandler() {
......
invoke(xxxx){
......
}
......
});
@Override
public Call<List<Repo>> listRepos(String user) {
try {
//獲取你宣告的介面中需要呼叫的方法資訊,你呼叫哪個方法就獲取哪個方法,畢竟介面中不止一個方法,是吧。
Method method = GitHubService.class.getMethod("listRepos");
return (Call<List<Repo>>)mInvocationHandler
.invoke(this, method, xxx);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
......
}
}
Proxy.newProxyInstance方法方法就是做的這個事,retrofit.create(GitHubService.class);方法實際上建立一個Service介面的實現類的物件,當呼叫介面中宣告的方法的時候,就會通過InvocationHandler物件呼叫invoke方法,並傳入介面中宣告的方法資訊(包括註解,引數,返回值型別),返回了一個Retrofit的Call物件,這裡的疑問是invoke方法,也就是InvocationHandler .invoke(this, method, xxx);這個代理又做哪些事?
回頭看一下invoke方法被實現的地方,在Proxy.newProxyInstance()的第三個引數中,直接把第三個引數的回撥方法抽出來看,如下:
invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
private final Platform platform = Platform.get();
//getDeclaringClass()意思是宣告的方法在哪個類中宣告的,比如onCreate()方法是在MainActivity中宣告的。
//下面這行,如果方法宣告所在的類是object.class那麼就不改寫這個方法,直接返回。
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
//platform這裡確定平臺,分別有Android Java8 預設Platform
//下面這行,確定介面預設的方法的實現的平臺, 這一行和上面一行都是保證程式的相容性,讓程式不出錯而已
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
// 下面這三行重要程式碼看來是關鍵了,單獨抽出來看看
// 第1行重要程式碼
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
// 第2行重要程式碼
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
// 第3行重要程式碼
return serviceMethod.adapt(okHttpCall);
}
});
第1行重要程式碼 ServiceMethod serviceMethod = (ServiceMethod) loadServiceMethod(method);
ServiceMethod 官方註釋: /** Adapts an invocation of an interface method into an HTTP call. */
看意思是將介面的方法適配到一個HTTP call中,不知所云,反正不知道幹啥用的,接著看loadServiceMethod(method)的實現,程式碼如下:
ServiceMethod<?, ?> loadServiceMethod(Method method) {
// serviceMethodCache是一個Map<Method, ServiceMethod<?, ?>>,用map來做Cache非常常見
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
// 如果沒有就去建立ServiceMethod,然後放入map中,抽出來看ServiceMethod.Builder裡面的實現,如下
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
Builder(Retrofit retrofit, Method method) {
// retrofit是一個總管了,這本來就是一個Retrofit框架是不是
this.retrofit = retrofit;
// 根據api定義介面中的方法
this.method = method;
// 方法中每個註解資訊
this.methodAnnotations = method.getAnnotations();
// 定義方法的返回值型別
this.parameterTypes = method.getGenericParameterTypes();
// 方法中每個引數和引數註解的資訊
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
......
public ServiceMethod build() {
// 這個暫時不知道幹啥的
callAdapter = createCallAdapter();
// calladapter 的響應型別中的泛型,比如 Call<User> 中的 User
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {......}
// 點進去看看 ⬇
responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) {
// 解析每一個方法的註解
parseMethodAnnotation(annotation);
}
......
}
private Converter<ResponseBody, T> createResponseConverter() {
// 獲取宣告方法的註解
Annotation[] annotations = method.getAnnotations();
try {
// 根據返回值型別和方法註解獲取Converter<ResponseBody, T>物件
// responseConverter做的事情就是
// 將網路請求的資料轉化為宣告介面方法中的返回泛型
return retrofit.responseBodyConverter(responseType, annotations);
}
......
}
//在ServiceMethod可以直接找到呼叫的地方
R toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
哦,這麼一看有點明白了,ServiceMethod做的事情就是解析我們宣告的介面中的方法,比如方法註解,返回值型別,引數和引數註解等。
第2行重要程式碼 OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
通過解析我們定義的方法而生成的ServiceMethod物件傳入到OkHttpCall的構成方法中返回了一個OkHttpCall的物件,來看看裡面的實現,可以看出OkHttpCall實現了Call,而這個Call實際上就是Retrofit使用方式的第四步中Call> repos = gitHubService.listRepos("octocat");,注意:是 Retrofit 中的 Call 而不是 OkHttp 中的 Call ,而這個Call就是宣告Service介面中定義的
@GET("users/{user}/repos")
Call> listRepos(@Path("user") String user);
final class OkHttpCall<T> implements Call<T> {
OkHttpCall(ServiceMethod<T, ?> serviceMethod, @Nullable Object[] args) {
//serviceMethod傳進來僅僅做了一些變數初始化,接著看invoke方法中最後一行程式碼,往下。
this.serviceMethod = serviceMethod;
this.args = args;
}
}
第3行重要程式碼 serviceMethod.adapt(okHttpCall)
根據第2行重要程式碼生成的 OkHttpCall物件,終於知道我們的Call物件是怎麼來的了,怎麼還要作為引數傳入到serviceMethod的adapt()方法中,沒辦法,接著往下看
T adapt(Call<R> call) {
//看到這個callAdapter,是不是很熟悉,就是在第1行重要程式碼中
//通過 build 生成 ServiceMethod 物件的地方,當時猜測callAdapter做型別轉換的事情
return callAdapter.adapt(call);
}
現在找找callAdapter是如何建立的,這裡 ServiceMethod.Builder<>(this, method).build();
public ServiceMethod build() {
//現在來看看createCallAdapter()的具體實現
callAdapter = createCallAdapter();
......
}
⬇⬇
private CallAdapter<T, R> createCallAdapter() {
......
// 獲取方法返回值型別
Type returnType = method.getGenericReturnType();
......
// 獲取方法所有的註解資訊
Annotation[] annotations = method.getAnnotations();
try {
//noinspection unchecked
//巴拉巴拉,一坨程式碼,核心程式碼原來在這裡,傳入上面兩個初始化引數,生成了CallAdapter,繼續看內部實現
return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) {
......
}
}
⬇⬇
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
//繼續點進去看
return nextCallAdapter(null, returnType, annotations);
}
⬇⬇
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
......
// 原理CallAdapter是從callAdapterFactories集合中取出Factory,再根據返回值型別,註解資訊獲取到的。
// Factory是CallAdapter的一個內部類,callAdapterFactories又是如何獲取的呢?接著往下看
CallAdapter<?, ?> adapter = callAdapterFactories.get(i)
.get(returnType, annotations, this);
......
}
⬇⬇
public final class Retrofit {
......
final List<CallAdapter.Factory> callAdapterFactories;
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
@Nullable Executor callbackExecutor, boolean validateEagerly) {
......
// 在這裡,是通過作為Retrofit的構造方法的引數傳進來的,再看看哪裡呼叫了 Retrofit 的構造方法
this.callAdapterFactories = callAdapterFactories;
......
}
⬇⬇
//看這個是不是很熟悉,就是我們的Retrofit物件通過Buidle模式建立的時候呼叫的地方
public Retrofit build() {
//callAdapterFactories找的好辛苦,原來在這裡實現的
// 這裡有兩行
//這一行會將一個空的callAdapterFactories新增進來
List<CallAdapter.Factory> callAdapterFactories =
new ArrayList<>(this.callAdapterFactories);
//原來是通過defaultCallAdapterFactory方法獲取callAdapterFactories
// 接下來看看 defaultCallAdapterFactory()方法的實現
callAdapterFactories.add(
platform.defaultCallAdapterFactory(callbackExecutor));
......
//在這裡呼叫了Retrofit的構造方法,並傳入了callAdapterFactories
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
⬇⬇
CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
if (callbackExecutor != null) {
//繼續點進去看
return new ExecutorCallAdapterFactory(callbackExecutor);
}
return DefaultCallAdapterFactory.INSTANCE;
}
//這個類繼承了CallAdapter.Factory也
//就是通過callAdapterFactories集合獲取到的物件
final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
......
ExecutorCallAdapterFactory(Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
// 這裡就是上述方法中呼叫get方法真正獲取CallAdapter物件
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
......
return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}
// 最重要的關鍵點,就是動態代理中invoke方法的最後一行重要方法傳入HttpCall物件,
@Override public Call<Object> adapt(Call<Object> call) {
return new ExecutorCallbackCall<>(callbackExecutor, call);
}
};
}
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
......
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");
//終於知道enqueue是如何呼叫的了,delegate是一個Call物件,
//是invoke方法中第3行重要程式碼中傳入的HttpCall物件,而HttpCall是實現Call介面並重寫了enqueue方法的,等下介紹響應部分原始碼
delegate.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, final Response<T> response) {
// 最最重要的是這個了callbackExecutor
// 回撥方法執行器,是Executor物件,和執行緒相關的類,
// 來看看callbackExecutor的實現地方
callbackExecutor.execute(new Runnable() {
@Override public void run() {
......
//callback就是自己呼叫enqueue方法傳入的物件
callback.onFailure(ExecutorCallbackCall.this, new
......
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
}
});
}
......
});
}
callbackExecutor的物件是通過呼叫ExecutorCallAdapterFactory構造方法傳入
ExecutorCallAdapterFactory(Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
//看看哪裡呼叫了ExecutorCallAdapterFactory方法
⬇
//就是我們的Retrofit物件通過Buidle模式建立的時候呼叫的地方,也是callAdapterFactor初始化的地方
public Retrofit build() {
......
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
//callbackExecutor是在這裡返回的,前面說過,
//platform是java8和Android都有的平臺,看看在哪裡繼承了Platform,
//並呼叫了defaultCallbackExecutor()方法
callbackExecutor = platform.defaultCallbackExecutor();
}
List<CallAdapter.Factory> callAdapterFactories =
new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
......
⬇
//在這裡,Android平臺繼承了Platform,並呼叫了defaultCallbackExecutor方法
static class Android extends Platform {
@Override public Executor defaultCallbackExecutor() {
// MainThreadExecutor,是不是很熟悉了,想想應該是做主執行緒切換的作用,
//MainThreadExecutor看看具體的實現
return new MainThreadExecutor();
}
⬇
static class MainThreadExecutor implements Executor {
// 獲取主執行緒的Handler
private final Handler handler = new Handler(Looper.getMainLooper());
@Override public void execute(Runnable r) {
// 通過handler將執行緒切換到主執行緒
handler.post(r);
}
}
現在回頭看看建立Retrofit的build方法,裡面的各種屬性就應該很好理解了O(∩_∩)O
public Retrofit build() {
.....
// callFactory就是OkHttpClient,真正執行網路請求的的工廠
callFactory = new OkHttpClient();
.....
// 執行緒相關的類,通過它可以獲取到CallAdapter
callbackExecutor = platform.defaultCallbackExecutor();
......
// CallAdapter的工廠,將執行環境切換到主執行緒中的作用,當然,並不是只做這個,
// 還可以做 Rxjava 的適配,可以將執行緒切換交給Rxjava來處理,
// 你如果沒有支援的話,預設使用ExecutorCallAdapterFactory
// 介面中會根據宣告的每個方法的註解和引數以及返回值型別建立每個CallAdapter
List<CallAdapter.Factory> callAdapterFactories =
new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
// 資料轉換工廠
List<Converter.Factory> converterFactories =
new ArrayList<>(1 + this.converterFactories.size());
......
}
請求過程大致結構和過程看過來了,其中細節特多,最後,第三行重要程式碼多的夠夠的,整套流程下來,差不多明白了serviceMethod的adapt(),主要做執行緒切換和Rxjava的適配。
響應部分原始碼
上面說了HttpCall是繼承了Call介面,並重寫了enqueue方法,最後通過handler將執行環境切換到主執行緒的。說了這麼多,OkHttp在哪裡呢?來看看HttpCall呼叫enqueue()的地方。
@Override
public void enqueue(final Callback<T> callback) {
okhttp3.Call call;
......
// 建立了OkHttp的Call的物件,上面說的Call包括 HttpCall 都是 Retrofit 的 Call,
// 這裡是 Retrofit 和 OkHttp 的地方
call = rawCall = createRawCall();
......
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
......
response = parseResponse(rawResponse);
......
}
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
......
int code = rawResponse.code();
// 響應碼 小於200 或 大於300 都算失敗,一般 Okhttp內部就已經解決了,基本上失敗情況到不了這裡
if (code < 200 || code >= 300) {
......
}
// 返回一個沒有body的結果,body就是OkHttp響應報文中的body
if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
try {
// 這裡將請求下來的body轉化為返回值中對應的泛型,並返回。
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
}
......
}
}
最後
花了大概兩天時間,整理了一下,把總體架構梳理一篇,尤其動態代理怎麼做的,重點說了一下,細節很多,自己寫的都暈了,很多說的不夠好。僅僅作為自己的總結,學習,記錄一下,如有錯誤,多多指教。Retrofit框架超級厲害,大量封裝了建造者模式、裝飾者模式,值得學習。