深入淺出 OkHttp 原始碼

yangxi_001發表於2017-06-21

OkHttp3是Square出品的高質量Http網路請求庫,目前在GitHub上的star數超過17000。很多Android專案的網路元件都是基於OkHttp封裝的,還有著名的Retrofit也是基於OkHttp封裝的。

OkHttp的基本使用

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url(ENDPOINT)
    .build();
//同步請求    
Response response = client.newCall(request).execute();
//非同步請求
client.newCall(request).enqueue(new Callback() {
  @Override
  public void onFailure(Call call, IOException e) {

  }

  @Override
  public void onResponse(Call call, Response response) throws IOException {

  }
});

最基本的用法就是先建立一個OkHttpClient,然後build出一個Requset物件,最後傳送請求,可以是同步請求,也可以是非同步請求。使用起來很簡單,但背後是怎麼實現的,下面從原始碼層面來分析下。

OkHttp 呼叫流程

OkHttp內部呼叫流程圖

第一步: new OkHttpClient(Builder)

//OkHttpClient.java
public OkHttpClient() {
    this(new Builder());
}
OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    ......
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
}

這裡建立了一個預設的OkHttpCient.Builder,用於配置各種引數。

第二步:okhttpclient.newCall(request)

//OkHttpClient.java
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
//RealCall.java
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}

這裡用request物件建立了一個RealCall物件,把一些引數傳到RealCall。

第三步:execute() or enqueue()

//RealCall.java
@Override public Response execute() throws IOException {
synchronized (this) {
  if (executed) throw new IllegalStateException("Already Executed");
  executed = true;
}
captureCallStackTrace();
try {
  client.dispatcher().executed(this);
  //核心的函式
  Response result = getResponseWithInterceptorChain();
  if (result == null) throw new IOException("Canceled");
  return result;
} finally {
  client.dispatcher().finished(this);
}
}

同步請求,很直接就呼叫到了最核心的函式getResponseWithInterceptorChain()。再看下非同步請求。

//RealCall.java
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

而非同步請求,將使用者介面的responseCallback物件封裝成一個AsyncCall物件提交給Dispather來處理,這裡的AsyncCallRealCall的一個內部類。再看下這個Dispather怎麼處理這個AsyncCall的。

//Dispatcher.java
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
  runningAsyncCalls.add(call);
  executorService().execute(call);
} else {
  readyAsyncCalls.add(call);
}
}

Dispather管理了一些請求佇列,如果正在執行的非同步請求沒有達到上限,就直接將這個請求提交給執行緒池,否則加入到等待佇列中。而且這裡直接把AsyncCall的物件給了執行緒池,其實這個AsyncCall就是一個Runnable的實現類。

//RealCall.java
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
    ......
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          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);
      }
    }
  }

AsyncCall父類的run()方法會呼叫抽象方法execute(),也就是將在Dispather裡的執行緒池執行AsyncCall物件的時候,就會執行到execute(),在這個方法裡同樣呼叫了核心的網路請求方法getResponseWithInterceptorChain()
而且在execute()裡會回撥使用者介面responseCallback的回撥方法。注意:這裡的回撥是在非主執行緒直接回撥的,也就是在Android裡使用的話要注意這裡面不能直接更新UI操作。
至此,同步請求和非同步請求最終都是呼叫的getResponseWithInterceptorChain();來傳送網路請求,只是非同步請求涉及到一些執行緒池操作,包括請求的佇列管理、排程。

第四步:getResponseWithInterceptorChain()

//RealCall.java
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);
return chain.proceed(originalRequest);
}

在這個方法裡就是新增了一些攔截器,然後啟動一個攔截器呼叫鏈,攔截器遞迴呼叫之後最後返回請求的響應Response。這裡的攔截器分層的思想就是借鑑的網路裡的分層模型的思想。請求從最上面一層到最下一層,響應從最下一層到最上一層,每一層只負責自己的任務,對請求或響應做自己負責的那塊的修改。

Q1:這裡為什麼每次都重新建立RealInterceptorChain物件,為什麼不直接複用上一層的RealInterceptorChain物件?(文末給出答案)

OkHttp攔截器分層結構

//RealInterceptorChain.java
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
  Connection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
......
RealInterceptorChain next = new RealInterceptorChain(
    interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
...
return response;
}

RealInterceptorChain的proceed(),每次重新建立一個RealInterceptorChain物件,然後呼叫下一層的攔截器的interceptor.intercept()方法。
每一個攔截器的intercept()方法都是這樣的模型

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    // 1、該攔截器在Request階段負責的事情

    // 2、呼叫RealInterceptorChain.proceed(),其實是遞迴呼叫下一層攔截器的intercept方法
    response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);

    //3、該攔截器在Response階段負責的事情,然後返回到上一層攔截器的 response階段
    return  response;     
    }
  }

這差不多就是OkHttp的分層攔截器模型,借鑑了網路裡的OSI七層模型的思想。最底層是CallServerInterceptor,類比網路裡的物理層。OkHttp還支援使用者自定義攔截器,插入到最頂層和CallServerInterceptor上一層的位置。比如官方寫了一個Logging Interceptor,用於列印網路請求日誌的攔截器。

BridgeInterceptor

Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
// Request階段
RequestBody body = userRequest.body();
if (body != null) {
  MediaType contentType = body.contentType();
    ......
  long contentLength = body.contentLength();
  if (contentLength != -1) {
    requestBuilder.header("Content-Length", Long.toString(contentLength));
    requestBuilder.removeHeader("Transfer-Encoding");
  } else {
    requestBuilder.header("Transfer-Encoding", "chunked");
    requestBuilder.removeHeader("Content-Length");
  }
  if (userRequest.header("Connection") == null) {
  requestBuilder.header("Connection", "Keep-Alive");
 }
}
    .....
Response networkResponse = chain.proceed(requestBuilder.build());
// Response階段
    .....
if (transparentGzip
    && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
    && HttpHeaders.hasBody(networkResponse)) {
  GzipSource responseBody = new GzipSource(networkResponse.body().source());
  Headers strippedHeaders = networkResponse.headers().newBuilder()
      .removeAll("Content-Encoding")
      .removeAll("Content-Length")
      .build();
  responseBuilder.headers(strippedHeaders);
  responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}

BridgeInterceptor攔截器在Request階段,將使用者的配置資訊,重新建立Request.Builder物件,重新build出Request物件,並新增一些請求頭,比如:host,content-length,keep-alive等。
BridgeInterceptor在Response階段做gzip解壓操作。

CacheInterceptor

CacheInterceptor攔截器在Request階段判斷該請求是否有快取,是否需要重新請求,如果不需要重新請求,直接從快取裡取出內容,封裝一個Response返回,不需要再呼叫下一層。
CacheInterceptor攔截器在Response階段,就是把下面一層的Response做快取。

ConnectInterceptor

//ConnectInterceptor.java
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);

ConnectInterceptor攔截器只在Request階段建立連線,Response階段直接把下一層的Response返回給上一層。再看下建立連線的過程。

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
....
try {
  RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
      writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
  HttpCodec resultCodec = resultConnection.newCodec(client, this);
......
} catch (IOException e) {
  throw new RouteException(e);
}
}

findHealthyConnection()函式尋找一條健康的網路連線,其內部主要呼叫了findConnection()

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
  boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
 .....
  // Attempt to get a connection from the pool.
  Internal.instance.get(connectionPool, address, this);
  if (connection != null) {
    return connection;
  }

  selectedRoute = route;
}

// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {
  selectedRoute = routeSelector.next();
}

// Create a connection and assign it to this allocation immediately. This makes it possible for
// an asynchronous cancel() to interrupt the handshake we're about to do.
RealConnection result;
synchronized (connectionPool) {
  route = selectedRoute;
  refusedStreamCount = 0;
  result = new RealConnection(connectionPool, selectedRoute);
  acquire(result);
  if (canceled) throw new IOException("Canceled");
}

// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());

Socket socket = null;
synchronized (connectionPool) {
  // Pool the connection.
  Internal.instance.put(connectionPool, result);
 .....
}
closeQuietly(socket);
return result;
}

這裡面大概就是從連線池裡去找已有的網路連線,如果有,則複用,減少三次握手;沒有的話,則建立一個RealConnection物件,三次握手,建立連線,然後將連線放到連線池。具體的內部connect過程,就不深入了。

public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
}

ConnectionPool最多支援保持5個地址的連線keep-alive,每個keep-alive 5分鐘,並有非同步執行緒迴圈清理無效的連線。

CallServerInterceptor

@Override public Response intercept(Chain chain) throws IOException {
...
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
  ......
  if (responseBuilder == null) {
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }
}
httpCodec.finishRequest();
if (responseBuilder == null) {
  responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
    .request(request)
    .handshake(streamAllocation.connection().handshake())
    .sentRequestAtMillis(sentRequestMillis)
    .receivedResponseAtMillis(System.currentTimeMillis())
    .build();

int code = response.code();
.....
return response;
}

CallServerInterceptor 精簡出來的程式碼就是writeRequestHeaders(),flushRequest(),finishRequest(),傳送請求,然後readResponseHeaders,openResponseBody讀取response。
CallServerInterceptor底層的IO流讀寫依賴於Square自家的Okio專案,HttpCodec是封裝的IO編碼和解碼的實現。

至此,OkHttp中幾個核心的攔截器就到此為止了,OkHttp最精髓的部分也就體現在這個攔截器上。最後補充幾個關於OkHttp的面試問題。
* OkHttp是如何做鏈路複用?
* OkHttp的Intereptor能不能取消一個request?
這兩個問題在分析原始碼之後應該很容易回答了。

回答上面留的一個問題:

每次重新建立一個RealInterceptorChain物件,因為這裡是遞迴呼叫,在呼叫下一層攔截器的interupter()方法的時候,本層的 response階段還沒有執行完成,如果複用RealInterceptorChain物件,必然導致下一層修改RealInterceptorChain,所以需要重新建立RealInterceptorChain物件。

轉自:https://www.diycode.cc/topics/640

相關文章