Android小知識-剖析Retrofit中網路請求的兩種方式
本平臺的文章更新會有延遲,大家可以關注微信公眾號-顧林海,包括年底前會更新kotlin由淺入深系列教程,目前計劃在微信公眾號進行首發,如果大家想獲取最新教程,請關注微信公眾號,謝謝!
在上一節《Android小知識-剖析Retrofit中ServiceMethod相關引數以及建立過程》介紹了動態代理類中三行核心程式碼的第一行,通過loadServiceMethod方法獲取ServiceMethod物件,在loadServiceMethod方法中先會檢查快取集合中是否有對應網路請求介面方法的ServiceMethod物件,如果不存在就通過Builder模式建立,同時介紹了ServiceMethod內部的一些成員變數,其實ServiceMethod就是對網路請求介面內部一個個方法的封裝,通過解析方法內部或方法上的註解來封裝ServiceMethod物件。這節來介紹三行核心程式碼的剩餘兩行。
public <T> T create(final Class<T> 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 {
...
//核心程式碼1
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
//核心程式碼2
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
//核心程式碼3
return serviceMethod.adapt(okHttpCall);
}
});
}
複製程式碼
在核心程式碼2處建立OkHttpCall,OkHttpCall是Call的實現類,在Retrofit中內部是通過OkHttp來進行網路的請求,這個OkHttpCall就是對OkHttp請求的封裝。
final class OkHttpCall<T> implements Call<T> {
....
private @Nullable okhttp3.Call rawCall;
OkHttpCall(ServiceMethod<T, ?> serviceMethod, @Nullable Object[] args) {
this.serviceMethod = serviceMethod;
this.args = args;
}
}
複製程式碼
在OkHttpCall中可以看到rawCall,它是OkHttp的Call,這也驗證之前所說的內部會通過OkHttp來實現網路請求,OkHttpCall建構函式傳入兩個引數,serviceMethod物件和args網路請求引數,接著看核心程式碼3。
return serviceMethod.adapt(okHttpCall);
複製程式碼
serviceMethod的adapt方法中會呼叫callAdatper的adapter方法,通過介面卡的adapt方法來將OkHttpCall轉換成其他平臺使用的物件,這個callAdapter是在建立serviceMethod時通過構建者模式建立的,它代表網路請求的介面卡,這裡使用的RxJava平臺。
回到一開始的例項程式碼:
private void initRetrofit() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://icould.glh/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
NetworkInterface networkInterface = retrofit.create(NetworkInterface.class);
Map<String, String> params = new HashMap<>();
params.put("newsId", "1");
params.put("token", "yud133f");
Call call = networkInterface.getNewsDetails(params);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
System.out.println(response.body());
}
@Override
public void onFailure(Call call, Throwable t) {
System.out.println("請求錯誤");
}
});
}
複製程式碼
通過networkInterface介面呼叫getNewsDetails是不行的,因此在Retrofit的create獲取網路介面的動態代理,在執行networkInterface的getNewDetails方法時,通過動態代理攔截,並執行動態代理物件內InvocationHandler中的invoke方法,將OkHttpCall轉換成RxJava平臺的適用的Call,而這個OkHttpCall物件是對OkHttp網路庫的封裝,最後返回OkHttpCall型別的Call物件,有了這個Call物件就可以進行同步或非同步請求,OkHttpCall內提供了同步請求方法execute和非同步請求方法enqueue,接著下來重點分析這兩個方法。
在Retrofit同步請求流程中,首先需要對網路請求介面中方法以及引數進行解析,通過ParameterHandler進行解析,然後根據ServiceMethod物件建立OkHttp的Request物件,ServiceMethod物件內部包含了網路請求的所有資訊,它是對網路介面方法的封裝,有了Request物件後就可以通過OkHttp這個庫來進行網路請求,最後解析服務端給客戶端返回的資料,通過converter資料轉換器來完成資料的轉換。
OkHttpCall的同步請求execute方法:
@Override public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else if (creationFailure instanceof RuntimeException) {
throw (RuntimeException) creationFailure;
} else {
throw (Error) creationFailure;
}
}
call = rawCall;
if (call == null) {
try {
call = rawCall = createRawCall();
} catch (IOException | RuntimeException | Error e) {
throwIfFatal(e); // Do not assign a fatal error to creationFailure.
creationFailure = e;
throw e;
}
}
}
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());
}
複製程式碼
下面貼出execute區域性程式碼,方便分析。
@Override public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else if (creationFailure instanceof RuntimeException) {
throw (RuntimeException) creationFailure;
} else {
throw (Error) creationFailure;
}
}
...
}
...
}
複製程式碼
上面程式碼中一開始建立了一個OkHttp的call物件,下面是一個同步程式碼塊,通過判斷executed是否執行過通過請求,如果執行過就會丟擲異常,接著判斷creationFailure,不為null時,判斷異常型別並丟擲異常,execute方法的前段部分就是對異常的判斷。
@Override public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
...
call = rawCall;
if (call == null) {
try {
call = rawCall = createRawCall();
} catch (IOException | RuntimeException | Error e) {
throwIfFatal(e); // Do not assign a fatal error to creationFailure.
creationFailure = e;
throw e;
}
}
}
...
}
複製程式碼
當沒有任何異常時,將rawCall也就是OkHttp的原生call賦值給區域性變數call,當call為null時,通過createRawCall方法建立OkHttp的Call物件以及Request。
進入createRawCall方法:
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = serviceMethod.toCall(args);
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
複製程式碼
內部通過serviceMethod的toCall方法將傳入的請求引數轉換成Call物件。
進入serviceMethod的toCall方法:
okhttp3.Call toCall(@Nullable 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;
...
for (int p = 0; p < argumentCount; p++) {
handlers[p].apply(requestBuilder, args[p]);
}
return callFactory.newCall(requestBuilder.build());
}
複製程式碼
RequestBuilder內部儲存著網路請求的相關引數,接著在for迴圈中通過ParameterHandler對引數進行解析,最後通過callFactory的newCall建立OkHttp的Call物件,newCall內部傳入的是Request物件,通過requestBuilder.build()建立Request物件,到這裡將OkHttp的Call物件返回給execute方法內部的成員變數call以及OkHttpCall的成員變數rawCall。
@Override public Response<T> execute() throws IOException {
okhttp3.Call call;
...
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());
}
複製程式碼
有了OkHttp的Call之後,就通過call.execute()進行阻塞式的同步請求,並將返回的Response傳入parseResponse方法中。
進入parseResponse方法:
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
...
try {
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
...
}
}
複製程式碼
只看核心程式碼,通過呼叫serviceMethod的toResponse方法返回body,進入toResponse方法,看它到底做了哪些操作。
R toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
複製程式碼
原來是呼叫了資料轉換器將OkHttp返回的Response轉換成Java物件,這裡我們使用的Gson,也就是通過Gson將伺服器返回的資料轉換成我們需要的Java物件,最後通過Response的success方法將返回的Java物件封裝成Response。
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
...
return Response.success(body, rawResponse);
}
複製程式碼
進入Response的success方法:
public static <T> Response<T> success(@Nullable T body, okhttp3.Response rawResponse) {
...
return new Response<>(rawResponse, body, null);
}
複製程式碼
返回建立好的Response,將body也就是我們的Java物件傳過去。
Response的建構函式:
private Response(okhttp3.Response rawResponse, @Nullable T body,
@Nullable ResponseBody errorBody) {
this.rawResponse = rawResponse;
this.body = body;
this.errorBody = errorBody;
}
複製程式碼
到這裡大家應該很熟悉了,我們利用Retrofit進行網路的同步或非同步請求,最終會返回一個Response物件並通過response.body來獲取結果,這個body就是通過轉換器轉換好的Java物件。
接下來分析非同步請求:
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
System.out.println(response.body());
}
@Override
public void onFailure(Call call, Throwable t) {
System.out.println("請求錯誤");
}
});
複製程式碼
執行非同步請求的流程和同步類似,只不過非同步請求的結果是通過回撥來傳遞的,非同步是通過enqueue方法來執行的,而這個Call的實現類是OkHttpCall,進入OkHttpCall的enqueue方法。
OkHttpCall的enqueue方法(上半部分):
@Override public void enqueue(final Callback<T> callback) {
...
okhttp3.Call call;
...
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) {
throwIfFatal(t);
failure = creationFailure = t;
}
}
}
...
}
複製程式碼
可以發現enqueue的上半部分與上面介紹同步請求時是一樣的,建立OkHttp的Call,並檢查相關異常,如果call為null,就通過createRawCall方法建立OkHttp的Call以及請求所需要的Request。
OkHttpCall的enqueue方法(下半部分):
@Override public void enqueue(final Callback<T> callback) {
...
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
callFailure(e);
return;
}
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
...
});
}
複製程式碼
上面這段程式碼不用我講,大家也應該知道就是通過OkHttp的Call的enqueue方法進行非同步請求,關於OkHttp相關知識可以閱讀之前寫的OkHttp分析的相關係列教程,在OkHttp的Call的enqueue方法的回撥方法onResponse方法中,將返回的Response通過parseResponse方法轉換成Java物件並返回Retrofit的Response物件,通過前面傳入的Callback物件將Response回撥給客戶端。
到這裡關於Retrofit網路請求框架的封裝就講解完畢了!