一、概述
在上一篇 Retrofit 源碼深入分析 —— Call 對象的誕生與請求 的文章中我們基本把 Retrofit 從如何構(gòu)建一個(gè)請求到返回響應(yīng)的整個(gè)過程都梳理了一遍负间,對 Retrofit 的基本工作原理有了一個(gè)完整的了解色乾。按照文章的完成度來說提岔,上一篇文章基本把 Retrofit 講的差不多了宰僧,但筆者還是想把日常普遍使用的幾種方式都梳理一遍匪傍,讓兩篇文章對 Retrofit 的分析更加完整您市。
本篇文章其實(shí)按理來說應(yīng)該整合到上一篇中,但這樣讓本就有點(diǎn)長的文章變得更長役衡,對于閱讀來說可能會很累茵休,而對于筆者來說無論是寫還是校對也很累,索性單開一篇手蝎。而且也并不會妨礙彼此的連貫性榕莺。
二、Retrofit 對 RxJava 的支持
讓我們看看如何用 RxJava 的方式進(jìn)行請求柑船,還是用官方 sample 的例子
- 添加對 RxJava 的支持
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
- 聲明一個(gè)返回類型為 Obervable 的接口方法
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> contributors(@Path("owner") String owner,@Path("repo") String repo);
}
- 創(chuàng)建接口服務(wù)
GitHub github = retrofit.create(GitHub.class);
- 發(fā)起一個(gè)請求
Observable<List<Contributor>> observable = github.contributors("square", "retrofit");
observable.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Contributor>>() {
@Override
public void onCompleted() {}
@Override
public void onError(Throwable throwable) { }
@Override
public void onNext(List<Contributor> contributors) {
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
});
上述是一個(gè)標(biāo)準(zhǔn)的支持 RxJava 的請求步驟帽撑,這里與默認(rèn)的請求最大的區(qū)別除了請求的過程不同外,返回的類型由原來的 Call.class 類型變?yōu)榱?Observable.class 類型也就是一個(gè)被觀察者對象鞍时,所以很明顯 RxJavaCallAdapterFactory 內(nèi)部幫我門做了某種轉(zhuǎn)換,至于注解的解析過程都是一樣的亏拉,這里不在贅述。
讓我們再次延續(xù)上篇文章的 6.1 小節(jié)部分逆巍〖疤粒回到創(chuàng)建 CallAdapter 的地方,也就是 createCallAdapter 方法锐极,上篇文章對這個(gè)方法已經(jīng)進(jìn)行了描述笙僚,所以廢話不多說讓我們直接進(jìn)入 RxJavaCallAdapterFactory 的 get 方法
@Override public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
Class<?> rawType = getRawType(returnType);
boolean isSingle = rawType == Single.class;
boolean isCompletable = rawType == Completable.class;
if (rawType != Observable.class && !isSingle && !isCompletable) {
return null;
}
if (isCompletable) {
return new RxJavaCallAdapter(Void.class, scheduler, isAsync, false, true, false, true);
}
boolean isResult = false;
boolean isBody = false;
Type responseType;
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
Class<?> rawObservableType = getRawType(observableType);
//Model 不可聲明為 Retrofit 的Response 類型
if (rawObservableType == Response.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Response must be parameterized"
+ " as Response<Foo> or Response<? extends Foo>");
}
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
} else if (rawObservableType == Result.class) { //也不可聲明為 RxJava 包下的 Result 類型
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Result must be parameterized"
+ " as Result<Foo> or Result<? extends Foo>");
}
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
isResult = true;
} else {
responseType = observableType;
isBody = true;
}
return new RxJavaCallAdapter(responseType, scheduler, isAsync, isResult, isBody, isSingle,
false);
RxJavaCallAdapterFactory 的 get 方法的邏輯還是很清晰的,首先創(chuàng)建 RxJavaCallAdapter 的前置條件必須為 Observable灵再、Single 和 Completable 而如果是 Completable 只接創(chuàng)建一個(gè)返回類型為 Void 的 adapter, 至于 Single 和 Completable 肋层,前者在 RxJava 中代表只能處理一次事件亿笤,即只能發(fā)射單個(gè)數(shù)據(jù)或錯(cuò)誤事件。而 Completable 正如它的名字栋猖,它不負(fù)責(zé)發(fā)送數(shù)據(jù)净薛,只會處理 Rxjava 的 conComplete 和 onError 事件。
RxJavaCallAdapter 創(chuàng)建完成后就和 Call 對象的誕生流程差不多了蒲拉。所以讓們直接進(jìn)入其中的 adapt 方法看看 Observable 的真面目
//形參為 OkHttpCall 創(chuàng)建的過程請看上一篇文章
@Override public Object adapt(Call<R> call) {
OnSubscribe<Response<R>> callFunc = isAsync
? new CallEnqueueOnSubscribe<>(call)//異步肃拜,將 OkHttpCall 對象傳入
: new CallExecuteOnSubscribe<>(call);//同步,將 OkHttpCall 對象傳入
OnSubscribe<?> func;
if (isResult) {//聲明類型不是的 RxJava 包下的 Result 類型 默認(rèn) false
func = new ResultOnSubscribe<>(callFunc);
} else if (isBody) { //默認(rèn) true
func = new BodyOnSubscribe<>(callFunc);
} else {
func = callFunc;
}
//創(chuàng)建一個(gè)被觀察對象
Observable<?> observable = Observable.create(func);
if (scheduler != null) {
observable = observable.subscribeOn(scheduler);
}
if (isSingle) {//返回一個(gè) Single 事件
return observable.toSingle();
}
if (isCompletable) {//返回一個(gè) toCompletable 事件
return observable.toCompletable();
}
return observable;
}
注釋已經(jīng)進(jìn)行了詳細(xì)的解釋就不多說了雌团,由于我們使用的是異步請求燃领,所以肯定創(chuàng)建的是 CallEnqueueOnSubscribe 對象,該對象實(shí)現(xiàn)了 OnSubscribe 接口并重寫了 call 方法锦援,代碼如下
@Override public void call(Subscriber<? super Response<T>> subscriber) {
// Since Call is a one-shot type, clone it for each new subscriber.
Call<T> call = originalCall.clone();
final CallArbiter<T> arbiter = new CallArbiter<>(call, subscriber);
subscriber.add(arbiter);
subscriber.setProducer(arbiter);
call.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, Response<T> response) {
arbiter.emitResponse(response);
}
@Override public void onFailure(Call<T> call, Throwable t) {
Exceptions.throwIfFatal(t);
arbiter.emitError(t);
}
});
}
代碼并不復(fù)雜猛蔽,可以看到內(nèi)部還是委托 OkHttpCall 的 enqueue 方法來獲取請求的 response ,在經(jīng)過 GsonConvertAdapter 轉(zhuǎn)換后,將該 Response 發(fā)射出去雨涛。那么到這里整個(gè) RxJava 的支持過程就清晰了枢舶。
首先當(dāng)我們把接口的返回類型聲明為 Observable<T> 類型時(shí),會通過 RxJavaCallAdapterFactory 的 get 方法來幫我們創(chuàng)建一個(gè) RxJavaCallAdapter 對象替久,RxJavaCallAdapter 的 adapt 方法中會幫我們創(chuàng)建一個(gè)被觀察者對象返回凉泄。而當(dāng)我們通過如下代碼發(fā)起一個(gè)訂閱
observable.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer());
訂閱完成后,CallEnqueueOnSubscribe 對象的 call 方法就會被觸發(fā)蚯根,剩下的就是在 onNext 方法中接收 Resposne 了后众。
從整體的處理環(huán)節(jié)來看,除了 RxJava 本身的特性之外颅拦,基本的邏輯和默認(rèn)的 Call<T> 類型的處理是差不多的蒂誉。因?yàn)槎际亲裱耐惶捉涌跇?biāo)準(zhǔn)。所以如果你不喜歡默認(rèn)的類型或 Rxjava距帅,完全可以按照 Retorfit 的標(biāo)準(zhǔn)建立一套自己的 callAdapter右锨。
三、Retrofit 對協(xié)程的支持
協(xié)程作為近幾年很火的異步框架碌秸,其簡便的異步操作方式可以說但凡用過的人沒有不喜歡的绍移,其熱度在 Android 領(lǐng)域已經(jīng)有超過 RxJava 的趨勢。而 Retrofit 自然不會甘于人后讥电,在 2.6.x版本以上也對協(xié)程進(jìn)行了支持蹂窖。
如果你還不了解協(xié)程,建議先 Google/Baidu 查詢一下相關(guān)文章了解一番恩敌,否則可能無法無法愉快的閱讀瞬测。
接下來讓還是讓我們以官方 Sample 為例看看 Retrofit 在 Android 開發(fā)中使用協(xié)程的基本流程(下面的步驟是 Kotlin 代碼)
- 添加基礎(chǔ)依賴
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
//Android 協(xié)程依賴庫
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// 包含協(xié)程的 Activity lifecycle 擴(kuò)展
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"
- 構(gòu)建 Retrofit 實(shí)例
val API_URL = "https://api.github.com"
val retrofit = Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
- 創(chuàng)建掛起函數(shù)
interface Github {
@GET("/repos/{owner}/{repo}/contributors")
suspend fun contributors(
@Path("owner") owner: String,
@Path("repo") repo: String) : List<Contributor>
}
- 創(chuàng)建接口服務(wù)
val github = retrofit.create(Github::class.java)
- 開啟一個(gè)協(xié)程進(jìn)行請求
lifecycleScope.launch{
val contributors = github.contributors("square", "retrofit")
for ((login, contributions) in contributors) {
println("$login ($contributions)")
}
}
上述流程算是在 Android 中的常用步驟,但在實(shí)際使用中 lifecycleScope.launch 會單獨(dú)封裝不會像上面那樣單獨(dú)拿出來使用。
簡單了解了上面的流程后月趟,我們來看看 Retrofit 是如何對攜程進(jìn)行支持的灯蝴。
3.1、Retrofit 是如何知道我們用的是協(xié)程孝宗?
要想知道這些讓我們回到創(chuàng)建 RequestFactory 的地方(上一篇文章第五節(jié))
RequestFactory build() {
//解析接口方法的注解绽乔,列如 @GET @POST @Headers 的值等等...
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
//省略一些判斷代碼...
//解析形參的注解并獲取注解對應(yīng)的 value
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);
}
//省略一些判斷代碼...
return new RequestFactory(this);
}
這里我們重點(diǎn)要關(guān)注的是解析形參的部分也就是 parseParameter 方法,該方法的完整簽名如下
/*
* @p 傳入的循環(huán)索引
* @parameterType 參數(shù)類型 例如 String Int
* @annotations 參數(shù)注解數(shù)組 例如 @Path @Query
* @allowContinuation 是否使用了協(xié)程
*/
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
了解了上面的內(nèi)容碳褒,我們現(xiàn)在再看那個(gè)循環(huán)解析參數(shù)注解的邏輯,如果對協(xié)程不了解的話閱讀這段代碼的時(shí)候你可能會很疑惑看疗,為什么當(dāng)索引 P == lastparameter 的時(shí)候就可以允許支持協(xié)程了沙峻, 而且還有一個(gè)重要的布爾字段那就是 isKotlinSuspendFunction 的改為 true 的過程,可能看了也會讓你迷惑两芳,讓我們進(jìn)入 parseParameter 方法看看關(guān)鍵代碼
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
ParameterHandler<?> result = null;
//省略參數(shù)解析代碼...
if (result == null) {
if (allowContinuation) { //當(dāng) allowContinuation 為 true 也就是最后一個(gè)參數(shù)的索引和注解數(shù)組長度相等
try {
//最后一個(gè)參數(shù)類型為 Continuation.class 類型 isKotlinSuspendFunction 為true
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
}
}
throw parameterError(method, p, "No Retrofit annotation found.");
}
return result;
}
可以看到表面邏輯倒也沒什么難以理解的摔寨,就是判斷參數(shù)是否為最后一個(gè)并判斷最后一個(gè)參數(shù)是否為 Continuation.class ,奇怪了怖辆,我們并未聲明 Continuation.class 類型的參數(shù)是复,它判斷個(gè)什么?要想解答這些困惑竖螃,我們需要對 suspend 函數(shù)進(jìn)行一下反編譯來看看它在 java 中是個(gè)什么樣子
//kotlin 代碼
@GET("/repos/{owner}/{repo}/contributors")
suspend fun contributors(
@Path("owner") owner: String,
@Path("repo") repo: String) : List<Contributor>
//反編譯后的 Java 代碼
@GET("/repos/{owner}/{repo}/contributors")
@Nullable
Object contributors(
@Path("owner") @NotNull String var1,
@Path("repo") @NotNull String var2,
@NotNull Continuation var3);
看了上面的反編譯代碼是不是有點(diǎn)恍然大明白的感覺,原來 suspend 轉(zhuǎn)換為 java 代碼后返回的類型變?yōu)榱俗钤嫉?Object ,同時(shí)形參自動增加了一個(gè) Continuation.class 類型的參數(shù)茁肠,至此也就明白了為什么要判斷最后一個(gè)參數(shù)為 Continuation.class 類型才能把 isKotlinSuspendFunction 字段改為 true 的處理邏輯画髓。
3.2、Retrofit 對協(xié)程的處理
上篇文章中筆者故意將協(xié)程相關(guān)代碼隱藏腻格,主要是為了專注非協(xié)程情況下的源碼分析画拾,而本節(jié)將會重點(diǎn)展示協(xié)程相關(guān)代碼,以梳理 Retrofit 對協(xié)程的支持過程
上一節(jié)中我們已經(jīng)明白了 isKotlinSuspendFunction 字段的處理邏輯菜职,現(xiàn)在讓我們把視線轉(zhuǎn)到 HttpServiceMethod 的 parseAnnotations 方法青抛,看看協(xié)程部分的代碼
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
//是否為 Suspend 掛起函數(shù),也就是 Kotlin 的協(xié)程
if (isKotlinSuspendFunction) {
//獲取所有參數(shù)類型
Type[] parameterTypes = method.getGenericParameterTypes();
//獲取參數(shù)類型的下邊界
Type responseType = Utils.getParameterLowerBound(0,
(ParameterizedType) parameterTypes[parameterTypes.length - 1]);
//獲取類型全限定類名并判斷是否為 Response 類型
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true; //協(xié)程返回類型為 Response<T> 類型標(biāo)記位 true
} else {
//省略...
}
//獲取要適配的 call 類型 酬核,其實(shí)就是創(chuàng)建了一個(gè) ParameterizedType 的實(shí)例蜜另,類型為 call
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
//這里調(diào)用了一個(gè)實(shí)現(xiàn)了 SkipCallbackExecutor 注解的類,
//ensurePresent 方法主要作用是將原注解數(shù)組替換為 SkipCallbackExecutor 注解
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
//獲取要適配的 call 類型
adapterType = method.getGenericReturnType();
}
//省略若干代碼...
//獲取 OkHttpClient 實(shí)例
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//返回的是 Response<T> 類型
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//返回的是 Body
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}
注釋解釋的很清楚了愁茁,這里我們重點(diǎn)關(guān)注一下 SkipCallbackExecutorImpl.ensurePresent() 方法蚕钦,代碼如下
static Annotation[] ensurePresent(Annotation[] annotations) {
if (Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)) {
return annotations;
}
//創(chuàng)建一個(gè)新的注解數(shù)組
Annotation[] newAnnotations = new Annotation[annotations.length + 1];
// 利用系統(tǒng)深拷貝將原數(shù)組注解類型替換為 SkipCallbackExecutor 注解
newAnnotations[0] = SkipCallbackExecutorImpl.INSTANCE;
System.arraycopy(annotations, 0, newAnnotations, 1, annotations.length);
return newAnnotations;
}
看完上面的代碼你可能會覺得這個(gè) SkipCallbackExecutor 似曾相識,還記得 DefaultCallAdapterFactory 的 get 方法嗎
@Override
public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
//省略若干代碼...
//這里其實(shí)主要的作用是為了判斷是否使用了協(xié)程鹅很,如果實(shí)現(xiàn)了協(xié)程那么則不使用系統(tǒng)的回調(diào)線程并返回null
final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
return executor == null
? call // OkHttpCall 對象
: new ExecutorCallbackCall<>(executor, call);
}
};
}
如此 SkipCallbackExecutor 注解的作用就清晰了嘶居,首先當(dāng)為 Suspend 函數(shù)時(shí),Retrofit 會將原注解進(jìn)行深拷貝變?yōu)?SkipCallbackExecutor 注解類型,然后會在創(chuàng)建 Call 對象的時(shí)候判斷邮屁,如果符合條件則不用默認(rèn)的 Executor , 直接通過 OkHttpCall 進(jìn)行 async / enqueue 請求整袁。
接下來就是協(xié)程和非協(xié)程返回對象的區(qū)別,從上面的代碼中我們可以很清晰的看到佑吝,對于協(xié)程返回有兩種Response 類型坐昙,一種為自定義的 Model , 一種為 responseBody 類型,分別對應(yīng) SuspendForResponse 和 SuspendForBody 對象芋忿。而兩者和非協(xié)程環(huán)境下的 CallAdapted 對象相同炸客,都是 HttpServiceMethod 的子類,所以無論哪種環(huán)境戈钢,拋開其它因素痹仙,兩者的調(diào)用過程都是相同的,即先調(diào)用 invoke 方法創(chuàng)建 OkHttpCall 對象殉了,在調(diào)用 adapt 方法進(jìn)行具體的請求开仰,SuspendForResponse 類的 adapt 方法如下
/*
* @call 是 OkHttpCall 對象
* @args 是我們請求的參數(shù) 就是 github.contributors("square", "retrofit")
*/
@Override
protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
//獲取參數(shù)列表中的最后一個(gè) continuation 類型參數(shù) ,詳看上面 suspend 反編譯成 Java 代碼
Continuation<Response<ResponseT>> continuation =
(Continuation<Response<ResponseT>>) args[args.length - 1];
// 調(diào)用 Kotlin 擴(kuò)展函數(shù)
try {
return KotlinExtensions.awaitResponse(call, continuation);
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}
在 Retrofit 源碼中有一個(gè) KotlinExtensions.kt 文件薪铜,是一個(gè) Kotlin 的擴(kuò)展文件众弓,專門用來處理 suspend 函數(shù),我們進(jìn)入 awaitResponse 函數(shù)看看
suspend fun <T : Any> Call<T>.awaitResponse(): Response<T> {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
//調(diào)用 OkHttpCall 的 enqueue 方法獲取轉(zhuǎn)換后的響應(yīng)(和上篇文章中的過程是一樣的)
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
//喚醒掛起函數(shù)隔箍,返回 response
//表現(xiàn)形式看開頭通過協(xié)程獲取 model 的代碼
continuation.resume(response)
}
override fun onFailure(call: Call<T>, t: Throwable) {
//喚醒掛起函數(shù)谓娃,拋出異常
continuation.resumeWithException(t)
}
})
}
}
注釋解釋的很清楚了,其中 suspendCancellableCoroutine 是創(chuàng)建協(xié)程的方式之一蜒滩,感興趣的可以了解下傻粘。至于 SuspendForBody 對象,它和 SuspendForResponse 的處理過程是一樣的帮掉,這里就不多說了弦悉。
所以回顧整個(gè)流程對于 suspend 函數(shù)的處理步驟如下
- 如果形參列表的最后一個(gè)參數(shù)是為 Continuation.class 類型,則 isKotlinSuspendFunction 賦值為 true
- 獲取 suspend 函數(shù)返回類型蟆炊,如果為 Response<T> 類型稽莉,continuationWantsResponse = true
- 獲取接口方法所有注解,深拷貝替換為 SkipCallbackExecutor 類型涩搓,并以此為條件決定采用默認(rèn)的 Excutor 還是 直接使用 OkHttpCall
- 根據(jù) continuationWantsResponse 判斷返回 SuspendForResponse 還是 SuspendForBody 對象
- 在 SuspendForResponse / SuspendForBody 的 adapt 方法中調(diào)用 KotlinExtensions.awaitResponse / awaitNullable 方法獲取 response 并喚起協(xié)程返回?cái)?shù)據(jù)
如果有些地方還是不明白可以跟著筆者的步驟跟進(jìn)源碼一步步看污秆,這樣會更清晰。
四昧甘、結(jié)語
終于良拼,用了兩篇文章將 Retrofit 分析完了,沒有拉下任何一個(gè)常用的關(guān)鍵步驟充边,可以說是非常全面的分析了庸推。而對于自己來說也有種難言的收獲感常侦,文章很長,代碼也是反復(fù)看了很久很久贬媒,而過程中的抓耳撓腮聋亡,反復(fù) debug, 在理解完最后一行源碼的時(shí)候,頓時(shí)有一種通透的舒爽感际乘,久久不能平靜坡倔,這里也吐槽一下自己,其實(shí)關(guān)于 Retrofit 的文章在前幾年就該寫出來脖含,但一直拖到了現(xiàn)在罪塔,再次感到自己是真的懶,以后盡量勤快點(diǎn)吧养葵!