一、簡介
OkHttp
無疑是目前使用的最多的網路框架,現在最火的Retrofit
也就是基於OkHttp
來實現的,和以前一樣,我們將從最簡單的例子開始,一步步剖析OkHttp
的使用及內部實現原理。
OkHttp
專案相關文件
- 官網地址:square.github.io/okhttp/
- 原始碼地址:github.com/square/okht…
API
文件:square.github.io/okhttp/3.x/…
二、OKHttp 的使用
2.1 應用背景
我們首先用一個簡單的例子來演示OkHttp
的簡單使用 - 通過中國天氣網提供的官方API
獲取城市的天氣,完整的程式碼可以檢視我的Github
倉庫 RepoOkHttp 中第一章的例子。
2.2 引入依賴
目前官網的最新版本為3.9.1
,因此我們需要在build.gradle
檔案中進行宣告:
compile 'com.squareup.okhttp3:okhttp:3.9.1'
複製程式碼
不要忘了在AndroidManifest.xml
中宣告網路許可權:
<uses-permission android:name="android.permission.INTERNET" />
複製程式碼
最後,我們用一個簡單的例子演示OkHttp
的同步請求,首先,通過HandlerThread
建立一個非同步執行緒,通過傳送訊息的形式,通知它發起網路請求,請求完畢之後,將結果中的body
部分傳送回主執行緒用TextView
進行展示:
public class SimpleActivity extends AppCompatActivity {
private static final String URL = "http://www.weather.com.cn/adat/sk/101010100.html";
private static final int MSG_REQUEST = 0;
private static final int MSG_UPDATE_UI = 0;
private Button mBtRequest;
private Button mBtRequestAsync;
private TextView mTvResult;
private BackgroundHandler mBackgroundHandler;
private MainHandler mMainHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
mBtRequest = (Button) findViewById(R.id.bt_request_sync);
mBtRequestAsync = (Button) findViewById(R.id.bt_request_async);
mTvResult = (TextView) findViewById(R.id.tv_result);
mBtRequest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startSyncRequest();
}
});
mBtRequestAsync.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAsyncRequest();
}
});
HandlerThread backgroundThread = new HandlerThread("backgroundThread");
backgroundThread.start();
mBackgroundHandler = new BackgroundHandler(backgroundThread.getLooper());
mMainHandler = new MainHandler();
}
/**
* 同步發起請求的例子。
*/
private void startSyncRequest() {
//傳送訊息到非同步執行緒,發起請求。
mBackgroundHandler.sendEmptyMessage(MSG_REQUEST);
}
/**
* 非同步發起請求的例子。
*/
private void startAsyncRequest() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(URL).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String result = response.body().string();
//返回結果給主執行緒。
Message message = mMainHandler.obtainMessage(MSG_UPDATE_UI, result);
mMainHandler.sendMessage(message);
}
});
}
private class BackgroundHandler extends Handler {
BackgroundHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//在非同步執行緒發起請求。
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(URL).build();
Call call = client.newCall(request);
try {
Response response = call.execute();
String result = response.body().string();
//返回結果給主執行緒。
Message message = mMainHandler.obtainMessage(MSG_UPDATE_UI, result);
mMainHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private class MainHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//主執行緒獲取到結果之後進行更新。
String result = (String) msg.obj;
mTvResult.setText(result);
}
}
}
複製程式碼
執行結果為:
三、簡要流程分析
是不是很簡單,僅僅幾句話就完成了網路請求,核心的四個步驟為:
- 構建
OkHttpClient
物件 - 構建
Request
物件 - 由前兩步建立的
OkHttpClient
和Request
物件建立Call
物件 - 通過
Call
物件發起請求,並得到一個Response
,它就是最終返回的結果。
//1.構建 OkHttpClient 物件
OkHttpClient client = new OkHttpClient();
//2.構建 Request 物件。
Request request = new Request.Builder().url(URL).build();
//3.由 OkHttpClient 通過 Request 建立 Call 物件
Call call = client.newCall(request);
try {
//4.通過 Call 物件發起請求
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
複製程式碼
3.1 構建 OkHttpClient 物件
OkHttpClient
提供了許多配置項供使用者進行設定,例如快取、Cookie
、超時時間等等,對於其中引數的初始化可以採用建造者模式,其原始碼地址為 OkHttpClient.java,例如:
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(3000, TimeUnit.MILLISECONDS)
.readTimeout(3000, TimeUnit.MILLISECONDS);
OkHttpClient client = builder.build();
複製程式碼
如果我們什麼也不做,像最開始的那樣直接new OkHttpClient()
,那麼將會採用預設的配置,部分配置如下,關於各個引數的含義可以參考:OkHttpClient.Builder。
//(1) Sets the dispatcher used to set policy and execute asynchronous requests.
this.dispatcher = new Dispatcher();
//(2) Configure the protocols used by this client to communicate with remote servers
this.protocols = OkHttpClient.DEFAULT_PROTOCOLS;
//(3) Unknow
this.connectionSpecs = OkHttpClient.DEFAULT_CONNECTION_SPECS;
//(4) Configure a factory to provide per-call scoped listeners that will receive analytic events for this client
this.eventListenerFactory = EventListener.factory(EventListener.NONE);
//(5) Sets the proxy selection policy to be used if no is specified explicitly
this.proxySelector = ProxySelector.getDefault();
//(6) Sets the handler that can accept cookies from incoming HTTP responses and provides cookies to outgoing HTTP requests
this.cookieJar = CookieJar.NO_COOKIES;
//(7) Sets the socket factory used to create connections
this.socketFactory = SocketFactory.getDefault();
//(8) Sets the verifier used to confirm that response certificates apply to requested hostnames for HTTPS connections
this.hostnameVerifier = OkHostnameVerifier.INSTANCE;
//(9) Sets the certificate pinner that constrains which certificates are trusted
this.certificatePinner = CertificatePinner.DEFAULT;
//(10) Sets the authenticator used to respond to challenges from proxy servers
this.proxyAuthenticator = Authenticator.NONE;
//(11) Sets the authenticator used to respond to challenges from origin servers
this.authenticator = Authenticator.NONE;
//(12) Sets the connection pool used to recycle HTTP and HTTPS connections
this.connectionPool = new ConnectionPool();
//(13) ets the DNS service used to lookup IP addresses for hostnames
this.dns = Dns.SYSTEM;
//(14) Configure this client to follow redirects from HTTPS to HTTP and from HTTP to HTTPS
this.followSslRedirects = true;
//(15) Configure this client to follow redirects
this.followRedirects = true;
//(16) Configure this client to retry or not when a connectivity problem is encountered
this.retryOnConnectionFailure = true;
//(17) TimeOut
this.connectTimeout = 10000;
this.readTimeout = 10000;
this.writeTimeout = 10000;
//(18) Sets the interval between web socket pings initiated by this client
this.pingInterval = 0;
複製程式碼
上面的引數很多,後面用到的時候再去分析,這裡我們主要關注兩個重要的成員變數,它們是Interceptor
型別元素的列表,在後面我們將會花很大的篇幅來介紹它:
final List<Interceptor> interceptors = new ArrayList();
final List<Interceptor> networkInterceptors = new ArrayList();
複製程式碼
3.2 構建 Request
OkHttpClient
用於全域性的引數配置,一般來說,一個程式中擁有一個OkHttpClient
物件即可。而Request
則對應於一個請求的具體資訊,每發起一次請求,就需要建立一個新的Request
物件,其配置資訊包括請求的url
、method
、headers
、body
等等,這些都是HTTP
的基礎知識,推薦大家看一下這篇文章 一篇文章帶你詳解 HTTP 協議(網路協議篇一),總結得很全面。
Request
中的引數也可以通過建造者模式來進行配置,其原始碼地址為 Request.java。
3.3 構建 Call
經過前面兩步我們得到了OkHttpClient
和Request
這兩個例項,接下來就需要建立請求的具體執行者:
Call call = client.newCall(request);
複製程式碼
newCall
的程式碼很簡單,其實就是通過RealCall
的靜態方法返回了一個RealCall
物件,並持有OkHttpClient
和Request
的引用,同時它實現了Call
介面:
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false);
}
複製程式碼
Call
介面的定義如下,它定義了請求的介面:
public interface Call extends Cloneable {
//返回原始的請求物件。
Request request();
//同步發起請求。
Response execute() throws IOException;
//非同步發起請求。
void enqueue(Callback var1);
//取消請求。
void cancel();
//請求是否已經被執行。
boolean isExecuted();
//請求是否取消。
boolean isCanceled();
//clone 物件。
Call clone();
//工廠類。
public interface Factory {
Call newCall(Request var1);
}
}
複製程式碼
當使用RealCall
發起請求時,有同步和非同步兩種方式,分別對應於.execute
和.enqueue
兩個方法,這裡我們先將 同步的方法,因為 非同步 也是建立在它的基礎之上的。
3.4 同步發起請求 - execute()
//[同步請求的函式]
public Response execute() throws IOException {
synchronized(this) {
//如果已經發起過請求,那麼直接跑出異常。
if(this.executed) {
throw new IllegalStateException("Already Executed");
}
//標記為已經發起過請求。
this.executed = true;
}
//捕獲這個請求的 StackTrace。
this.captureCallStackTrace();
//通知監聽者已經開始請求。
this.eventListener.callStart(this);
Response var2;
try {
//通過 OkHttpClient 的排程器執行請求。
this.client.dispatcher().executed(this);
//getResponseWithInterceptorChain() 責任鏈模式。
Response result = this.getResponseWithInterceptorChain();
if(result == null) {
throw new IOException("Canceled");
}
var2 = result;
} catch (IOException var7) {
//通知監聽者發生了異常。
this.eventListener.callFailed(this, var7);
throw var7;
} finally {
//通過排程器結束該任務。
this.client.dispatcher().finished(this);
}
//返回結果。
return var2;
}
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList();
interceptors.addAll(this.client.interceptors());
interceptors.add(this.retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
interceptors.add(new CacheInterceptor(this.client.internalCache()));
interceptors.add(new ConnectInterceptor(this.client));
if(!this.forWebSocket) {
interceptors.addAll(this.client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(this.forWebSocket));
Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
return chain.proceed(this.originalRequest);
}
複製程式碼
整個通過請求分為以下幾步:
3.4.1 將 Call 任務加入到同步佇列當中
this.client.dispatcher().executed(this);
複製程式碼
這裡的執行並不是真正的執行,預設情況下排程器的實現為Dispatcher
,它的executed
方法實現為:
//同步佇列。
private final Deque<RealCall> runningSyncCalls = new ArrayDeque();
//Dispatcher.executed 僅僅是將 Call 加入到佇列當中,而並沒有真正執行。
synchronized void executed(RealCall call) {
this.runningSyncCalls.add(call);
}
複製程式碼
3.4.2 執行請求
Response result = this.getResponseWithInterceptorChain()
複製程式碼
真正地觸發了請求的執行是上面這句,我們來簡單看一下getResponseWithInterceptorChain
是怎麼觸發請求的。
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList();
//先加入使用者自定義的攔截器。
interceptors.addAll(this.client.interceptors());
//加入標準的攔截器。
interceptors.add(this.retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
interceptors.add(new CacheInterceptor(this.client.internalCache()));
interceptors.add(new ConnectInterceptor(this.client));
if(!this.forWebSocket) {
interceptors.addAll(this.client.networkInterceptors());
}
//訪問伺服器的攔截器。
interceptors.add(new CallServerInterceptor(this.forWebSocket));
//建立呼叫鏈,注意第五個引數目前的值為0。
Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
//執行呼叫鏈的 proceed 方法。
return chain.proceed(this.originalRequest);
}
複製程式碼
這裡我們將一系列的Interceptor
加入到了List
當中,構建完之後,List
中的內容如下所示,對於使用者自定義的interceptors
將會加在列表的頭部,而自定義的networkInterceptors
則會加在CallServerInterceptor
之前:
RealInterceptorChain
物件,它的建構函式的第1
引數就是上面List<Interceptor>
列表,除此之外還需要注意第5
個引數為0
,這個對於下面的分析很重要,最後就是呼叫了RealInterceptorChain
的proceed
方法,其實參就是前面建立的Request
物件,為了便於理解,我們再看一下RealInterceptorChain
:
接下來,看一下 最關鍵的 RealInterceptorChain
的proceed
中的邏輯:
//RealInterceptorChain 的 proceed 方法。
public Response proceed(Request request) throws IOException {
return this.proceed(request, this.streamAllocation, this.httpCodec, this.connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
if(this.index >= this.interceptors.size()) {
throw new AssertionError();
} else {
++this.calls;
if(this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must retain the same host and port");
} else if(this.httpCodec != null && this.calls > 1) {
throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must call proceed() exactly once");
} else {
//關鍵部分的程式碼是這幾句。
//(1) 建立一個新的 RealInterceptorChain 物件,這裡注意前面說的第5個引數變成了 index+1。
RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpCodec, connection, this.index + 1, request, this.call, this.eventListener, this.connectTimeout, this.readTimeout, this.writeTimeout);
//(2) 取列表中位於 index 位置的攔截器。
Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);
//(3) 呼叫它的 intercept 方法,並傳入新建立的 RealInterceptorChain。
Response response = interceptor.intercept(next);
if(httpCodec != null && this.index + 1 < this.interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once");
} else if(response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
} else if(response.body() == null) {
throw new IllegalStateException("interceptor " + interceptor + " returned a response with no body");
} else {
return response;
}
}
}
}
複製程式碼
忽略不重要的程式碼,RealInterceptorChain
關鍵的程式碼有三個步驟:
- 建立一個新的
RealInterceptorChain
物件,這裡注意前面說的第5
個引數變成了index+1
- 取列表中位於
index
位置的攔截器。 - 呼叫它的
intercept
方法,並傳入新建立的RealInterceptorChain
而每個Interceptor
在執行完它的操作之後,就會呼叫RealInterceptorChain
的proceed
方法,使得下一個Interceptor
的intercept
方法可以被執行,以第一個攔截器RetryAndFollowUpInterceptor
為例:
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
this.streamAllocation = new StreamAllocation(this.client.connectionPool(), this.createAddress(request.url()), call, eventListener, this.callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
//這是一個 While 迴圈,知道沒有達到終止的條件就一直重試。
while(!this.canceled) {
boolean releaseConnection = true;
Response response;
try {
//呼叫下一個攔截器。
response = realChain.proceed(request, this.streamAllocation, (HttpCodec)null, (RealConnection)null);
releaseConnection = false;
} catch (RouteException var16) {
if(!this.recover(var16.getLastConnectException(), false, request)) {
throw var16.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException var17) {
boolean requestSendStarted = !(var17 instanceof ConnectionShutdownException);
if(!this.recover(var17, requestSendStarted, request)) {
throw var17;
}
releaseConnection = false;
continue;
} finally {
if(releaseConnection) {
this.streamAllocation.streamFailed((IOException)null);
this.streamAllocation.release();
}
}
if(priorResponse != null) {
response = response.newBuilder().priorResponse(priorResponse.newBuilder().body((ResponseBody)null).build()).build();
}
Request followUp = this.followUpRequest(response);
if(followUp == null) {
if(!this.forWebSocket) {
this.streamAllocation.release();
}
//返回了 response,那麼整個呼叫就結束了。
return response;
}
//....
}
this.streamAllocation.release();
throw new IOException("Canceled");
}
複製程式碼
整個遞迴呼叫的過程為:
在整個遞迴呼叫過程中,如果有任意一個Interceptor
的intercept
方法返回了而沒有呼叫proceed
方法,那麼整個呼叫將會結束,排在它之後的Interceptor
將不會被執行。
CallServerInterceptor
CallServerInterceptor
是最後一個Interceptor
,與之前的攔截器不同,在它的intercept
方法中 不會建立一個新的 RealInterceptorChain ,而是直接返回了Response,使得整個遞迴呼叫一步步向上返回。
public Response intercept(Chain chain) throws IOException {
//發起請求..
Response response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();
realChain.eventListener().responseHeadersEnd(realChain.call(), response);
int code = response.code();
if(this.forWebSocket && code == 101) {
response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();
} else {
response = response.newBuilder().body(httpCodec.openResponseBody(response)).build();
}
//只有兩種選擇,丟擲異常或者返回結果,不會進行下一步的呼叫。
if((code == 204 || code == 205) && response.body().contentLength() > 0L) {
throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
} else {
return response;
}
}
複製程式碼
3.4.3 結束任務
回到最開始的程式碼,當getResponseWithInterceptorChain
返回之後,最後通過dispatcher.finish(RealCall call)
方法結束任務:
void finished(RealCall call) {
this.finished(this.runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized(this) {
//從 runningSyncCalls 移除它。
if (!calls.remove(call)) {
throw new AssertionError("Call wasn't in-flight!");
}
//false 不執行。
if (promoteCalls) {
this.promoteCalls();
}
runningCallsCount = this.runningCallsCount();
idleCallback = this.idleCallback;
}
//如果當前已經沒有可以執行的任務,那麼呼叫 idleCallback.run() 方法。
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
public synchronized int runningCallsCount() {
return this.runningAsyncCalls.size() + this.runningSyncCalls.size();
}
複製程式碼
四、小結
可以看到,對於 同步請求,這個函式的呼叫都是在.execute()
呼叫的執行緒執行的,其實 非同步請求 的核心邏輯和同步請求是相同的,只不過加入了執行緒的管理。不知不覺說得又有點長了,還是把 非同步請求 的分析放在下一篇文章裡面講吧,順便結合OkHttp
中對於執行緒的管理,這一章只是一個入門,關鍵是讓大家對整個OkHttp
請求的流程有個大概的印象,特別是呼叫鏈的模式。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- Android 面試文件分享:www.jianshu.com/p/8456fe6b2…