本文針對的是Retrofit 2.1.0
一、Retrofit簡介
如今開發一個Android應用,幾乎都需要和網絡打交道,最常見的就是通過http協議來請求服務器數據或者是給服務器發送數據,雖然Android本身也提供了HttpUrlConnection和Volley來發起http請求,功能上其實也能滿足,但鑒于Retrofit+OkHttp的組合如此流行,所以幾個項目下來,都是用的這一套。
??Retrofit是一個針對Android和java的類型安全的、開源的http客戶端,由大名鼎鼎的Square公司出品(另外還有:picasso、dagger等),通過注解的方式,讓開發者定義一個接口方法來完成http請求(真正的請求其實是由OkHttp來完成的),至于這個過程是怎么實現的,后面會詳細說。
相關鏈接:
二、使用
簡單介紹下Retrofit的使用
- 定義一個接口:ApiService
public interface ApiService {
/**
* 獲取協議內容
*
* @return 此時返回的JsonResponse里面的body字段的類型為 AgreementResponseBody
*/
@GET("system/getAgreement")
Call<JsonResponse> getAgreement();
/**
* 獲取點贊信息
* @param getApprovalInfoBean
* @return
*/
@POST("approval/getApproval")
Call<JsonResponse> getApprovalInfo(@Body GetApprovalInfoBean getApprovalInfoBean);
}
上面就定義了兩個最常用的http的方法,GET請求和POST請求,可以看到,需要使用GET或者是POST只需要在方法上加上注解就可以(當然還有其它方法,像:DELETE等)。POST方法的話因為有請求體,所以會用@Body來注解,這里放的是我自定義的一個類(因為結合了fastjson使用)。兩個方法的返回都是Call<T>的形式。
- Retrofit的相關配置及初始化
??Retrofit使用了Builder模式,主要是做了一些配置,可以看到,實例化了一個OkHttpClient,因為要用它來進行實際的http請求。
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.readTimeout(10, TimeUnit.SECONDS);
builder.connectTimeout(9, TimeUnit.SECONDS);
//添加攔截器,保留一些調試時的日志
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
}
OkHttpClient okHttpClient = builder.build();
/**
* addConverterFactory,是為了對象的序列化和反序列化,一般就是使用json相關的工具。
* addCallAdapterFactory,是為了返回Call類型之外的其它類型
*/
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(FastJsonConverterFactory.create())
.build();
api = retrofit.create(ApiService.class);
- 發起請求
??一般用的比較多的是異步請求,當然也可以使用同步請求。在Callback回調里面可以進行成功及失敗,回調都是在主線程里面(在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) {
}
});
至此基本的用法就大概說完了,當然還有很多其它的用法,具體可以參考下官網。基本上Http的方法都支持。
三、源碼分析
-
源碼的包結構
image.png
http包:
image.png
http包里面放的都是一些自定義的注解類,平常開發者會用到的是:Call、CallAdapter、Callback、Converter、Retrofit
- 主要的幾個類之間的關系
- Retrofit是如何把一個接口方法轉變為http請求的嗎
Retrofit里面用到了大量的反射、泛型、注解,還用到了動態代理(這是將接口方法轉換為http請求的關鍵,畢竟我們沒有看到 Call<JsonResponse> getAgreement();方法的實現)。
可能自己之前寫的代碼比較簡單,考慮的方面也比較單一,反射、注解之類的東西用的也比較少。拋開性能不說(反射會降低性能),這些功能(或者說是語法吧)能夠實現一些“比較特殊”的功能,還有注解、泛型這些可以給讓代碼變得更加優雅,增加代碼的可擴展性和動態性。感謝開源,讓我們能夠讀到大神的源碼。
??關鍵點是這里,使用了java的動態代理,其實上面我們定義的接口方法getAgreement()真正的執行地方是下面invoke()方法里面,通過反射調用了。
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);
}
});
真正的方法調用:
return method.invoke(this, args);
return platform.invokeDefaultMethod(method, service, proxy, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
這里可以看到有三個return的地方,第一種情況是:
這里又有一個關鍵的類:ServiceMethod,關于ServiceMethod,請看下文的“4”。
- ServiceMethod分析(關鍵銜接點)
ServiceMethod就是具體來做相關的方法參數的解析(解析方法上面的注解)及解析方法的返回值。解析注解里面的值(http的參數)其實是通過正則表達式匹配來完成的。
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,對于有許多需要配置的參數的情況,這樣寫起來更優雅一點)。
最終的請求是由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()產生了Request的實例,而Request是 OkHttp里面的類。由此可見,Retrofit只是封裝了http的請求,最終的請求還是通過OkHttp來完成的。
- Call接口
可用于異步也可用于同步請求,異步的話就放到隊列里,調用enqueue()方法;同步的話調用execute()方法,直接返回一個Response<T>對象。
- Callback接口
Call的實例調用enqueue()方法的參數,用于接收回調,包含兩個回調方法:
- http層面上的正常響應,即網絡正常
/**
* 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層面上的異常響應
/**
* 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);
一般在正常響應里面需要判斷下應用層面的情況,如:200、404等;判斷完應用層面后需要再判斷下自定義的情況,一般:刷新成功、刷新失敗等。所以可以寫一個抽象類實現Callback接口,在里面做一些自已的處理,避免寫太多重復代碼。
- Converter<F, T>接口
將F類型轉換為T類型,主要用在轉成json對象,如:addConverterFactory(FastJsonConverterFactory.create())。
包含了一個方法和一個抽象工廠類:
T convert(F value) throws IOException;
abstract class Factory
抽象工廠類主要有三種轉換器方法:
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轉成自己想要的類型,將自定義類型轉為RequestBody,將http的參數轉為字符串類型
跟這個相關的有一個自帶的類:
final class BuiltInConverters extends Converter.Factory
- 重寫了responseBodyConverter和requestBodyConverter這兩個方法
- CallAdapter<R, T> 接口
這個接口常見的好像主要是為了配合Rxjava使用,因為還沒有用過Rxjava,所以等后面用了再作更深入的分析。
??主要用于將響應的類型R轉換為自己想要的類型,用的比較多的是將響應轉換為rxjava支持,會用到RxJava2CallAdapterFactory.create()。
在Retrofit.Builder#addCallAdapterFactory(Factory)里調用 ,一般在初始化里面。
包含了兩個方法和一個內部的抽象類
Type responseType();
T adapt(Call<R> call);
abstract class Factory
抽象工廠方法里面包含了一個get()方法,返回一個CallAdapter類型;包含兩個返回類型的方法:
/**
* 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);
}
- 關于http包里的自定義注解類
這里定義注解是為了后面可以解析注解得到http請求對應的方法及各種參數
- 方法注解:GET、POST、PUT、DELETE、PATCH、HTTP、HEAD、FormUrlEncoded、Headers、Multipart、OPTIONS、Streaming
- 參數注解:Body、Field、FieldMap、HeaderMap、Part、PartMap、Path、Query、QueryMap、Url
- Platform類(判斷是屬于哪個平臺)
Retrofit的介紹說的就是可以在普通的java中使用,也可以在Android中使用,所以里面有一段代碼用來判斷當前代碼是運行在哪個平臺上。
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();
}
其實就是通過查找有沒有平臺相關的類來判斷具體是哪個平臺,像在Android上就找“android.os.Build”;普通java的話,就通過找“java.util.Optional”來判斷;但是iOS查找“"org.robovm.apple.foundation.NSObject"”我就不是很明白了,iOS上也可以運行嗎?
??這個小技巧感覺可以學習一下。
四、總結
之前使用Retrofit的時候也只是參照官網的例子簡單使用了一下,并沒有去深究它內部的實現,最近在看自己寫的代碼的時候,發現每一次http請求都需要在回調里面做好多判斷,如:在onResponse()里面要判斷http的狀態碼,進一步又要判斷自定義的狀態,感覺好臃腫,于是就想到用抽象類來解決。最近剛好有點時間,就閱讀了一下源碼,發現收獲還是挺多的。
- 通過注解的方式來完成http請求,對于我來說還算是比較新的。
- 給方法、參數加上自定義注解,來增加一些編譯期和運行時的行為。
- 目前做項目的時候拋出異常的情況還比較少,而如果是設計一個庫供他人使用,可能就得像Retrofit一樣拋出一些異常,做一些參數檢查,來增加代碼的健壯性。
- 使用工廠模式隱藏創建過程的復雜度,
- 使用Builder模式來避免構造方法里面有一堆的參數。
- 使用泛型來增加代碼的可擴展性,避免寫一堆的樣板代碼。
第一次寫博客,以前都是在有道云筆記里面記東西,雖然也記了挺多,但是都比較隨意。這一次會想寫有兩個原因:第一、應工作室同學的邀請;第二、學Android也有一段時間了,也用了不少開源庫,看過一部分源碼,感覺需要好好總結一下,提升一下自己的技術水平。以前總是想著說自己的水平還不夠高,還沒到寫博客的時候,但是現在想想,其實也沒那么絕對,寫出來是對自己的一個階段性總結,對學過的東西的一個鞏固,一開始可能寫的不好,但寫多了,慢慢總會有進步。之前看到過一句話“沒看過優秀的源碼,是不太容易寫好優秀的代碼的。”我對此還是比較認同的,開源的世界帶來了如此豐富的資源,需要我們好好利用。
??寫了一些代碼,也看過一些代碼,感覺看源碼就像在看書一樣,也有結構、語法、先后順序,都是要表達些什么。只不過文字和源代碼是從不同方面來理解世界,從不同角度來構筑這個世界。我越發地相信,這個世界有很多東西是相通的,是有聯系的。