二十五、OkHttp原理分析(一)

一、使用方式

OkHttp的使用分爲了同步請求和異步請求,分別通過調用execute和enqueue方法,在異步的時候需要傳入一個CallBack回調。當使用Get請求的時候,直接傳入URL,而在Post請求的時候需要構建RequestBody。

package com.jilian.pinzi.base;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class OkHttpTest {
    OkHttpClient okHttpClient = new OkHttpClient();

    /**
     * 同步請求
     *
     * @param url
     */
    void getSyn(String url) throws IOException {
        Request request = new Request.Builder().url(url).build();
        Call call = okHttpClient.newCall(request);
        Response response = call.execute();
        ResponseBody body = response.body();
    }

    /**
     * 異步請求
     *
     * @param url
     */
    void getSyc(String url) throws IOException {
        RequestBody body = new FormBody.Builder().add("name","allen").build()
        Request request = new Request.Builder().url(url).
                post(body).
                build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                ResponseBody body = response.body();
            }
        });


    }
}

二、源碼流程分析

1、OkHttp中的分發器Dispatcher

我們來看下異步的流程

  • 創建一個OkHttpClient對象
    OkHttpClient okHttpClient = new OkHttpClient();
  • 創建一個Request對象,並傳入URL和RequestBody,封裝成了一個請求對象Request
  RequestBody body = new FormBody.Builder().add("name","allen").build()
        Request request = new Request.Builder().url(url).
                post(body).
                build();
  • 通過OkHttpClient對象調用newCall方法,傳入Request對象,創建一個Call對象
        Call call = okHttpClient.newCall(request);
  • 最終通過RealCall調用newRealCall,傳入了OkHttpClient,和Request返回一個Call對象,而RealCall實現了Call
  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }
  • 以上就把我們的請求封裝成了一個Call,並且在Call對象中封裝了Request個OkHttplient。接着通過Call對象調用enqueue方法發起請求
   call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                ResponseBody body = response.body();
            }
        });

接着會執行到RealCall的如下代碼,核心代碼在最後一行,通過OkHttpClient的dispatcher()執行enqueue方法,並且將我們的Callback封裝成了AsyncCall作爲參數

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

接着就執行到了Dispatcher中的enqueue方法,Dispatcher也就是我們說的分發器,用來分發請求的任務。第一行將我們的Call,也就是請求任務添加到一個準備隊列去。

  void enqueue(AsyncCall call) {
    synchronized (this) {
//請求任務添加到一個準備隊列去
      readyAsyncCalls.add(call);

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
  }

最後一行調用promoteAndExecute方法
(1)遍歷準備隊列
(2)如果正在運行的任務隊列大於設置的最大等於請求數maxRequests,直接跳出循環
(3)如果同一個相同Host的請求數量,也就是請求同一個主機的請求數量大於等於設置的最大數量maxRequestsPerHost,直接跳出循環
(4)如果以上不滿足,就將當前請求任務從準備隊列readyAsyncCalls中移除
(5)將當前請求任務添加到運行隊列
(6)同時給每一個請求任務設置一個線程池對象去執行。該線程池對象是單例的

 private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
//遍歷準備隊列
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();
      //如果正在運行的任務隊列大於設置的最大請求數maxRequests,直接跳出循環
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
      //如果同一個相同Host的請求數量,也就是請求同一個主機的請求數量大於等於設置的最大數量maxRequestsPerHost,直接跳出循環
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
    //如果以上不滿足,就將當前遍歷的請求任務移除隊列
        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }
  //同時給每一個請求任務設置一個線程池對象。改線程池對象是單例的
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }
  • 以上的流程總結來說就是:
    OkHttp請求的時候最終會通過內部的分發器Dispatcher去分發任務,而Dispatcher分發器維持了兩個隊列,一個是運行隊列、一個是準備隊列。通過最大請求任務數和同一個主機最大的請求任務數來控制我們新的任務是進入到運行隊列還是準備隊列。並且爲每一個請求任務設置了一個線程池對象去執行,該對象是單例的。
    而我們的請求任務被封裝成了一個AsyncCall,它最終是實現了Runnable接口,因此接着通過線程池執行我們的任務,
    void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

AsyncCall是RealCall的一個內部類,不僅持有了我們傳入的Callback同時也持有了傳入的Request。到這裏我們就知道我們的請求任務封裝成了一個Request並封裝成AsyncCall。最終通過線程池去執行我們的請求任務。在run方法中執行了execute方法,我們的任務最終執行到了execute方法:

@Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
      //執行我們的網絡請求
        Response response = getResponseWithInterceptorChain();
        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);
        }
      } catch (Throwable t) {
        cancel();
        if (!signalledCallback) {
          IOException canceledException = new IOException("canceled due to " + t);
          canceledException.addSuppressed(t);
//回調失敗
          responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
      } finally {
//最終完成請求,分發器結束執行
        client.dispatcher().finished(this);
      }
    }
  }

(1)執行我們的網絡請求
Response response = getResponseWithInterceptorChain();
(2)回調成功
responseCallback.onResponse(RealCall.this, response);
(3)回調失敗
responseCallback.onFailure(RealCall.this, e);
(4)在finally部分表示請求完成,通過分發器調用finished方法,而在finished方法中,我們就去更新同步分發器中的兩個隊列,準備隊列和運行隊列

  void finished(AsyncCall call) {
    call.callsPerHost().decrementAndGet();
    finished(runningAsyncCalls, call);
  }
  private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }

    boolean isRunning = promoteAndExecute();

    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }
  • 以上我們就完成了請求任務的分發、準備隊列和運行隊列的管理、線程池執行我們的任務、線程池執行完畢之後不管是失敗還是成功重新同步我們的準備隊列和運行隊列。

2、OkHttp中的攔截器

我們以上分析了請求任務是通過分發器分發給了線程池去執行接着我們來看任務是如何執行的?
在OkHttp中的請求任務是通過五大攔截器去執行的,每個攔截器負責各自的任務,在這裏採用了責任鏈的設計模式,來看下具體執行任務的方法getResponseWithInterceptorChain
主要包含了五大攔截器:

  • RetryAndFollowUpInterceptor重試攔截器
    通過返回的結果狀態碼判斷是否要重定向重試。
    -BridgeInterceptor 橋接攔截器
    負責將HTTP協議中必備的請求頭加入到其中,例如Host
  • CacheInterceptor緩存攔截器交給下一個攔截器執行的時候判斷是否需要使用緩存中的數據,獲得結果的時候判斷是否需要緩存
  • ConnectInterceptor 連接攔截器
    連接攔截器在把任務交給下一個攔截器之前負責找到或者創建一個連接,並獲得Socket流。
  • CallServerInterceptor 請求服務器攔截器
    請求服務器攔截器負責真正的與服務器進行通信,向服務器發送數據並解析響應的數據
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    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, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

因此我們知道在責任鏈社交模式中我們的一次網絡請求任務通過五大攔截器往下執行,每個攔截器負責處理自己的任務然後交給下一個任務執行,並且也可以直接返回,不交給下面的攔截器,比如緩存攔截器,當需要讀取緩存的時候直接讀取緩存返回給上一個攔截器。並且最開始執行的攔截器總是最後收到請求結果,最後執行的攔截器比如請求服務器攔截器最先拿到結果,然後依次反饋給上面的攔截器。最終返回到我們的接受值。
我們通過一張圖來了解攔截器的工作流程:


三、小結

  • 通過Okhttp開始一個網絡請求,首先通過Dispatcher分發器分發任務,在分發器中維護了兩個隊列:準備隊列和運行隊列用來控制運行任務的最大併發量以及同一個Host的最大併發量。運行隊列中的任務則是通過一個單例的線程池去執行。
  • 而到了任務的具體執行過程則是通過Okhttp中的五大攔截器去執行,這裏使用了責任鏈的設計模式,每一個攔截器負責處理各自的任務,然後交給下一個攔截器執行。當然我們也可以手動添加自定義的攔截器來執行我們的相關代碼。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章