Android小知識-剖析OkHttp中的異步請求

本平臺的文章更新會有延遲,你們能夠關注微信公衆號-顧林海,包括年末前會更新kotlin由淺入深系列教程,目前計劃在微信公衆號進行首發,若是你們想獲取最新教程,請關注微信公衆號,謝謝java

其實從OkHttp的同步和異步的調用來看差異不是很大,在剖析OkHttp中的同步請求一節中知道同步是經過Call對象的execute()方法,而這節的異步請求調用的是Call對象的enqueue方法,但異步請求機制與同步請求相比,仍是有所區別,這節就來分析異步請求的流程以及源碼分析。web

仍是先貼出異步請求的代碼:微信

private OkHttpClient mHttpClient = null;

    private void initHttpClient() {
        if (null == mHttpClient) {
            mHttpClient = new OkHttpClient.Builder()
                    .readTimeout(5, TimeUnit.SECONDS)//設置讀超時
                    .writeTimeout(5, TimeUnit.SECONDS)////設置寫超時
                    .connectTimeout(15, TimeUnit.SECONDS)//設置鏈接超時
                    .retryOnConnectionFailure(true)//是否自動重連
                    .build();
        }
    }

    private void asyRequest() {
        final Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .get()
                .build();
        Call call = mHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println(request.body().toString());
            }
        });
    }
複製代碼

這段代碼很熟悉,咱們快速過一下流程:網絡

  1. 建立OkHttpClient對象。異步

  2. 建立Request對象。ide

  3. 經過OkHttpClient的newCall方法將Request對象封裝Http實際請求的Call對象。源碼分析

  4. 最後經過Call對象的enqueue方法傳入Callback對象並實現兩個回調方法。ui

這裏最大的區別就是最後一步調用的是enqueue方法,前三步都沒有發起真正的網絡請求,真正的網絡請求是在第四步,因此咱們着重看最後一步。this

在enqueue方法中,會傳入Callback對象進來,這個Callback對象就是用於請求結束後對結果進行回調的,進入enqueu方法。url

public interface Call extends Cloneable {
        ...
        void enqueue(Callback responseCallback);
        ...
    }
複製代碼

發現這個Call只是接口,在剖析OkHttp中的同步請求一節中知道RealCall纔是真正實現Call的類。

點擊進入RealCall的enqueue方法:

@Override public void enqueue(Callback responseCallback) {
        //判斷同一Http是否請求過
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        //捕捉Http請求的異常堆棧信息
        captureCallStackTrace();
        eventListener.callStart(this);
        //重點1
        client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
    }
複製代碼

在enqueue方法中會先判斷RealCall這個Http請求是否請求過,請求過會拋出異常。

接着看最後一行代碼重點1:

一、傳入的Callback對象被封裝成了AsyncCall對象,點進去看一下AsyncCall對象究竟是幹什麼的。

final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;

        AsyncCall(Callback responseCallback) {
            super("OkHttp %s", redactedUrl());
            this.responseCallback = responseCallback;
        }
        ...
    }
複製代碼

這個AsyncCall繼承了NameRunnable,這個NameRunnable又是什麼呢?點進去看一下:

public abstract class NamedRunnable implements Runnable {

}
複製代碼

原來NameRunnable就是一個Runnable對象。回過頭來總結一下,也就是說咱們傳入的Callback對象被封裝成AsyncCall對象,這個AsyncCall對象本質就是一個Runnable對象。

二、獲取Dispatcher分發器,調用Dispatcher對象的enqueue方法並將AsyncCall對象做爲參數傳遞過去,最終完成異步請求,咱們看下Dispatcher的enqueue方法。

private int maxRequests = 64;
    private int maxRequestsPerHost = 5;
    synchronized void enqueue(RealCall.AsyncCall call) {
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
            //第一步
            runningAsyncCalls.add(call);
            executorService().execute(call);
        } else {
            //第二步
            readyAsyncCalls.add(call);
        }
    }
複製代碼

在enqueue方法前使用了synchronized關鍵字進行修飾,也就是爲這個方法加了個同步鎖,繼續往下看第一步,先是判斷當前異步請求總數是否小於設定的最大請求數(默認是64),以及正在運行的每一個主機請求數是否小於設定的主機最大請求數(默認是5),若是知足這兩個條件,就會把傳遞進來的AsyncCall對象添加到正在運行的異步請求隊列中,而後經過線程池執行這個請求。若是知足不了上面的兩個條件就會走第二步,將AsyncCall對象存入readyAsyncCalls隊列中,這個readyAsyncCalls就是用來存放等待請求的一個隊列。

總結RealCall的enqueue方法:

  1. 判斷當前Call:實際的Http請求是否只執行一次,若是不是拋出異常。

  2. 封裝成一個AsyncCall對象:將Callback對象封裝成一個AsyncCall對象,AsyncCall對象就是一個Runnable對象。

  3. client.dispatcher().enqueue():構建完AsyncCall也就是Runnable對象後,調用Dispatcher對象的enqueue方法來進行異步的網絡請求,並判斷當前請求數小於64以及當前host請求數小於5的狀況下,將Runnable對象放入正在請求的異步隊列中並經過線程池執行RealCall請求。若是不知足條件,將Runnable添加到等待就緒的異步請求隊列當中。

在上面總結的第三步中,知足條件會將AsyncCall對象經過線程池執行,咱們看一下線程池方法executorService():

private @Nullable ExecutorService 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;
    }
複製代碼

executorService方法只是返回一個線程池對象executorService。

獲取線程池對象後,就能夠調用它的execute方法,execute方法須要傳入一個Runnable對象,AsyncCall對象繼承NamedRunnable對象,而NamedRunnable又繼承了Runnable對象,那麼AsyncCall就是一個Runnable對象,這裏就會將AsyncCall對象傳入。

在源碼中發現AsyncCall並無實現run方法,那麼這個run方法必定就是在它的父類NamedRunnable中,咱們點擊進去看下:

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @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,這是一個典型的模板方法模式。既然AsyncCall繼承了NamedRunnable這個抽象類,那麼抽象方法execute的具體實現就交由AsyncCall來實現了。

進入AsyncCall中的execute方法:

@Override protected void execute() {
        boolean signalledCallback = false;
        try {
            //重點1
            Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
                //重點2:重定向和重試攔截器
                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 {
                eventListener.callFailed(RealCall.this, e);
                responseCallback.onFailure(RealCall.this, e);
            }
        } finally {
            //重點3:請求當前的異步請求
            client.dispatcher().finished(this);
        }
    }
複製代碼

在重點1處經過getResponseWithInterceptorChain()方法獲取返回的Response對象,getResponseWithInterceptorChain方法的做用是經過一系列的攔截器獲取Response對象。

在重點2處判斷重定向和重試攔截器是否取消,若是取消,調用responseCallback的onFailure回調,responseCallback就是咱們經過enqueue方法傳入的Callback對象。若是沒取消,調用responseCallback的onResponse回調。

因爲execute方法是在run方法中執行的,因此onFailure和onResponse回調都是在子線程當中。

在重點3處finally塊中,調用Dispatcher的finished方法。

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

    private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
            //移除請求
            if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
            if (promoteCalls) promoteCalls();
            //計算同步請求隊列+異步請求隊列的總數
            runningCallsCount = runningCallsCount();
            idleCallback = this.idleCallback;
        }

        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }
複製代碼

finished方法內部會將本次的異步請求RealCall從正在請求的異步請求隊列中移除,因爲promoteCalls傳入的是true,接着調用promoteCalls()方法,接着統計正在請求的同步和異步的請求總數,以及判斷當前總的請求數若是等於0而且idleCallback對象不爲空的狀況下執行idleCallback對象的run方法。

finished方法的介紹在剖析OkHttp中的同步請求一節中其實已經介紹過了,惟一有區別的就是promoteCalls參數,同步的時候傳入的是false,但在異步請求時傳入的是true,也就是會執行promoteCalls方法。

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.
    }
  }
複製代碼

在完成異步請求後,須要將當前的異步請求RealCall從正在請求的異步隊列中移除,移除完畢後會經過promoteCalls方法,將等待就緒的異步隊列中的請求添加到正在請求的異步請求隊列中去並經過線程池來執行異步請求。


838794-506ddad529df4cd4.webp.jpg

搜索微信「顧林海」公衆號,按期推送優質文章。

相關文章
相關標籤/搜索