OkHttp 知識梳理(1) OkHttp 原始碼解析之入門

澤毛發表於2017-12-21

一、簡介

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中宣告網路許可權:

<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物件
  • 由前兩步建立的OkHttpClientRequest物件建立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物件,其配置資訊包括請求的urlmethodheadersbody等等,這些都是HTTP的基礎知識,推薦大家看一下這篇文章 一篇文章帶你詳解 HTTP 協議(網路協議篇一),總結得很全面。

Request中的引數也可以通過建造者模式來進行配置,其原始碼地址為 Request.java

3.3 構建 Call

經過前面兩步我們得到了OkHttpClientRequest這兩個例項,接下來就需要建立請求的具體執行者:

Call call = client.newCall(request);
複製程式碼

newCall的程式碼很簡單,其實就是通過RealCall的靜態方法返回了一個RealCall物件,並持有OkHttpClientRequest的引用,同時它實現了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之前:

List<Interceptor>
接著建立了一個RealInterceptorChain物件,它的建構函式的第1引數就是上面List<Interceptor>列表,除此之外還需要注意第5個引數為0,這個對於下面的分析很重要,最後就是呼叫了RealInterceptorChainproceed方法,其實參就是前面建立的Request物件,為了便於理解,我們再看一下RealInterceptorChain

RealInterceptorChain

接下來,看一下 最關鍵的 RealInterceptorChainproceed中的邏輯:

    //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在執行完它的操作之後,就會呼叫RealInterceptorChainproceed方法,使得下一個Interceptorintercept方法可以被執行,以第一個攔截器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");
    }
複製程式碼

整個遞迴呼叫的過程為:

遞迴呼叫結果

在整個遞迴呼叫過程中,如果有任意一個Interceptorintercept方法返回了而沒有呼叫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 知識梳理系列:

相關文章