2017-10-6(Retrofit使用範例的原始碼分析 )

wyman_1007發表於2018-01-23

講到Retrofit先聊一下什麼是rest風格的url,為什麼說Retrofit十分適合rest風格。

以前客戶端傳送請求給服務端 一般請求的url都是這樣子的:

//get請求
www.baidu.com/search?id=1&name='abc'
//post請求
www.baidu.com/update
//post請求體
id=1
name='abc'
複製程式碼

然而你會發現我們的請求不是post就是get;一般把請求的操作(即增刪改查)都放在url上,請求的方式(即post或get)是固定的。這樣我們的客戶端的http請求都會把請求方式封裝成post或get,一般網路請求框架都是這樣。

rest風格:是把資源請求(即url)與操作方式(post請求或get或put或delete)分開。

//rest風格url
www.baidu.com/rest/user/1
//上面請求方式通過get請求,獲取user id為1的客戶資訊。get請求代表是查詢 而url僅僅是一種資源請求。把資源請求與操作分開。
//刪除操作
www.baidu.com/rest/user/1
//這樣上面的url與之前的查詢user id為1的url一樣,但這次是用delete請求,這樣更直觀說明rest風格的核心就是資源請求與操作分開。
複製程式碼

那為什麼Retrofit適合rest風格呢?主要是因為它的註解方式。post或get或delete都可以通過註釋的方式操作。不同以往的框架耦合在工具類上。

下面說一下Retrofit使用中的原始碼分析

//示例程式碼
//第一步,建立一個介面 ApiService就是該介面
//第二步,建立retrofit物件
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("")
//使用的網路請求 OkHttpClient;這個可以新增個過濾,超時
.client(okHttpClient)
//返回資料轉換處理
.addConverterFactory(GsonConverterFactory.create())
.build();
//第三步,建立一個實現了介面的代理物件;這裡retrofit.create(ApiService.class);其實就是動態代理,生成代理物件
ApiService apiService = retrofit.create(ApiService.class);
//第四步,呼叫介面方法,返回一個Call物件(這裡不一定是Call物件可以是RxJava的Observer)。生成一個OKHttpCall的代理物件,其實返回什麼主要看ApiService介面定義的返回值
Call<ResponseBody> call = apiService.getMovie(1,1);
//第五步,呼叫execute執行同步請求。 返回結果
retrofit2.Response<ResponseBody> response = call.execute();
//第六步,從響應獲取資料。列印資料
System.out.println(response.body().string());
複製程式碼

一般就是這麼個步驟,下面來逐一分析每步Retrofit幹什麼的。

第一步定義介面

這一步其實沒有什麼好講的,就是像代理模式一樣定義一個介面,介面寫上一些註解,為Retrofit之後的獲取各個引數做的。

@POST("top250")
Call<ResponseBody> getMovie(@Query("start")int start, @Query("count")int count);
複製程式碼

第二步,建立retrofit物件

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("")
//使用的網路請求 OkHttpClient;這個可以新增個過濾,超時
.client(okHttpClient)
//返回資料轉換處理
.addConverterFactory(GsonConverterFactory.create())
.build();
複製程式碼

我們一句句看看原始碼:

Retrofit retrofit = new Retrofit.Builder()

獲取平臺
這個說在前面就是判斷是什麼平臺的java或Android或IOS;繼續往下看:

Paste_Image.png

Paste_Image.png
通過反射判斷該類物件是否存在。看看new Android()幹什麼的。

Paste_Image.png

就這麼點程式碼;在這階段還沒有操作什麼之後那幾個方法就會有用處了。繼續看下一句程式碼:

.baseUrl("")
這個很簡單就是獲取基本的url然後封裝成okhttp的httpUrl

![PasteImage.png](http://upload-images.jianshu.io/uploadimages/1717635-5b87eceddb8554fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/

.client(okHttpClient)
就是設定okhttpClient

Paste_Image.png
這裡只是檢測非空。

.addConverterFactory(GsonConverterFactory.create())
這裡新增預設的返參轉化,當然可以自己實現一個。

Paste_Image.png
converterFactories是一個list集合用於存放這些轉換器。其實就是使用第一個。

.build();
所有設定完成就是build()構建retrofit物件。

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);
}
}
複製程式碼

程式碼不多也容易理解:

  • 首先檢測baseUrl是否為空;
  • 看看是否有傳入okhttp3.Call.Factory,如果沒有就new OkHttpClient();預設的
  • 傳入Executor ,這個我們之前的程式碼是沒有就通過
    platform.defaultCallbackExecutor();獲取一個。
    看看程式碼:

Paste_Image.png

Paste_Image.png
事實上就是獲取主執行緒的Handler。Platform這個類的作用基本上就是判斷平臺,二獲取Handler,為了之後http請求後在主執行緒處理結果。

  • 建立兩個list集合分別存放Call物件的和轉換器的

Paste_Image.png

  • 最後就是構建Retrofit物件

Paste_Image.png

第二步,構建代理物件;這裡不瞭解java的動態代理可以看看我寫的這篇文章:動態代理

ApiService apiService = retrofit.create(ApiService.class);
//呼叫介面方法,返回一個Call物件(這裡不一定是Call物件可以是RxJava的Observer)。生成一個OKHttpCall的代理物件,其實返回什麼主要看ApiService介面定義的返回值
Call call = apiService.getMovie(1,1);

其實我這裡覺得它是沒有使用到代理模式的,只是使用了Java中的Proxy類和InvocationHandler介面來實現它對我們自定義那些介面獲取相應的引數封裝成一個Okhttp的Request請求來傳送http請求而已。因為作為代理模式,它並沒有委託物件,只是有一個介面而已。具體做什麼其實Retrofit也很清楚,我們自定義介面只是為了因應不同請求作出不同的引數變化而已。不知道我這樣理解對不對。
言歸正傳看看第三步幹了什麼

public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(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, 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);
}
});
}
複製程式碼

第三步,就是呼叫介面方法,而呼叫介面方法都會呼叫到InvocationHandler介面的invoke方法。

其實就關注invoke方法。剛才也說了Retrofit通過Proxy和InvocationHandler實現我們自定義介面的物件,而物件呼叫任何一個方法都會呼叫invoke方法;

Paste_Image.png
主要關注這三行程式碼,其他都是校驗語句;

ServiceMethod serviceMethod = loadServiceMethod(method);

Paste_Image.png

這裡程式碼大致意思就是有ServiceMethod物件就從快取取,沒有就構建一個。我們看看構建的程式碼。

result = new ServiceMethod.Builder(this, method).build();

new ServiceMethod.Builder(this, method)

這段程式碼很明顯就是從我們自定義的介面方法中獲取各種Retrofit需要的引數:

  • method的註解(如POST或GET)
  • method的傳參的型別
  • method傳參的註解
    再看看build
public ServiceMethod build() {
callAdapter = createCallAdapter();
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();
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (isMultipart) {
throw methodError(
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
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.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
if (relativeUrl == null && !gotUrl) {
throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError("Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError("Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError("Multipart method must contain at least one @Part.");
}
return new ServiceMethod<>(this);
}
複製程式碼

callAdapter = createCallAdapter();

獲取介面方法的返回型別:Type returnType = method.getGenericReturnType();
獲取方法的註解
Annotation[] annotations = method.getAnnotations();
retrofit.callAdapter(returnType, annotations);這個其實就是一個Call物件
其實一直看下去就會發現呼叫了Retrofit的一個叫nextCallAdapter方法

Paste_Image.png
這裡其實就是獲取最後一個,因為第一個是預設的,而自定義的會放在最後。
再看看之前的程式碼:

Paste_Image.png

Paste_Image.png
這樣應該很清楚:

我們的CallAdapter就是ExecutorCallAdapterFactory,因為我們的示例程式碼必沒有新增addCallAdapterFactory,如果與RxJava一併使用就需要新增builder.addCallAdapterFactory(RxJavaCallAdapterFactory.create());

而回撥方法就是主執行緒Handler的Runnable;
ExecutorCallAdapterFactory這個類的方法不多,到時候再看看它長成什麼樣。

剛看完createCallAdapter();現在繼續往下看:
其實後面的方法就是對我們自定義介面裡面方法的引數作解釋繼而組裝成一個http請求

OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
這個就是通過serviceMethod和我們方法的引數生成一個okhttpCall

serviceMethod.callAdapter.adapt(okHttpCall);

由於callAdapter是一個抽象類,而實現它的類是一個匿名內部類:

  • 在建立ServiceMethod物件時,呼叫createCallAdapter();方法;
    這個方法呼叫了retrofit的nextCallAdapter()方法;

createCallAdapter()

Paste_Image.png

nextCallAdapter()

Paste_Image.png
上面已經說了這裡的CallAdapter實現類是ExecutorCallAdapterFactory
那麼看看ExecutorCallAdapterFactory類的get方法:

Paste_Image.png
ExecutorCallAdapterFactory類的get方法最終返回一個匿名的CallAdapter。
而invoke方法最終返回serviceMethod.callAdapter.adapt(okHttpCall);即截圖的箭頭:new ExecutorCallbackCall<>(callbackExecutor, call);
其實這個是ExecutorCallAdapterFactory的內部類同事也是一個Call;

Call call = apiService.getMovie(1,1);

那麼第四步其實就是返回一個Call物件,即示例程式碼返回一個ExecutorCallbackCall物件。

retrofit2.Response response = call.execute();

第五步,執行同步,當然也有非同步方法enqueue

在示例程式碼中其實就是呼叫ExecutorCallbackCall類實現Call介面的execute()或enqueue()方法。
而還有一處就是callbackExecutor.execute(new Runnable() {其實就是執行Platform的execute,即將回撥放回主執行緒的Handler處理:

Paste_Image.png

這裡其實不管是呼叫execute()或enqueue()方法都是呼叫delegate的:

Paste_Image.png

Paste_Image.png
而delegate其實就是OkHttpCall。

Paste_Image.png

最後再看看OkHttpCall:這裡只看enqueue吧,都是大同小異

@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;
}
}
}
if (failure != null) {
callback.onFailure(this, failure);
return;
}
if (canceled) {
call.cancel();
}
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
throws IOException
{
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
callFailure(e);
return;
}
callSuccess(response);
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callSuccess(Response<T> response) {
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
複製程式碼

其實這裡基本上已經不是Retrofit範疇了,Retrofit已經將http請求交由okhttp處理了。
這裡的Call已經是Okhttp的Call了。

Paste_Image.png

Paste_Image.png
serviceMethod.toRequest(args);就是將我們自定義的method引數給serviceMethod組裝Request物件;
serviceMethod.callFactory.newCall(request);就是通過OkHttpClient物件建立okhttp3.Call物件;

Paste_Image.png

後面的程式碼就是Okhttp的Call執行然後將結果給回撥的CallBack物件

Paste_Image.png

現在歸納一下Retrofit幾個重要的類和它們的作用:

  • Platform:用於判斷平臺,Android中獲取主執行緒的Handler,然回撥結果給主執行緒處理;
  • Retrofit : 收集baseUrl;OkHttpClient;ConverterFactory;CallAdapter.Factory;
    建立代理物件;
  • ServiceMethod:將自定義介面方法的註解,引數,返參封裝起來形成Request物件
  • ExecutorCallAdapterFactory或其他CallAdapterFactory負責耦合OkHttpCall和Platform實現回撥和最終okhttp中Call物件傳送請求
  • OkHttpCall 實現okhttp中Call物件傳送請求和將結果傳回給CallBack
  • OkHttpClient 這裡的示例程式碼作用只是生成Okhttp的Call物件,其實在自定義OkHttpClient中它的作用還是有很多的,如過濾,日誌;

Retrofit核心的幾個類基本上就這些了。如果本文有哪裡有誤希望指出,大家共同學習。本人是一名菜鳥,希望通過閱讀原始碼另自己的程式設計能力上一個臺階。

補充UML關係圖

RetrofitUML.png

相關文章