Okhttp解析(一)請求的分發(fā),攔截

Okhttp特性

Okhttp是一個高效的,請求速度更快,更節(jié)省流量的http庫。擁有以下特性。

  1. 支持SPDY和http2,對同一服務(wù)器的所有請求共享同一個socket。
  2. 擁有自動維護(hù)的socket連接池,減少握手次數(shù)。
  3. socket自動選擇最好路線,并支持自動重連。
  4. 擁有隊列線程池,輕松寫并發(fā)。
  5. 擁有Interceptors輕松處理請求與響應(yīng)(比如透明GZIP壓縮,LOGGING)。
  6. 無縫的支持GZIP來減少數(shù)據(jù)流量。
  7. 支持基于Headers的緩存策略,緩存響應(yīng)數(shù)據(jù)來減少重復(fù)的網(wǎng)絡(luò)請求。
  8. 支持服務(wù)器多IP重連,支持從常用的連接問題中自動恢復(fù),還處理了代理服務(wù)器問題和SSL握手失敗問題。
注:SPDY是什么?
SPDY(讀作“SPeeDY”)是Google開發(fā)的基于TCP的傳輸層協(xié)議,用以最小化網(wǎng)絡(luò)延遲,提升網(wǎng)絡(luò)速度,優(yōu)化用戶的網(wǎng)絡(luò)使用體驗。
SPDY并不是一種用于替代HTTP的協(xié)議,而是對HTTP協(xié)議的增強(qiáng)。新協(xié)議的功能包括數(shù)據(jù)流的多路復(fù)用、請求優(yōu)先級以及HTTP報頭壓縮。
谷歌表示,引入SPDY協(xié)議后,在實驗室測試中頁面加載速度比原先快64%。

OkHttpClient分析版本

Okhttp3(3.2.0版本)

OkHttpClient的簡單調(diào)用

Get請求

public void doGet(String url){
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    Log.i("輸出:" + response.body().string());
}

Post請求

public void doPost(String url){
    MediaType json = MediaType.parse("application/json; charset=utf-8")
    RequestBody body = RequestBody.create(JSON, json);
    
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).post(body).build();
    Response response = client.newCall(request).execute();
    Log.i("輸出:" + response.body().string());
}

OkHttpClient介紹

OkHttpClient顧名思義是一個http請求的客戶端,實現(xiàn)了Call.Factory接口,提供newCall方法用于創(chuàng)建一個請求調(diào)用Call。

@Override public Call newCall(Request request) {
    return new RealCall(this, request);
}

OkHttpClient包含很多模塊,包括Dispatcher(請求分發(fā)器),ProxySelector(代理服務(wù)器選擇),InternalCache(內(nèi)部緩存)等其他的模塊,由它對外提供這些模塊的訪問,是典型的外觀模式,同時OkHttpClient設(shè)計為建造者模式,提供Builder方便為不同模塊進(jìn)行配置。

Request,Response,Call,RealCall,AsynCall介紹

  • Request作為請求信息的封裝,內(nèi)部包含了請求url,請求方法method,請求頭headers,請求體RequestBody,tag標(biāo)簽等。
  • Response作為相應(yīng)信息的封裝,內(nèi)部包含對應(yīng)的請求信息request,http協(xié)議protocol,響應(yīng)碼code,響應(yīng)頭headers,響應(yīng)消息message,響應(yīng)體ResponseBody等。
  • Call作為一個請求的接口,提供獲取對應(yīng)請求信息息request,執(zhí)行請求execute,異步請求入隊enqueue,取消請求cancel等方法的定義。
  • RealCall是Call請求的具體實現(xiàn),實現(xiàn)了同步請求執(zhí)行,異步請求入隊,請求取消等操作。同時內(nèi)部提供ApplicationInterceptorChain負(fù)責(zé)請求和響應(yīng)的攔截處理。
  • AsynCall是一個Runnable異步請求任務(wù),分發(fā)器Dispatcher負(fù)責(zé)管理這些異步請求,在可請求數(shù)量滿足的情況下會交給線程池執(zhí)行它的execute方法。

Dispatcher請求調(diào)用分發(fā)器

Dispatcher是請求Call的分發(fā)器,用于管理請求的等待,執(zhí)行,取消。分為同步請求RealCall和異步請求AsyncCall的管理。

  • 針對同步請求,用runningSyncCalls隊列記錄正在執(zhí)行的同步請求。
  • 針對異步請求,用readyAsyncCalls(待執(zhí)行的異步請求隊列)和runningAsyncCalls(正在執(zhí)行的異步請求隊列)記錄異步請求。
  • 內(nèi)部提供線程池,負(fù)責(zé)執(zhí)行異步請求,查看executorService()。
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;
}

這個線程池,我們看參數(shù)解析:

1. 參數(shù)0,代表核心線程的數(shù)量,即線程池中最少線程時的數(shù)量(當(dāng)所有線程都空閑時),為0的話,說明線程空閑時,不保留任何線程,做到線程低占用。
2. 參數(shù)Integer.MAX_VALUE,代表最大線程數(shù),即線程池中最多線程時的數(shù)量,為Integer.MAX_VALUE的話,說明可以開啟無限多的線程進(jìn)行工作,線程工作完了再關(guān)閉。
3. 參數(shù)60,和參數(shù)TimeUnit.SECONDS,表示當(dāng)線程池的數(shù)量比核心線程的數(shù)量大時,等待60秒之后就會去關(guān)閉空閑線程,使總的線程數(shù)量不會大于核心線程數(shù)量。
4. 參數(shù)new SynchronousQueue<Runnable>(), 代表這個線程等待隊列是同步隊列,當(dāng)有一個線程進(jìn)來時,就同時會有一個線程出去,也就是說線程不會停留在其中,一進(jìn)入就立馬出去執(zhí)行,這種方式在高頻請求時是很合適的。
5. 參數(shù)Util.threadFactory("OkHttp Dispatcher", false),表示提供一個線程工廠,用于創(chuàng)建新的線程。

這個線程池的設(shè)計,是需要時可以創(chuàng)建無限多的線程,不需要時不保留任何線程,空閑線程60秒后如果還是空閑就會回收,以保證高阻塞低占用的使用。

  • 設(shè)置有maxRequests(最大異步請求的數(shù)量)和maxRequestsPerHost(單個服務(wù)器的最大異步請求數(shù)量),這是為了限制同時執(zhí)行的總的請求數(shù)量和針對同一個服務(wù)器訪問的請求數(shù)量,如果有超過了這兩個的限制,就將異步請求AsyncCall添加到readyAsyncCalls(待執(zhí)行的異步請求隊列)去等待執(zhí)行。這兩個參數(shù)可以配置。
  • enqueue方法,AsyncCall入隊操作
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //未超過限制,執(zhí)行加入到線程池執(zhí)行異步請求
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      //超過限制,將行異步請求加入等待隊列,等待執(zhí)行
      readyAsyncCalls.add(call);
    }
}
  • finished方法,標(biāo)記當(dāng)前AsyncCall已經(jīng)完成,這時會考慮從異步等待隊列取出請求去執(zhí)行。
//標(biāo)記請求完成
synchronized void finished(AsyncCall call) {
    if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
    promoteCalls();
}

//從異步等待隊列取出請求去執(zhí)行,同時記錄到正在執(zhí)行的異步隊列中
private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
    
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }
    
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
}
  • cancelAll方法,取消所有請求,包括所有同步請求,所有正在執(zhí)行的異步請求,所有等待執(zhí)行的異步請求。
public synchronized void cancelAll() {
    for (AsyncCall call : readyAsyncCalls) {
      call.cancel();
    }
    
    for (AsyncCall call : runningAsyncCalls) {
      call.cancel();
    }
    
    for (RealCall call : runningSyncCalls) {
      call.cancel();
    }
}

RealCall 真正執(zhí)行請求調(diào)用的地方

RealCall是真正執(zhí)行請求調(diào)用的入口,無論是同步請求的execute,還是異步請求的enqueue(由Dispatcher進(jìn)行分發(fā)),最終都會到RealCall的getResponseWithInterceptorChain。

QQ截圖20171102182751.png

getResponseWithInterceptorChain里創(chuàng)建了一個ApplicationInterceptorChain攔截鏈來處理當(dāng)前RealCall中的Request請求數(shù)據(jù)。接下來講講Okhttp中攔截器是這樣的一種運行模式。

Interceptors攔截器原理

Interceptors攔截器采用了責(zé)任鏈模式一層一層的處理請求響應(yīng)信息。這里我們從同步請求去分析添加了HttpLoggingInterceptor日志打印攔截器的運行原理。

final class RealCall implements Call {

  //執(zhí)行請求,這里創(chuàng)建了ApplicationInterceptorChain攔截鏈,負(fù)責(zé)對所有攔截器進(jìn)行調(diào)用,調(diào)用proceed開始處理攔截
  private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
    return chain.proceed(originalRequest);
  }

  class ApplicationInterceptorChain implements Interceptor.Chain {
    private final int index;
    private final Request request;
    private final boolean forWebSocket;

    ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
      this.index = index;
      this.request = request;
      this.forWebSocket = forWebSocket;
    }

    @Override public Connection connection() {
      return null;
    }

    @Override public Request request() {
      return request;
    }

    //這里遍歷攔截器,通過index在攔截鏈中找到對應(yīng)的攔截器,然后調(diào)用intercept進(jìn)行攔截處理,返回加工后的Response響應(yīng)信息
    @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;
      }

      //當(dāng)所有攔截器都遍歷處理后,開始執(zhí)行真正請求,返回Response,這里才是真正產(chǎn)生Response響應(yīng)的地方。
      // No more interceptors. Do HTTP.
      return getResponse(request, forWebSocket);
    }
  }

}

看了以上代碼之后,我們知道是通過index一個一個遍歷攔截鏈的攔截器,去攔截處理,這是一個遞歸的過程,當(dāng)所有的攔截器處理了Request請求信息之后(也就是真正請求之前的預(yù)處理,比如打日志,修改請求頭什么的),才真正的交給網(wǎng)絡(luò)請求引擎去執(zhí)行請求,返回響應(yīng)信息,然后又按相反順序?qū)esponse響應(yīng)信息進(jìn)行攔截處理(也就是對響應(yīng)信息的再加工,比如打印響應(yīng)信息等)。那么我們分析HttpLoggingInterceptor日志打印攔截器會更加的的清晰這個過程。

public final class HttpLoggingInterceptor implements Interceptor {

    //攔截鏈上的
    @Override public Response intercept(Chain chain) throws IOException {
    Level level = this.level;

    //獲取請求信息
    Request request = chain.request();
    
    //如果不打印日志,那就直接調(diào)用chain.proceed執(zhí)行下一個攔截操作,不做其他操作。
    if (level == Level.NONE) {
      return chain.proceed(request);
    }

    //這部分是打印請求信息,對請求進(jìn)行預(yù)處理(這里可以對請求信息進(jìn)行修改或做其他操作)
    ---------------------------------------
    boolean logBody = level == Level.BODY;
    boolean logHeaders = logBody || level == Level.HEADERS;

    RequestBody requestBody = request.body();
    boolean hasRequestBody = requestBody != null;

    Connection connection = chain.connection();
    Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
    String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
    if (!logHeaders && hasRequestBody) {
      requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
    }
    logger.log(requestStartMessage);

    if (logHeaders) {
      if (hasRequestBody) {
        // Request body headers are only present when installed as a network interceptor. Force
        // them to be included (when available) so there values are known.
        if (requestBody.contentType() != null) {
          logger.log("Content-Type: " + requestBody.contentType());
        }
        if (requestBody.contentLength() != -1) {
          logger.log("Content-Length: " + requestBody.contentLength());
        }
      }

      Headers headers = request.headers();
      for (int i = 0, count = headers.size(); i < count; i++) {
        String name = headers.name(i);
        // Skip headers from the request body as they are explicitly logged above.
        if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
          logger.log(name + ": " + headers.value(i));
        }
      }

      if (!logBody || !hasRequestBody) {
        logger.log("--> END " + request.method());
      } else if (bodyEncoded(request.headers())) {
        logger.log("--> END " + request.method() + " (encoded body omitted)");
      } else {
        Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);

        Charset charset = UTF8;
        MediaType contentType = requestBody.contentType();
        if (contentType != null) {
          charset = contentType.charset(UTF8);
        }

        logger.log("");
        logger.log(buffer.readString(charset));

        logger.log("--> END " + request.method()
            + " (" + requestBody.contentLength() + "-byte body)");
      }
    }
    //請求預(yù)處理完成
    ----------------------------------
    
    //這里調(diào)用chain.proceed執(zhí)行下一個攔截操作,返回Response響應(yīng)信息,結(jié)合ApplicationInterceptorChain的proceed方法,很容易看出是一個責(zé)任鏈的遞歸調(diào)用模式
    long startNs = System.nanoTime();
    Response response = chain.proceed(request);
    long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

    //接下來打印響應(yīng)信息,對Response響應(yīng)進(jìn)行再加工(這里可以對響應(yīng)信息進(jìn)行修改或做其他操作)
    ---------------------------------------
    ResponseBody responseBody = response.body();
    long contentLength = responseBody.contentLength();
    String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
    logger.log("<-- " + response.code() + ' ' + response.message() + ' '
        + response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
        + bodySize + " body" : "") + ')');

    if (logHeaders) {
      Headers headers = response.headers();
      for (int i = 0, count = headers.size(); i < count; i++) {
        logger.log(headers.name(i) + ": " + headers.value(i));
      }

      if (!logBody || !HttpEngine.hasBody(response)) {
        logger.log("<-- END HTTP");
      } else if (bodyEncoded(response.headers())) {
        logger.log("<-- END HTTP (encoded body omitted)");
      } else {
        BufferedSource source = responseBody.source();
        source.request(Long.MAX_VALUE); // Buffer the entire body.
        Buffer buffer = source.buffer();

        Charset charset = UTF8;
        MediaType contentType = responseBody.contentType();
        if (contentType != null) {
          try {
            charset = contentType.charset(UTF8);
          } catch (UnsupportedCharsetException e) {
            logger.log("");
            logger.log("Couldn't decode the response body; charset is likely malformed.");
            logger.log("<-- END HTTP");

            return response;
          }
        }

        if (contentLength != 0) {
          logger.log("");
          logger.log(buffer.clone().readString(charset));
        }

        logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
      }
    }
    //響應(yīng)再加工完成
    ----------------------------------

    //這里返回響應(yīng)信息
    return response;
  }
}

明白攔截器的運行模式之后,我們知道真正的請求是在RealCall的getResponse方法中開始的。

/**
* Performs the request and returns the response. May return null if this call was canceled.
*/
Response getResponse(Request request, boolean forWebSocket) throws IOException {
    //這里負(fù)責(zé)執(zhí)行請求,然后返回響應(yīng)數(shù)據(jù)
    ...
}

具體請求我們下節(jié)分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容