OkHttp源碼解讀總結(四)--->OkHttp異步請求源碼總結

OkHttp源碼解讀總結(四)—>OkHttp異步請求源碼總結

標籤(空格分隔): OkHttp源碼 學習筆記


前言

  • 以下的相關知識總結是通過慕課網的相關學習和自己的相關看法,如果有需要的可以去查看一下慕課網的相關教學,感覺還可以。

上一節已經總結了同步請求源碼,接下來查看異步,因爲同步和異步的差別只在於Call之後的execute()和enqueue()方法,因此前三步驟這裏不再贅述。

enqueue()異步請求

call是個接口,因此enqueue這個方法要到實現類RealCall中進行

 @Override 
 public void enqueue(Callback responseCallback) {
    //鎖
    synchronized (this) {
      //是否已經執行過  
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    //捕捉堆棧信息
    captureCallStackTrace();
    //開啓監聽
    eventListener.callStart(this);
    //通過將傳入的Callback對象傳入AsyncCall()裏面創建一個AsyncCall對象實例  因此當我們通過client.dispatcher獲取到Dispatcher這個分發器 然後傳入到剛創建好的Runnable這個實例,然後調用enqueue()方法進行異步網絡請求
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

client.dispatcher()

 //返回Dispatcher對象
 public Dispatcher dispatcher() {
    return dispatcher;  //這個就是在okhttpclient的構造方法的第一行就已經實例化好的
  }

AsyncCall(responseCallback)

class AsyncCall extends NamedRunnable   //我們可以看出,這個AsyncCall繼承於NamedRunnable,查看NamedRunnable實現Runnable,因此AsyncCall就是一個Runnable實例

enqueue(AsyncCall call)

 //同步鎖  傳入創建好的異步Runnable
 synchronized void enqueue(AsyncCall call) {
    //判斷實際的運行請求數是否小於允許的最大的請求數量(64)  並且共享主機的正在運行的調用的數量小於同時最大的相同Host的請求數(5)
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
     //如果都符合 那麼就把這個請求添加到正在執行的異步請求隊列當中
      runningAsyncCalls.add(call);
      //然後通過線程池去執行這個請求call
      executorService().execute(call);
    } else {
      //否則的話 在就緒(等待)異步請求隊列當中添加
      readyAsyncCalls.add(call);
    }
  }

executorService()(Dispatcher類中的方法)

  //單例  返回ExecutorService這個線程池對象  同步
  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      //雖然這個地方定義了最大的線程池的數量是Integer.MAX_VALUE  但是我們知道上面對請求數量有了限制(64個)
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

關於線程池的推薦
深入理解java線程池—ThreadPoolExecutor

當創建好ExecutorService這個線程池對象之後,就需要調用execute(call)執行方法執行Runnable,也就是執行AsyncCall裏面的run()方法,我們去查找AsyncCall裏面的run方法,可以看到沒有這個方法,卻有一個execute()方法,而這個方法是複寫的父類的方法,我們查看NamedRunnable這個類,可以發現有個run方法

  @Override public final void run() {
    //獲取到當先線程的名字()
    String oldName = Thread.currentThread().getName();
    //賦值
    Thread.currentThread().setName(name);
    try {
      //執行  這個是抽象類 也就是剛纔在AsyncCall看到的execute()方法
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

RealCall中的execute()方法

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
      //攔截器鏈
        Response response = getResponseWithInterceptorChain();
        //重定向/重試是否取消
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          //callback的onFailure()返回  在call.enqueue()裏面傳進去的
          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 {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        //
        client.dispatcher().finished(this);
      }
    }

finished(AsyncCall call)方法

 void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

finished(runningAsyncCalls, call, true)方法

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
     //1、調用calls.remove()方法進行刪除正在請求的異步線程
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      //2、調用promoteCalls()方法調整整個請求隊列
      if (promoteCalls) promoteCalls();
      //3、重新計算正在執行的線程的數量
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

最終不管是成功還是失敗,都會返回到我們複寫的Callback方法裏面

requestCall.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //當請求出錯的時候  會回調到這個方法中
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //要注意這個地方是子線程,okhttp並沒有給我們把響應的數據轉換到主線程中,因此我們在這個地方更新UI的時候
                //是會報錯的  需要我們手動的轉換到主線程  然後才進行數據的解析和UI更新   在之前的retrofit的講解中
                //也稍微有所涉及  那就是retrofit已經幫我們把異步的這個請求  返回來的callback已經切換到了主線程
                //得益於retrofit裏面的Executorer這個回調執行器
                if (response.isSuccessful()){
                    String string = response.body().string();
                }
            }
        });

總結

  • 1、判斷當前call
    • 這個請求只能被執行一次,如果已經請求過了,就會拋出異常
  • 2、通過傳遞進來的Callback封裝成一個AsyncCall(Runnable)對象
  • 3、獲取Dispatcher對象並執行enqueue()異步請求
    • 如果這個AsyncCall請求符合條件(判斷實際的運行請求數是否小於允許的最大的請求數量(64) 並且共享主機的正在運行的調用的數量小於同時最大的相同Host的請求數(5)) 纔會添加到執行異步請求隊列,然後通過線程池進行異步請求
    • 否則就把這個AsyncCall請求添加到就緒(等待)異步請求隊列當中
    • 這個Dispatcher持有一個正在執行的請求隊列/就緒(等待)執行的請求隊列,還有一個就是線程池。這三個來完整的處理異步請求操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章