天天看點

android okhttp 源碼解析,OkHttp 知識梳理(1) - OkHttp 源碼解析之入門

OkHttp 知識梳理系列文章

一、簡介

OkHttp無疑是目前使用的最多的網絡架構,現在最火的Retrofit也就是基于OkHttp來實作的,和以前一樣,我們将從最簡單的例子開始,一步步剖析OkHttp的使用及内部實作原理。

OkHttp項目相關文檔

二、OKHttp 的使用

2.1 應用背景

我們首先用一個簡單的例子來示範OkHttp的簡單使用 - 通過中國天氣網提供的官方API擷取城市的天氣,完整的代碼可以檢視我的Github倉庫 RepoOkHttp 中第一章的例子。

2.2 引入依賴

目前官網的最新版本為3.9.1,是以我們需要在build.gradle檔案中進行聲明:

compile 'com.squareup.okhttp3:okhttp:3.9.1'

不要忘了在AndroidManifest.xml中聲明網絡權限:

最後,我們用一個簡單的例子示範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);

}

}

}

運作結果為:

android okhttp 源碼解析,OkHttp 知識梳理(1) - OkHttp 源碼解析之入門

運作結果

三、簡要流程分析

是不是很簡單,僅僅幾句話就完成了網絡請求,核心的四個步驟為:

建構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 interceptors = new ArrayList();

final List 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 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 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 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之前:

android okhttp 源碼解析,OkHttp 知識梳理(1) - OkHttp 源碼解析之入門

List

接着建立了一個RealInterceptorChain對象,它的構造函數的第1參數就是上面List清單,除此之外還需要注意第5個參數為0,這個對于下面的分析很重要,最後就是調用了RealInterceptorChain的proceed方法,其實參就是前面建立的Request對象,為了便于了解,我們再看一下RealInterceptorChain:

android okhttp 源碼解析,OkHttp 知識梳理(1) - OkHttp 源碼解析之入門

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");

}

整個遞歸調用的過程為:

android okhttp 源碼解析,OkHttp 知識梳理(1) - OkHttp 源碼解析之入門

遞歸調用結果

在整個遞歸調用過程中,如果有任意一個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 void finished(Deque 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 知識梳理系列: