OkHttp源碼解析

應用攔截器和網(wǎng)絡攔截器

以前其實就有一直在使用okhttp,也有聽說過攔截器這東西,但是一直沒有去深入了解。最近看《安卓進階之光》剛好看到okhttp攔截器的內容,然后自己也去挖了下源碼,才發(fā)現(xiàn)其巧妙之處。

攔截器有兩種,應用攔截器和網(wǎng)絡攔截器。用法可以看下面的代碼:

class LogInterceptor implements Interceptor {
    private String mName;

    LogInterceptor(String name) {
        mName = name;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        Log.d("LogInterceptor", "[" + mName + "] : request url = " + response.request().url() + ", " + response.headers().toString());
        return response;
    }
}

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new LogInterceptor("ApplicationInterceptor"))
        .addNetworkInterceptor(new LogInterceptor("NetworkInterceptor"))
        .build();

Request request = new Request.Builder()
        .url("http://www.github.com")
        .build();

client.newCall(request).enqueue(null);

運行之后的打印如下:

12-29 00:07:02.378 12641-12859/com.example.okhttp D/LogInterceptor: [NetworkInterceptor] : request url = http://www.github.com/, Content-length: 0
    Location: https://www.github.com/
12-29 00:07:03.653 12641-12859/com.example.okhttp D/LogInterceptor: [NetworkInterceptor] : request url = https://www.github.com/, Content-length: 0
    Location: https://github.com/
12-29 00:07:04.889 12641-12859/com.example.okhttp D/LogInterceptor: [NetworkInterceptor] : request url = https://github.com/, Date: Thu, 28 Dec 2017 16:07:05 GMT
    Content-Type: text/html; charset=utf-8
    Transfer-Encoding: chunked
    Server: GitHub.com
    Status: 200 OK
    ...(省略部分打印)
12-29 00:07:04.896 12641-12859/com.example.okhttp D/LogInterceptor: [ApplicationInterceptor] : request url = https://github.com/, Date: Thu, 28 Dec 2017 16:07:05 GMT
    Content-Type: text/html; charset=utf-8
    Transfer-Encoding: chunked
    Server: GitHub.com
    Status: 200 OK
    ...(省略部分打印)

攔截器是一種強大的機制,可以在攔截器中進行監(jiān)視、重寫和重試調用。像我們上面的代碼就對請求進行了監(jiān)視。

從打印可以看到,網(wǎng)絡攔截器攔截到了三個請求,同時攔截到了重定向的訪問。而應用攔截器只攔截到了一個請求,同時我們可以看到它攔截到的請求的url是 https://github.com/ 和我們在代碼中的請求 http://www.github.com 并不一致。

簡單來講,網(wǎng)絡攔截器在每一次網(wǎng)絡訪問的時候都會攔截到請求,而應用攔截器只會在OkHttpClient.newCall返回的Call執(zhí)行的時候被調用一次。

okhttp的運行流程

在講攔截器的實現(xiàn)之前我們先來簡單介紹一下okhttp的運行流程。

首先通過OkHttpClient.newCall我們可以獲得一個RealCall:

public class OkHttpClient implements Cloneable, Call.Factory {
  ...
  public Call newCall(Request request) {
    return new RealCall(this, request);
  }
  ...
}

異步訪問

RealCall實現(xiàn)了Call。接口,我們通過調用enqueue方法可以實現(xiàn)異步網(wǎng)絡訪問。讓我們直接看看RealCall.enqueue吧:

final class RealCall implements Call {
  ...
  public void enqueue(Callback responseCallback) {
    enqueue(responseCallback, false);
  }

  void enqueue(Callback responseCallback, boolean forWebSocket) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
  }
  ...
}

client.dispatcher()可以獲得一個Dispatcher,它用于網(wǎng)絡訪問任務的調度,我們的異步并發(fā)網(wǎng)絡訪問就是通過Dispatcher實現(xiàn)的。這里創(chuàng)建了一個AsyncCall,然后將它傳入Dispatcher.enqueue。AsyncCall是RealCall的內部類,而且它實際上是一個Runnable:

final class RealCall implements Call {
  ...
  final class AsyncCall extends NamedRunnable {
    ...
  }
  ...
}
public abstract class NamedRunnable implements Runnable {
  ...
  @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();
}

NamedRunnable在run方法里面會調用抽象的execute方法,在這個方法內部就會進行實際的網(wǎng)絡訪問。那Dispatcher.enqueue又做了寫什么呢?其實Dispatcher.enqueue實際上將AsyncCall這個Runnable放到了一個線程池中:

public final class Dispatcher {
  ...
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
  ...
  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;
  }
  ...
}

一切明了,Call.enqueue實際上是將AsyncCall這個Runnable放到了線程池中執(zhí)行去訪問網(wǎng)絡,而AsyncCall是RealCall的一個內部類,它持有RealCall的引用,所以在被線程池調用的時候可以獲得Request的信息。

所以將okhttp的異步流程簡化之后實際上就是Dispatcher中的線程池對Runnable的執(zhí)行:

1.png

然后我們看看AsyncCall.execute的具體實現(xiàn):

final class AsyncCall extends NamedRunnable {
  ...
  @Override protected void execute() {
   boolean signalledCallback = false;
   try {
     Response response = getResponseWithInterceptorChain(forWebSocket);
     if (canceled) {
       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);
   }
  }
  ...
}

可以看到它是通過getResponseWithInterceptorChain來訪問網(wǎng)絡獲取Response的。

同步訪問

如果想用OkHttp去阻塞是的訪問網(wǎng)絡我們可以這樣調用:

Response response = client.newCall(request).execute();

這個execute是不是有點眼熟,但它是Call的一個方法,并不是我們上面異步訪問中提到的NamedRunnable.execute:

public interface Call {
  ...
  Response execute() throws IOException;
  ..
}

現(xiàn)在我們來看看具體實現(xiàn):

final class RealCall implements Call {
  ...
  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain(false);
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
  ...
}

它也是通過getResponseWithInterceptorChain來訪問網(wǎng)絡獲取Response的。

攔截器的實現(xiàn)

我們在前面的小節(jié)中已經(jīng)知道了,無論是同步還是異步,最終都是通過RealCall.getResponseWithInterceptorChain方法去訪問網(wǎng)絡的。但是在查看具體源代碼的時候發(fā)現(xiàn)在okhttp3.4.0-RC1開始其具體的實現(xiàn)細節(jié)有了一些不一樣的地方。所以我這邊分開兩部分來講一講okhttp3.4.0-RC1之前和之后攔截器的具體實現(xiàn)細節(jié)。

okhttp3.4.0-RC1之前的實現(xiàn)

okhttp3.4.0-RC1之前的RealCall.getResponseWithInterceptorChain 中實際上是調用了ApplicationInterceptorChain.proceed方法去訪問網(wǎng)絡獲取Response:

private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
  Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
  return chain.proceed(originalRequest);
}

然后繼續(xù)看源碼,可以發(fā)現(xiàn)proceed內部會從OkHttpClient獲取序號為index的攔截器,并且創(chuàng)建新的序號加一的ApplicationInterceptorChain傳遞給攔截器去執(zhí)行。于是有多少個攔截器就創(chuàng)建了多少個ApplicationInterceptorChain,他們會按照自己的序號調用對應的攔截器。這其實就是一種責任鏈模式的實現(xiàn)方式:

@Override public Response proceed(Request request) throws IOException {
  // If there's another interceptor in the chain, call that.
  if (index < client.interceptors().size()) {
    Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
    Interceptor interceptor = client.interceptors().get(index);
    Response interceptedResponse = interceptor.intercept(chain);

    if (interceptedResponse == null) {
      throw new NullPointerException("application interceptor " + interceptor
          + " returned null");
    }

    return interceptedResponse;
  }

  // No more interceptors. Do HTTP.
  return getResponse(request, forWebSocket);
}

如果ApplicationInterceptorChain的序號大于OkHttpClient中注冊的攔截器的數(shù)量,則調用getResponse方法。這里ApplicationInterceptorChain是RealCall的內部類,getResponse調用的是RealCall.getResponse方法。

再看RealCall.getResponse方法,它內部有個while true的死循環(huán),調用HttpEngine.sendRequest和HttpEngine.readResponse去發(fā)送請求和接收響應,如果出現(xiàn)了RouteException異?;蛘逫OException異常則重新嘗試訪問:

Response getResponse(Request request, boolean forWebSocket) throws IOException {
    ...
    while (true) {
    ...
    try {
        engine.sendRequest();
        engine.readResponse();
        releaseConnection = false;
    } catch (RouteException e) {
        HttpEngine retryEngine = engine.recover(e.getLastConnectException(), true, null);
        if (retryEngine != null) {
            releaseConnection = false;
            engine = retryEngine;
            continue;
        }
        throw e.getLastConnectException();
    }catch (IOException e) {
        HttpEngine retryEngine = engine.recover(e, false, null);
        if (retryEngine != null) {
            releaseConnection = false;
            engine = retryEngine;
            continue;
        }
        throw e;
    }
    ...
}

我們繼續(xù)看engine.readResponse的實現(xiàn),可以看到它調用了NetworkInterceptorChain.proceed方法去獲取響應:

public void readResponse() throws IOException {
...
Response networkResponse;
...
networkResponse = new NetworkInterceptorChain(0, networkRequest,
                streamAllocation.connection()).proceed(networkRequest);
...
}

NetworkInterceptorChain.proceed和ApplicationInterceptorChain.proceed類似,也會不斷的創(chuàng)建新的NetworkInterceptorChain并且調用網(wǎng)絡攔截器,如果沒有網(wǎng)絡攔截器可以調用了,則會調用readNetworkResponse方法讀取響應:

@Override public Response proceed(Request request) throws IOException {
...
if (index < client.networkInterceptors().size()) {
    NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request, connection);
    Interceptor interceptor = client.networkInterceptors().get(index);
    Response interceptedResponse = interceptor.intercept(chain);
    ...
    return interceptedResponse;
}
Response response = readNetworkResponse();
...
return response;
}

這里還有一點需要說明的是NetworkInterceptorChain是HttpEngine的內部類,它調用的readNetworkResponse方法實際上是HttpEngine.readNetworkResponse?,F(xiàn)在我們就對OkHttp攔截器的請求流程和攔截器的實現(xiàn)原理有了比較全面的了解,下面這張圖對整個流程做一個總結:

2.png

okhttp3.4.0-RC1之后的實現(xiàn)

然后讓我們再來看一下3.4.0-RC1之后的實現(xiàn):

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));

  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  return chain.proceed(originalRequest);
}

這里已經(jīng)不再區(qū)分ApplicationInterceptorChain和NetworkInterceptorChain了,統(tǒng)一用RealInterceptorChain去處理:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
    RealConnection connection) throws IOException {
  ...

  // Call the next interceptor in the chain.
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
      connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
      writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);

  ...

  return response;
}

這里將cookie處理、緩存處理、網(wǎng)絡連接都作為責任鏈的一部分,比起3.4.0.RC-1之前更加完全的實現(xiàn)了責任鏈模式。這里有必要講一下的就是retryAndFollowUpInterceptor, 它是一個RetryAndFollowUpInterceptor實例,它負責重連和重定向我們之前在3.4.0.RC-1之前看到的getResponse的while true就放到了這里來實現(xiàn)。

讓我們看看它的整個流程:

3.png

這樣的實現(xiàn)是不是以前要清晰很多?所有的步驟一目了然,看過原來的版本再看看3.4.0.RC-1重構后的版本,的確有一種眼前一亮的驚艷之感。果然好代碼都是需要一點點優(yōu)化出來的。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 1.OkHttp源碼解析(一):OKHttp初階2 OkHttp源碼解析(二):OkHttp連接的"前戲"——HT...
    隔壁老李頭閱讀 16,092評論 36 72
  • 這段時間老李的新公司要更換網(wǎng)絡層,知道現(xiàn)在主流網(wǎng)絡層的模式是RxJava+Retrofit+OKHttp,所以老李...
    隔壁老李頭閱讀 33,890評論 51 405
  • 博文出處:OkHttp源碼解析,歡迎大家關注我的博客,謝謝! Header 注:本文 OkHttp 源碼解析基于 ...
    俞其榮閱讀 681評論 0 10
  • 隨著Google拋棄HttpClient和Volley的逐步?jīng)]落,OkHttp越來越受到開發(fā)者的青睞。高樓大廈也是...
    24K男閱讀 540評論 0 1
  • 前言:對于OkHttp我接觸的時間其實不太長,一直都是使用Retrofit + OkHttp 來做網(wǎng)絡請求的,但是...
    mecury閱讀 41,466評論 23 178

友情鏈接更多精彩內容