OkHttp
無疑是目前使用的最多的網絡框架,如今最火的Retrofit
也就是基於OkHttp
來實現的,和之前同樣,咱們將從最簡單的例子開始,一步步剖析OkHttp
的使用及內部實現原理。html
OkHttp
項目相關文檔java
咱們首先用一個簡單的例子來演示OkHttp
的簡單使用 - 經過中國天氣網提供的官方API
獲取城市的天氣,完整的代碼能夠查看個人Github
倉庫 RepoOkHttp 中第一章的例子。android
目前官網的最新版本爲3.9.1
,所以咱們須要在build.gradle
文件中進行聲明:git
compile 'com.squareup.okhttp3:okhttp:3.9.1'
複製代碼
不要忘了在AndroidManifest.xml
中聲明網絡權限:github
<uses-permission android:name="android.permission.INTERNET" />
複製代碼
最後,咱們用一個簡單的例子演示OkHttp
的同步請求,首先,經過HandlerThread
建立一個異步線程,經過發送消息的形式,通知它發起網絡請求,請求完畢以後,將結果中的body
部分發送回主線程用TextView
進行展現:web
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();
}
複製代碼
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。cookie
//(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();
複製代碼
OkHttpClient
用於全局的參數配置,通常來講,一個進程中擁有一個OkHttpClient
對象便可。而Request
則對應於一個請求的具體信息,每發起一次請求,就須要建立一個新的Request
對象,其配置信息包括請求的url
、method
、headers
、body
等等,這些都是HTTP
的基礎知識,推薦你們看一下這篇文章 一篇文章帶你詳解 HTTP 協議(網絡協議篇一),總結得很全面。
Request
中的參數也能夠經過建造者模式來進行配置,其源碼地址爲 Request.java。
通過前面兩步咱們獲得了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
兩個方法,這裏咱們先將 同步的方法,由於 異步 也是創建在它的基礎之上的。
//[同步請求的函數]
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);
}
複製代碼
整個經過請求分爲如下幾步:
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);
}
複製代碼
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
是最後一個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;
}
}
複製代碼
回到最開始的代碼,當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
請求的流程有個大概的印象,特別是調用鏈的模式。