本文針對(duì)的是Retrofit 2.1.0
一、Retrofit簡(jiǎn)介
如今開發(fā)一個(gè)Android應(yīng)用,幾乎都需要和網(wǎng)絡(luò)打交道,最常見的就是通過http協(xié)議來請(qǐng)求服務(wù)器數(shù)據(jù)或者是給服務(wù)器發(fā)送數(shù)據(jù),雖然Android本身也提供了HttpUrlConnection和Volley來發(fā)起http請(qǐng)求,功能上其實(shí)也能滿足,但鑒于Retrofit+OkHttp的組合如此流行,所以幾個(gè)項(xiàng)目下來,都是用的這一套。
??Retrofit是一個(gè)針對(duì)Android和java的類型安全的、開源的http客戶端,由大名鼎鼎的Square公司出品(另外還有:picasso、dagger等),通過注解的方式,讓開發(fā)者定義一個(gè)接口方法來完成http請(qǐng)求(真正的請(qǐng)求其實(shí)是由OkHttp來完成的),至于這個(gè)過程是怎么實(shí)現(xiàn)的,后面會(huì)詳細(xì)說。
相關(guān)鏈接:
二、使用
簡(jiǎn)單介紹下Retrofit的使用
- 定義一個(gè)接口:ApiService
public interface ApiService {
/**
* 獲取協(xié)議內(nèi)容
*
* @return 此時(shí)返回的JsonResponse里面的body字段的類型為 AgreementResponseBody
*/
@GET("system/getAgreement")
Call<JsonResponse> getAgreement();
/**
* 獲取點(diǎn)贊信息
* @param getApprovalInfoBean
* @return
*/
@POST("approval/getApproval")
Call<JsonResponse> getApprovalInfo(@Body GetApprovalInfoBean getApprovalInfoBean);
}
上面就定義了兩個(gè)最常用的http的方法,GET請(qǐng)求和POST請(qǐng)求,可以看到,需要使用GET或者是POST只需要在方法上加上注解就可以(當(dāng)然還有其它方法,像:DELETE等)。POST方法的話因?yàn)橛姓?qǐng)求體,所以會(huì)用@Body來注解,這里放的是我自定義的一個(gè)類(因?yàn)榻Y(jié)合了fastjson使用)。兩個(gè)方法的返回都是Call<T>的形式。
- Retrofit的相關(guān)配置及初始化
??Retrofit使用了Builder模式,主要是做了一些配置,可以看到,實(shí)例化了一個(gè)OkHttpClient,因?yàn)橐盟鼇磉M(jìn)行實(shí)際的http請(qǐng)求。
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.readTimeout(10, TimeUnit.SECONDS);
builder.connectTimeout(9, TimeUnit.SECONDS);
//添加攔截器,保留一些調(diào)試時(shí)的日志
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
}
OkHttpClient okHttpClient = builder.build();
/**
* addConverterFactory,是為了對(duì)象的序列化和反序列化,一般就是使用json相關(guān)的工具。
* addCallAdapterFactory,是為了返回Call類型之外的其它類型
*/
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(FastJsonConverterFactory.create())
.build();
api = retrofit.create(ApiService.class);
- 發(fā)起請(qǐng)求
??一般用的比較多的是異步請(qǐng)求,當(dāng)然也可以使用同步請(qǐng)求。在Callback回調(diào)里面可以進(jìn)行成功及失敗,回調(diào)都是在主線程里面(在Android上使用,底層是通過Handler post到主線程)。
Call<JsonResponse> call = apiService.getApprovalInfo(getApprovalInfoBean);
requestList.add(call);
call.enqueue(new Callback<JsonResponse>() {
@Override
public void onResponse(Call<JsonResponse> call, Response<JsonResponse> response) {
}
@Override
public void onFailure(Call<JsonResponse> call, Throwable t) {
}
});
至此基本的用法就大概說完了,當(dāng)然還有很多其它的用法,具體可以參考下官網(wǎng)?;旧螲ttp的方法都支持。
三、源碼分析
-
源碼的包結(jié)構(gòu)
image.png
http包:
image.png
http包里面放的都是一些自定義的注解類,平常開發(fā)者會(huì)用到的是:Call、CallAdapter、Callback、Converter、Retrofit
- 主要的幾個(gè)類之間的關(guān)系

- Retrofit是如何把一個(gè)接口方法轉(zhuǎn)變?yōu)閔ttp請(qǐng)求的嗎
Retrofit里面用到了大量的反射、泛型、注解,還用到了動(dòng)態(tài)代理(這是將接口方法轉(zhuǎn)換為http請(qǐng)求的關(guān)鍵,畢竟我們沒有看到 Call<JsonResponse> getAgreement();方法的實(shí)現(xiàn))。
可能自己之前寫的代碼比較簡(jiǎn)單,考慮的方面也比較單一,反射、注解之類的東西用的也比較少。拋開性能不說(反射會(huì)降低性能),這些功能(或者說是語(yǔ)法吧)能夠?qū)崿F(xiàn)一些“比較特殊”的功能,還有注解、泛型這些可以給讓代碼變得更加優(yōu)雅,增加代碼的可擴(kuò)展性和動(dòng)態(tài)性。感謝開源,讓我們能夠讀到大神的源碼。
??關(guān)鍵點(diǎn)是這里,使用了java的動(dòng)態(tài)代理,其實(shí)上面我們定義的接口方法getAgreement()真正的執(zhí)行地方是下面invoke()方法里面,通過反射調(diào)用了。
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);
}
});
真正的方法調(diào)用:
return method.invoke(this, args);
return platform.invokeDefaultMethod(method, service, proxy, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
這里可以看到有三個(gè)return的地方,第一種情況是:
這里又有一個(gè)關(guān)鍵的類:ServiceMethod,關(guān)于ServiceMethod,請(qǐng)看下文的“4”。
- ServiceMethod分析(關(guān)鍵銜接點(diǎn))
ServiceMethod就是具體來做相關(guān)的方法參數(shù)的解析(解析方法上面的注解)及解析方法的返回值。解析注解里面的值(http的參數(shù))其實(shí)是通過正則表達(dá)式匹配來完成的。
static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);
ServiceMethod仍然采用了Builder模式(Retrofit源碼里面可以看到大量使用Builder,對(duì)于有許多需要配置的參數(shù)的情況,這樣寫起來更優(yōu)雅一點(diǎn))。
最終的請(qǐng)求是由toRequest方法來完成:
/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart);
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
int argumentCount = args != null ? args.length : 0;
if (argumentCount != handlers.length) {
throw new IllegalArgumentException("Argument count (" + argumentCount
+ ") doesn't match expected count (" + handlers.length + ")");
}
for (int p = 0; p < argumentCount; p++) {
handlers[p].apply(requestBuilder, args[p]);
}
return requestBuilder.build();
}
返回的requestBuilder.build()產(chǎn)生了Request的實(shí)例,而Request是 OkHttp里面的類。由此可見,Retrofit只是封裝了http的請(qǐng)求,最終的請(qǐng)求還是通過OkHttp來完成的。
- Call接口
可用于異步也可用于同步請(qǐng)求,異步的話就放到隊(duì)列里,調(diào)用enqueue()方法;同步的話調(diào)用execute()方法,直接返回一個(gè)Response<T>對(duì)象。
- Callback接口
Call的實(shí)例調(diào)用enqueue()方法的參數(shù),用于接收回調(diào),包含兩個(gè)回調(diào)方法:
- http層面上的正常響應(yīng),即網(wǎng)絡(luò)正常
/**
* Invoked for a received HTTP response.
* <p>
* Note: An HTTP response may still indicate an application-level failure such as a 404 or 500.
* Call {@link Response#isSuccessful()} to determine if the response indicates success.
*/
void onResponse(Call<T> call, Response<T> response);
- http層面上的異常響應(yīng)
/**
* Invoked when a network exception occurred talking to the server or when an unexpected
* exception occurred creating the request or processing the response.
*/
void onFailure(Call<T> call, Throwable t);
一般在正常響應(yīng)里面需要判斷下應(yīng)用層面的情況,如:200、404等;判斷完應(yīng)用層面后需要再判斷下自定義的情況,一般:刷新成功、刷新失敗等。所以可以寫一個(gè)抽象類實(shí)現(xiàn)Callback接口,在里面做一些自已的處理,避免寫太多重復(fù)代碼。
- Converter<F, T>接口
將F類型轉(zhuǎn)換為T類型,主要用在轉(zhuǎn)成json對(duì)象,如:addConverterFactory(FastJsonConverterFactory.create())。
包含了一個(gè)方法和一個(gè)抽象工廠類:
T convert(F value) throws IOException;
abstract class Factory
抽象工廠類主要有三種轉(zhuǎn)換器方法:
public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
Annotation[] annotations, Retrofit retrofit) {
return null;
}
public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
分別是將ResponseBody轉(zhuǎn)成自己想要的類型,將自定義類型轉(zhuǎn)為RequestBody,將http的參數(shù)轉(zhuǎn)為字符串類型
跟這個(gè)相關(guān)的有一個(gè)自帶的類:
final class BuiltInConverters extends Converter.Factory
- 重寫了responseBodyConverter和requestBodyConverter這兩個(gè)方法
- CallAdapter<R, T> 接口
這個(gè)接口常見的好像主要是為了配合Rxjava使用,因?yàn)檫€沒有用過Rxjava,所以等后面用了再作更深入的分析。
??主要用于將響應(yīng)的類型R轉(zhuǎn)換為自己想要的類型,用的比較多的是將響應(yīng)轉(zhuǎn)換為rxjava支持,會(huì)用到RxJava2CallAdapterFactory.create()。
在Retrofit.Builder#addCallAdapterFactory(Factory)里調(diào)用 ,一般在初始化里面。
包含了兩個(gè)方法和一個(gè)內(nèi)部的抽象類
Type responseType();
T adapt(Call<R> call);
abstract class Factory
抽象工廠方法里面包含了一個(gè)get()方法,返回一個(gè)CallAdapter類型;包含兩個(gè)返回類型的方法:
/**
* Extract the upper bound of the generic parameter at {@code index} from {@code type}. For
* example, index 1 of {@code Map<String, ? extends Runnable>} returns {@code Runnable}.
*/
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
/**
* Extract the raw class type from {@code type}. For example, the type representing
* {@code List<? extends Runnable>} returns {@code List.class}.
*/
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
- 關(guān)于http包里的自定義注解類
這里定義注解是為了后面可以解析注解得到http請(qǐng)求對(duì)應(yīng)的方法及各種參數(shù)
- 方法注解:GET、POST、PUT、DELETE、PATCH、HTTP、HEAD、FormUrlEncoded、Headers、Multipart、OPTIONS、Streaming
- 參數(shù)注解:Body、Field、FieldMap、HeaderMap、Part、PartMap、Path、Query、QueryMap、Url
- Platform類(判斷是屬于哪個(gè)平臺(tái))
Retrofit的介紹說的就是可以在普通的java中使用,也可以在Android中使用,所以里面有一段代碼用來判斷當(dāng)前代碼是運(yùn)行在哪個(gè)平臺(tái)上。
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) {
}
try {
Class.forName("org.robovm.apple.foundation.NSObject");
return new IOS();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
其實(shí)就是通過查找有沒有平臺(tái)相關(guān)的類來判斷具體是哪個(gè)平臺(tái),像在Android上就找“android.os.Build”;普通java的話,就通過找“java.util.Optional”來判斷;但是iOS查找“"org.robovm.apple.foundation.NSObject"”我就不是很明白了,iOS上也可以運(yùn)行嗎?
??這個(gè)小技巧感覺可以學(xué)習(xí)一下。
四、總結(jié)
之前使用Retrofit的時(shí)候也只是參照官網(wǎng)的例子簡(jiǎn)單使用了一下,并沒有去深究它內(nèi)部的實(shí)現(xiàn),最近在看自己寫的代碼的時(shí)候,發(fā)現(xiàn)每一次http請(qǐng)求都需要在回調(diào)里面做好多判斷,如:在onResponse()里面要判斷http的狀態(tài)碼,進(jìn)一步又要判斷自定義的狀態(tài),感覺好臃腫,于是就想到用抽象類來解決。最近剛好有點(diǎn)時(shí)間,就閱讀了一下源碼,發(fā)現(xiàn)收獲還是挺多的。
- 通過注解的方式來完成http請(qǐng)求,對(duì)于我來說還算是比較新的。
- 給方法、參數(shù)加上自定義注解,來增加一些編譯期和運(yùn)行時(shí)的行為。
- 目前做項(xiàng)目的時(shí)候拋出異常的情況還比較少,而如果是設(shè)計(jì)一個(gè)庫(kù)供他人使用,可能就得像Retrofit一樣拋出一些異常,做一些參數(shù)檢查,來增加代碼的健壯性。
- 使用工廠模式隱藏創(chuàng)建過程的復(fù)雜度,
- 使用Builder模式來避免構(gòu)造方法里面有一堆的參數(shù)。
- 使用泛型來增加代碼的可擴(kuò)展性,避免寫一堆的樣板代碼。
第一次寫博客,以前都是在有道云筆記里面記東西,雖然也記了挺多,但是都比較隨意。這一次會(huì)想寫有兩個(gè)原因:第一、應(yīng)工作室同學(xué)的邀請(qǐng);第二、學(xué)Android也有一段時(shí)間了,也用了不少開源庫(kù),看過一部分源碼,感覺需要好好總結(jié)一下,提升一下自己的技術(shù)水平。以前總是想著說自己的水平還不夠高,還沒到寫博客的時(shí)候,但是現(xiàn)在想想,其實(shí)也沒那么絕對(duì),寫出來是對(duì)自己的一個(gè)階段性總結(jié),對(duì)學(xué)過的東西的一個(gè)鞏固,一開始可能寫的不好,但寫多了,慢慢總會(huì)有進(jìn)步。之前看到過一句話“沒看過優(yōu)秀的源碼,是不太容易寫好優(yōu)秀的代碼的?!蔽覍?duì)此還是比較認(rèn)同的,開源的世界帶來了如此豐富的資源,需要我們好好利用。
??寫了一些代碼,也看過一些代碼,感覺看源碼就像在看書一樣,也有結(jié)構(gòu)、語(yǔ)法、先后順序,都是要表達(dá)些什么。只不過文字和源代碼是從不同方面來理解世界,從不同角度來構(gòu)筑這個(gè)世界。我越發(fā)地相信,這個(gè)世界有很多東西是相通的,是有聯(lián)系的。

