這個庫一直想要去學習,不過一直沒有時間去做,打算一點點梳理Okhttp。本文將按照發起網絡請求的順序來詳細描述下okhttp是如何發起網絡請求的,其中包含一些優秀文章可供大家參考。
文章目錄
使用實例
先放一段Okhttp的使用代碼。
//1
OkHttpClient client=new OkHttpClient();
//2
Request request = new Request.Builder()
.url("url)
.build();
//3
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String results = response.body().string();
}
});
我們閱讀源碼就要從暴露給用戶使用的那個類先去入手,對應Okhttp就是**(創建)OkhttpClient**,以及Request,Callback,在新寫Callback的時候要重寫onFailure(Call call, IOException e)與OnResponse(Call call, Response response)兩個函數,於是另一個重要的類Response類簡單來說,使用的時候,只需要傳入網絡的url地址,然後加上新創建的Callback對象(包括重寫兩個函數)就完成了對url地址的訪問(獲取數據或上傳數據)。
整體流程
1.創建OkhttpClient實例
在讀源碼之前,每個類都會有一些註釋,先閱讀下注釋。
1.1 用戶使用時,創建全局(單例)實例
這裏的意思就是說,最好在整個應用中只包含一個(全局)OkhttpClient實例,在每一個發起Http請求的時候重用該實例即可(單例),同時複用的還有Response緩存、共用線程池以及共用連接池。
* <h3>OkHttpClients should be shared</h3>
*
* <p>OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
* all of your HTTP calls. This is because each client holds its own connection pool and thread
* pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
* client for each request wastes resources on idle pools.
1.2 構造方式
可以使用兩種方式創建OkHttpClient的實例(構造函數和使用Builder),以及一種特殊的構造方式。
還是先看註釋。
<p>Use {@code new OkHttpClient()} to create a shared instance with the default settings:
* <pre> {@code
*
* // The singleton HTTP client.
* public final OkHttpClient client = new OkHttpClient();
* }</pre>
*
* <p>Or use {@code new OkHttpClient.Builder()} to create a shared instance with custom settings:
* <pre> {@code
*
* // The singleton HTTP client.
* public final OkHttpClient client = new OkHttpClient.Builder()
* .addInterceptor(new HttpLoggingInterceptor())
* .cache(new Cache(cacheDir, cacheSize))
* .build();
* }</pre>
我們來讀源碼。
這是第一種默認的方式
public OkHttpClient() {
this(new Builder());
}
最後會調用該函數,使用的就是默認的Builder
private OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
...
}
第二種方式:想要配置OkhttpClient的一些參數,就需要使用Builder。
public final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor())
.cache(new Cache(cacheDir, cacheSize))
...//其他設置
.build();
最後調用Builder類內的build函數,這裏的this就是Builder,所以會調用OkhttpClient(Builder builder)來更改client的數據域。
public OkHttpClient build() {
return new OkHttpClient(this);
}
總結:OkHttpClient本身不能設置參數,需要藉助於其內部類Builder設置參數,參數設置完成後,調用Builder的build方法得到一個配置好參數的OkHttpClient對象。這些配置的參數會對該OkHttpClient對象所生成的所有HTTP請求都有影響。
第三種方式:我們先看註釋中的文字。
您可以使用{@link#NewBuilder()}自定義共享的OkHttpClient實例。 這將構建一個共享相同連接池,線程池和配置的客戶端。 使用生成器方法爲特定目的配置派生客戶端。
共享相同連接池,線程池和配置的客戶端,共享誰的呢?當然是共享那個client實例的。有時候我們想單獨給某個網絡請求設置特別的幾個參數,比如只想設置某個請求的超時時間,但是還想保持OkHttpClient對象(client)中的其他的參數設置,那麼可以調用OkHttpClient對象的newBuilder()方法。
* <h3>Customize your client with newBuilder()</h3>
*
* <p>You can customize a shared OkHttpClient instance with {@link #newBuilder()}. This builds a
* client that shares the same connection pool, thread pools, and configuration. Use the builder
* methods to configure the derived client for a specific purpose.
*
* <p>This example shows a call with a short 500 millisecond timeout: <pre> {@code
*
* OkHttpClient eagerClient = client.newBuilder()
* .readTimeout(500, TimeUnit.MILLISECONDS)
* .build();
* Response response = eagerClient.newCall(request).execute();
* }</pre>
這裏的this是Okhttp的實例
public Builder newBuilder() {
return new Builder(this);
}
之後會調用Builder類中的這個構造函數,這樣就在builder中保存了OkHttpClient中的參數。
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
this.connectionSpecs = okHttpClient.connectionSpecs;
...
}
1.3 優點
不需要將其殺掉。
保持空閒的線程和連接將自動釋放。
<h3>Shutdown isn't necessary</h3>
1.4 涉及的模式
1.構造者模式
不用說了,都有builder了,肯定就是構造者模式了。
2.單例模式
有點單例模式的意思,但這裏沒有,就是我們構建的OkhttpCilent最好要單例,只構造一個來複用。
3.外觀模式
用戶可以使用這個類來進行一些操作來實現內部的功能。
2.構造Request對象
構造方法
Request也是需要通過構造者模式來構造。
private Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
下面是Builder中的部分代碼,可以看到無參構造方法時Builder會默認method爲get,其實我們可以簡單理解爲就是需要將url傳給Request,以便於它發送網絡請求。也可以看到我們如果想要構建Request對象就一定要構建一個Builder對象,因爲Builder類中的每個設置參數的函數都是返回this,所以可以使用鏈式結構賦值,最後使用build構建時,直接調用new Request(this),這裏的this是Builder對象。
//使用代碼
Request request=new Request.Builder().url(address).build();
//源碼
public static class Builder {
private HttpUrl url;
private String method;
private Headers.Builder headers;
private RequestBody body;
private Object tag;
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
private Builder(Request request) {
this.url = request.url;
this.method = request.method;
this.body = request.body;
this.tag = request.tag;
this.headers = request.headers.newBuilder();
}
public Builder url(HttpUrl url) {
if (url == null) throw new NullPointerException("url == null");
this.url = url;
return this;
}
...
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
get與post
get方式很簡單,因爲我們RequestBuilder的無參構造函數默認將method設置爲GET。
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
而post方式則不同,我們需要藉助RequestBody類
與post方法。因爲post方法會將所上傳的信息封裝在請求數據中併發給服務器,這裏請求數據就是RequestBody,具體代碼如下。
首先要創建一個RequestBody(傳入當前MediaType類型和json數據),同時要調用Builder的post方法,並傳入RequestBody實例。在post方法中會將method置爲post,對body賦值。
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
3.同步模式與異步模式
3.1 簡介
當構建完Request之後,我們就需要構建一個Call,Okhttp中使用異步模式就是使用enqueue
方法,而同步模式就是使用execute
方法。同步方式沒有傳入Callback對象,我們可以通過判斷返回的response的isSuccessfull字段來判斷是否請求成功。
同步方式:
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
異步方式:enqueue()方法的內部已經開好子線程了,在子線程中去執行HTTP請求,並將最終的請求結果回調OKhttp.Callback當中,在調用它的時候要重寫onResponse(),得到服務器返回的具體內容,重寫onFailure(),在這裏對異常情況進行處理。不可以再這裏進行UI操作。如果要進行UI操作一定要通過runOnUiThread(new Runnable(){});
切換回UI線程。
Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(WeatherActivity.this, "獲取天氣信息失敗",
Toast.LENGTH_SHORT).show();
//刷新事件結束,隱藏刷新進度條
swipeRefresh.setRefreshing(false);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String results = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
...
}
});
}
});
3.2 異步方式
3.2.1 newcall()方法
這裏我們是使用異步方式,所以先看異步的代碼。
client.newCall(request).enqueue(callback);
我們回到OkhttpClient,去看newCall方法,發現返回了的其實是 RealCall對象。
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
能看到OkHttpClient是實現了接口Call.Factory,很明顯這裏是工廠模式。將(Call)構建的細節交給實現它的類實現,頂層只需要返回Call對象即可,我們不管是怎麼構建的call。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {...}
interface Factory {
Call newCall(Request request);
}
繼續回到RealCall方法,能看到剛剛調用的構造函數,基本賦值包括OkHttpClient,Request,以及一個boolean變量,這裏默認是給false。
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
涉及到知識盲點了,默認創建一個RetryAndFollowUpInterceptor對象,這個類被叫做過濾器/攔截器。
3.2.2 enqueue()方法
現在Call創建完成(RealCall),接下來將請求加入調度,代碼如下:
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
...
}
@Override
public void onResponse(Call call, Response response) throws IOException {
...
}
});
這裏是調用了Call對象的enqueue方法,但其實是RealCall對象的enqueue方法,繼續回到RealCall類。可以看到這裏使用了synchronized加入了對象鎖,防止多線程同時調用,如果用過就會拋出異常,沒有用過就會將boolean變量賦值爲true。
百度百科:synchronized
synchronized方法後面的括號中是對象,就是對象鎖,如果線程進入,則得到當前對象鎖,那麼別的線程在該類所有對象上的任何操作都不能進行。
之後使用了captureCallStackTrace()方法。
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
captureCallStackTrace()方法中相當於爲RetryAndFollowUpInterceptor對象(攔截器)加入了一個用於追蹤堆棧信息的callStackTrace。
private void captureCallStackTrace() {
Object callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()");
retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);
}
之後使用了 client.dispatcher().enqueue(new AsyncCall(responseCallback));
創建了一個AsyncCall的實例。
看下 AsyncCall類,這裏可以簡單先理解爲把Callback對象先存儲在該類中,之後會敘述該類的作用。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
...
}
之後看下client.dispatcher()方法返回了OkHttpClient類中的Dispatcher對象(調度器)。該Dispatcher對象是在OkHttpClient對象構造時,獲取Bulider對象中的Dispatcher對象。(總結一下:所有OkHttpClient中的數據域對象,全是Builder類中的對象,在Builder類構造函數中被賦值)
public Dispatcher dispatcher() {
return dispatcher;
}
3.2.3 Dispatcher
進入Dispathcer類,首先看該類的成員變量(數據域)。
//最大異步請求數爲64
private int maxRequests = 64;
//每個主機的最大請求數爲5
private int maxRequestsPerHost = 5;
private Runnable idleCallback;
//線程池
/** Executes calls. Created lazily. */
private ExecutorService executorService;
//等待隊列(準備執行的異步請求)
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//(異步)執行隊列(正在執行的異步請求,包含已經取消但未執行完的請求)
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//(同步)執行隊列(正在執行的同步請求,包含已經取消單未執行完的請求)
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
我好菜,連Deque是雙端隊列都不知道。簡單來說雙端隊列就是可以對隊首和隊尾插入和移除元素。這裏稍微補充一下Deque的方法。
看起來與LinkedList有點相似。都是即可作爲棧也可以作爲隊列
之後去看該類的enqueue方法。該方法進行了上鎖,線程獲得了成員鎖,即一次只可以有一個線程訪問該方法,其他線程想要訪問的時候就會被阻塞,需要等到該線程用完纔可以訪問。
synchronized void enqueue(AsyncCall call) {
//判斷正在執行的異步請求數沒有達到閾值,並且該對象需要的主機請求數也沒有達到閾值
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//加入到執行隊列,並立即執行。
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
//加入等待隊列
readyAsyncCalls.add(call);
}
}
runningCallsForHost函數 返回該請求(AsyncCall對象)需要的主機請求數。
/** Returns the number of running calls that share a host with {@code call}. */
private int runningCallsForHost(AsyncCall call) {
int result = 0;
for (AsyncCall c : runningAsyncCalls) {
if (c.host().equals(call.host())) result++;
}
return result;
}
加入到執行隊列後,還有一行代碼。可以理解爲使用線程池創建一個線程去執行該請求(任務)。
這裏可以看到放入線程池中,線程池會創建一個線程去執行該請求,因此前面說的enqueue中會爲任務自己開啓線程去執行。
executorService().execute(call);
executorService()方法是通過懶漢單例模式創建了一個線程池。各個參數的含義如下
- 0(corePoolSize):核心線程的數量爲 0,空閒一段時間後所有線程將全部被銷燬。
- Integer.MAX_VALUE(maximumPoolSize): 最大線程數,當任務進來時可以擴充的線程最大值,相當於無限大。
- 60(keepAliveTime): 非核心線程的處於空閒後的最大存活時間。
- TimeUnit.SECONDS:存活時間的單位是秒。
- new SynchronousQueue():工作隊列,先進先出。
- Util.threadFactory(“OkHttp Dispatcher”, false):單個線程的工廠 ,通過線程工廠創建線程。
簡單來說,在實際運行中,當收到10個併發請求時,線程池會創建十個線程,當工作完成後,線程池會在60s後相繼關閉所有線程。(因爲都是非核心線程)
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
execute()方法可以理解爲將任務添加到線程去執行(當將一個任務添加到線程池中的時候,線程池會爲每個任務創建一個線程,該線程會在之後的某個時刻自動執行。)。
什麼樣的類叫做任務呢。任務就是一個實現了Runnable接口的類。線程就會在接下來自動執行(可以理解爲執行該任務類的run()方法)。
綜上所述,可以總結下enqueue方法。
該方法的核心思想如下:
首先做出判斷:
1執行隊列的大小(隊列中AsyncCall的數量也相當於正在執行的異步請求數)是否小於閾值
2.執行該請求(AsyncCall對象)的主機請求數是否小於主機的最大請求數。
滿足1和2就將該請求(任務)添加到執行隊列,立刻執行任務(將任務添加到線程池之後,線程池會爲任務創建一個線程),否則進入等待隊列。
接下來要看一下我們的從後臺獲取的數據是從哪裏返回的。
3.2.4 AsyncCall
前面說到簡單先理解AsyncCall存儲了我們的Callback對象,而且使用過Okhttp的人都知道,返回數據的操作都是在Callback對象的兩個函數(onFailure()與OnResponse()方法)中。
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String results = response.body().string();
}
});
那麼這兩個方法是在哪裏調用的呢,別跟我說是請求成功和請求失敗的時候調用的。我說的是具體位置好吧。
我們把目光放到AsyncCall類去,AsyncCall是RealCall的內部類。
爲什麼要看該類,因爲線程執行的是該任務,我們去找下他是怎麼實現的Runnable接口,也可以想到數據應該是在run()函數中獲取到的。
final class AsyncCall extends NamedRunnable
可以看到他繼承自NamedRunnable,我們進入該抽象類。沒的說,該類實現了Runnable接口。可以看到run函數中執行的是execute()函數,execute函數是一個抽象的函數。那麼一目瞭然,AsyncCall類繼承該抽象類就一定會實現execute()函數。同時,將一個AsyncCall任務添加到線程池中,線程在執行的就是所傳入任務(AsyncCall實例)的execute()函數。
/**
* Runnable implementation which always sets its thread name.
*/
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
接下來看AsyncCall類的execute()方法。果不其然,首先通過**getResponseWithInterceptorChain()**方法獲得Response,之後根據判斷過濾器retryAndFollowUpInterceptor是否被取消了,來判斷請求成功還是失敗進而回調Callback的onFailure
還是onResponse
。在回調函數中傳遞RealCall與response/IOException。那麼核心就是getResponseWithInterceptorChain()方法了。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
...
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
3.2.5 getResponseWithInterceptorChain
接着看getResponseWithInterceptorChain的代碼,簡單來看,首先創建了一個List用來存儲過濾器,其實在創建Okhttp對象的時候,都幫我們默認實現了這些過濾器。這裏涉及了每個過濾器完成自己的任務,互不耦合。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
//失敗和重定向過濾器
interceptors.add(retryAndFollowUpInterceptor);
//封裝request和response過濾器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//緩存相關的過濾器,負責讀取緩存直接返回、更新緩存
interceptors.add(new CacheInterceptor(client.internalCache()));
//負責和服務器建立連接
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//配置 OkHttpClient 時設置的 networkInterceptors
interceptors.addAll(client.networkInterceptors());
}
//負責向服務器發送請求數據、從服務器讀取響應數據(實際網絡請求)
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
之後執行過濾器,其他可以先不考慮,單單看index,這個index是幹嘛用的呢,我們繼續向下看代碼。
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, Connection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
}
創建RealInterceptorChain後,會調用該對象的proceed方法,直接去看下面的第二個方法。
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !sameConnection(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
首先就有下面的代碼,interceptors是我們之前創建RealInterceptorChain對象時傳入的list。
if (index >= interceptors.size()) throw new AssertionError();
省略兩個判斷,直接到這裏,註釋寫着調用鏈中的下一個攔截器。而且創建了新的RealInterceptorChain也使用了下個的index。根據我的理解就是首先取出當前index的攔截器(interceptors.get(index)),然後將其與下一個連接起來(或者可以理解爲去處理下一個index的攔截器)。這裏就有了遞歸調用的感覺
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
我們去看一下Interceptor的intercept方法。可以看到他繼續調用了RealInterceptorChain的proceed方法,這就是遞歸調用了。最後返回經過所有攔截器之後的Response,這就是getResponseWithInterceptorChain() 方法返回的結果。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
}
...
}
}
這裏應該是使用了責任鏈模式。
總結下getResonseWithInterceptorChain方法()做的事情。
- 創建一個List(interceptors)用來存儲攔截器
- 創建RealInterceptorChain對象,其中傳入的包括interceptors與index(爲0,從第一個開始)
- 調用RealInterceptorChain對象的proceed方法,在該方法中創建新的RealInterceptorChain對象(next),傳入list與index(1)。取出lsit中當前index的攔截器(interceptor),聲明Response對象,調用該攔截器的intercept方法,並傳入新創建的RealInterceptorChain,並將其返回值付給新創建的Response對象。(Response response = interceptor.intercept(next)),在proceed方法的結束會返回Response對象。
- 攔截器的intercept方法會繼續調用傳入的RealInterceptorChain對象的proceed方法,這樣就會遞歸處理攔截器鏈中的下一個攔截器。
- 遞歸結束後會返回一個Response對象。
3.3同步方式
先回顧一下同步方式是如何請求數據的。
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Response response = client.newCall(request).execute();
我們看到了熟悉的身影。
client.newCall(request)
和異步相同都是先創建一個RealCall對象(實現了接口的newCall方法,運用了工廠模式,不管具體怎麼製作,只要一個Call對象。)
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
繼續看execute方法。首先調用了Dispatcher對象的executed方法。
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
Dispatcher的executed方法很簡單,就是將請求加入同步進行隊列中。
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
之後還是通過getResponseWithInterceptorChain()方法獲得Response對象。
到這裏,同步方法和異步方法就全部講解完畢。
4.總結
最後以文字形式總結下總體請求流程,幾乎可以當做面試回答去回答面試官了有木有。
首先通過OkHttpClientBuilder創建OkHttpClient實例,之後通過OkHttpClient對象的newCall方法,(傳入創建好的Request對象),去創建RealCall對象。
異步方式調用RealCall對象的enqueue方法並傳入創建好的Callback對象(創建Callback對象需要重寫其OnResponse方法與OnFailure方法分別對應網絡請求成功與失敗),在該方法下,會繼續調用Dispatcher調度器的enqueue方法並傳入通過Callback對象創建的AsynCall對象,在該方法中會判斷是否立即執行該請求,如果可以就加入異步執行隊列,並使用線程池創建一個線程來執行該請求(AsynCall)對象(會調用AsynCall對象的execute方法(不返回Response對象,會根據重定向重試攔截器的isCanceled函數來判斷在其中調用OnResponse方法或OnFailure方法))。否則進入等待隊列。
同步方式則直接調用RealCall對象的execute方法,在該方法中調用Dispatcher調度器的executed方法(返回Response對象),直接進入同步執行隊列。
之後兩個execute方法都會調用getResponseWithInterceptorChain方法去獲取Response。在getResponseWithInterceptorChain方法中會遞歸的去處理下一個Interceptor(攔截器),最終返回Response。
本文精髓
1.OkHttpClient實現Call.Factory,負責爲Request創建Call(RealCall)。
2.異步方式中加入到執行隊列後線程池創建線程並執行該請求會調用AsynCall對象的executed方法,這是因爲抽象類NamedRunnable實現了Runnable接口並在run函數中執行execute方法(在類中爲抽象方法)而AsynCall類又繼承了NamedRunnable類,因此執行請求相當於調用AsynCall對象的execute方法。
3.同步方式調用的是RealCall對象的execute方法,在該方法中首先調用Dispatcher的executed方法,將RealCall對象加入同步執行隊列,之後會調用getResponseWithInterceptorChain方法。也就是說同步方式和異步方式調用的最終調用的雖然都是executed方法,但並不是同一個executed方法。網上有的圖片直接會都指向同一個executed,難免會讓讀者疑惑。
4.總結下getResonseWithInterceptorChain方法()做的事情。
- 創建一個List(interceptors)用來存儲攔截器
- 創建RealInterceptorChain對象,其中傳入的包括interceptors與index(爲0,從第一個開始)
- 調用RealInterceptorChain對象的proceed方法,在該方法中創建新的RealInterceptorChain對象(next),傳入list與index(1)。取出lsit中當前index的攔截器(interceptor),聲明Response對象,調用該攔截器的intercept方法,並傳入新創建的RealInterceptorChain,並將其返回值付給新創建的Response對象。(Response response = interceptor.intercept(next)),在proceed方法的結束會返回Response對象。
- 攔截器的intercept方法會繼續調用傳入的RealInterceptorChain對象的proceed方法,這樣就會遞歸處理攔截器鏈中的下一個攔截器。
- 遞歸結束後會返回一個Response對象。
流程圖
下圖是我總結的總體請求流程,可以說是非常詳細了。
參考鏈接(優秀文章彙總)
1.okhttp源碼分析(一)——基本流程(超詳細);
2.OkHttp-Request-請求執行流程;
3.Okhttp使用詳解
4.Okhttp官方網站
5.拆輪子系列:拆 OkHttp
6.拆輪子:OkHttp 的源碼解析(三):任務分發器(Dispatcher)
7.OkHttp Dispatcher的調度過程分析