天天看點

okHttp源碼分析(1)-分發流程

一. okHttp簡介

okhttp已經成為Android開發中必不可少的網絡請求工具,無論在平時開發還是面試的過程中,都會有所涉及,弄清okhttp的使用流程,已經是每一個android開發者成為進階工程師的必經之路。其實其原理并不複雜,本文将分兩篇來着重介紹okhttp中分發器和攔截器原理。

二. 大綱

  1. okHttp的優點以及請求過程
  2. Dispatcher的分發流程

三. okHttp的優點以及請求過程

  1. 首先我們來看看使用okhttp都有哪些優點:
  • 支援HTTP/2并允許對同一主機的所有請求共享一個套接字
  • 通過連接配接池,複用socket,減少請求延遲
  • 預設通過gzip壓縮資料
  • 響應緩存,避免重複請求網絡
  • 請求失敗自動重試主機其它ip,自動重定向
  1. okHttp的請求流程

使用okhttp的大緻流程為:

  1. 建立一個OkHttpClient
  2. 建立一個Request對象
  3. 建立一個call對象,接受request
  4. 進行請求執行任務
  5. 内部通過Dispatcher分發任務
  6. 五大預設攔截器完成整個請求過程
  7. 傳回結果

get請求流程:

//1.建立OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.建立Request對象,設定請求方式。
Request request = new Request.Builder()
        .url("https://www.jianshu.com/u/f260c485f077")
        .get()
        .build();
//3.建立一個call對象
Call call = okHttpClient.newCall(request);
//4.同步請求
new Thread(new Runnable() {
    @Override
    public void run() {
        
 		Response response = call.execute();
       
     }}).start();
//5.異步請求
call.enqueue(new Callback() {
    //請求失敗執行的方法
    @Override
    public void onFailure(Call call, IOException e) {
        String err = e.getMessage().toString();
    }

    //請求成功執行的方法
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        inal String rtn = response.body().string();
    }
});
           

post請求流程:

//1.建立OkHttpClient對象,設定參數
OkHttpClient okHttpClient = new OkHttpClient();

FormBody.Builder mBuild = new FormBody.Builder();
mBuild.add("key1", "vaule1")
        .add("key2", "vaule2");
RequestBody requestBodyPost = mBuild.build();

//2.建立Request對象,設定請求方式。
Request request = new Request.Builder()
        .url("http://www.baidu.com")
        .post(requestBodyPost)
        .build();
//3.建立一個call對象
Call call = okHttpClient.newCall(request);
//4.請求回調方法
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {

    }

    //請求成功執行的方法
    @Override
    public void onResponse(Call call, Response response) throws IOException {

    }
});

           

四. Dispatcher的分發流程

okHttp中Dispatcher的分發流程大緻分為以下幾步:

  1. 調用execute或者enqueue加入任務
  2. Dispatcher根據條件判斷将目前任務加入隊列 runningAsyncCalls(執行隊列)還是readyAsyncCalls(等待隊列)
  3. runningAsyncCalls隊列調用線程池執行任務,
  4. runningAsyncCalls執行任務完成,根據條件擷取readyAsyncCalls中任務執行

根據okhttp中Dispatcher的分發流程,我們有幾個問題需要弄清楚:

Q: Dispatcher将請求分發到隊列過程中,如何決定放入ready還是running?

Q: 從ready移動running的條件是什麼?(如何移動,從哪裡移動)

Q: Dispatcher分發器線程池的工作行為是怎樣的。

帶着以上三個問題,我們可以看看源碼是如何實作的。

第一個問題 Dispatcher将請求分發到隊列過程中,如何決定放入ready還是running

當我們要執行一個異步請求的時候,會調用okhttp的enqueue方法,其内部就會調用分發器的enqueue方法,我們直接來看分發器的enqueue方法做了什麼:

okhttp的enqueue方法:

@Override 
public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    // client就是OkHttpClient 通過client擷取Dispatcher 調用Dispatcher的enqueue
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
           

Dispatcher的enqueue方法:

private int maxRequests = 64;
private int maxRequestsPerHost = 5;

synchronized void enqueue(AsyncCall call) {
    //runningAsyncCalls.size() < maxRequests(最大正在請求的數量)
    //runningCallsForHost(call) < maxRequestsPerHost(同一域名最大正在請求的個數限制)
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) <  				   	   maxRequestsPerHost) {
      	runningAsyncCalls.add(call);
      	executorService().execute(call);
    } else {
      	readyAsyncCalls.add(call);
    }
  }
           

看到這裡相信大家都能明白第一個問題,由注解可以看到,Dispatcher将任務加入隊列的兩個條件,目前最大請求數不超過64,同一域名最大正在請求的個數限制不超過5,同時滿足這兩個的情況下,想請求加入執行隊列runningAsyncCalls中,并立即執行。不滿足條件加入等待隊列readyAsyncCalls中。

第二個問題 從ready移動running的條件是什麼?(如何移動,從哪裡移動)

然後我們再回過頭來看看AsyncCall是個什麼,我們的請求加入這個AsyncCall以後都做了什麼,打開AsyncCal源碼

首先可以看到RealCall implements Call ,當我們調用call.enqueue時,也就是再調用RealCall.enqueue,那麼我們隻要看RealCall.enqueue中做了什麼即可。

@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 {
        //請求執行完畢,調用finish();
        client.dispatcher().finished(this);
    }
}
           

如何把ready移動running,那麼肯定是running中任務已經執行完成,是以我們繼續看finnally中 client.dispatcher().finished(this);做了什麼。

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();
    }
}
           
private void promoteCalls() {
    //判斷正在執行的隊列個數,不滿足傳回
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
   //判斷等待隊列是否有請求,不滿足傳回
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
	//循環擷取等待隊列中的請求
    for (Iterator<RealCall.AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        RealCall.AsyncCall call = i.next();
		//判斷目前call的host執行個數是否小于maxRequestsPerHost
        if (runningCallsForHost(call) < maxRequestsPerHost) {
            i.remove();
            //将等待隊列中的call加入到runningAsyncCalls
            runningAsyncCalls.add(call);
            //開始執行任務
            executorService().execute(call);
        }
		//判斷正在執行的隊列個數,滿足就returen,不在繼續增加
        if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
}
           

以上代碼邏輯,注釋的很清晰。會通過相同的條件,将等待隊列中的請求加入到執行隊列

最後一個問題,分發器線程池的工作行為是怎樣的。

看到這裡,相信大家已經對okhtt的分發流程了解個大概,此時我們已經将請求加入到了隊列中,而在分發執行請求的過程中,okhttp是怎麼來執行這些任務的呢,還是繼續來看源碼,通過以上源碼的分析我們可以看到Dispatcher是通過executorService().execute(call)方法執行任務,是以我們直接看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;
}
           

熟悉線程池的同學,看到這裡估計已經明白了,okhttp内部幫我們建立了一個線程池,通過線程池來執行我們的請求,這裡我們主要介紹一下,線程池中的幾個參數

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
           

corePoolSize = 0 okhttp預設不幫我們緩存空閑線程個數,隻有當我們真正需要請求的時候才會幫我們建立線程

maximumPoolSize = Integer.MAX_VALUE 最大值保證了我們任務的最大并發數

keepAliveTime = 60 任務閑時最多存活時間

TimeUnit.SECONDS

new SynchronousQueue() 阻塞隊列,用于工作線程都被占用情況下,儲存新的任務請求

Util.threadFactory(“OkHttp Dispatcher”, false) 線程工廠

這裡我們隻需要這幾個問題便可

  • corePoolSize 為何設定為0
  • maximumPoolSize 為何設定為最大值
  • 阻塞隊列為何隻能使用SynchronousQueue,不能用ArrayBlockingQueue或者LinkedBlockingQueue。

當一個請求過來,線程池内部首先判斷corePoolSize 大小為0,是以加入SynchronousQueue,而SynchronousQueue是一個無容量隊列,再來判斷maximumPoolSize 值,是一個Int最大值,是以任務被立馬建立執行。之後來的每個請求都是這樣被建立,保障了最大并發。

再來看看ArrayBlockingQueue,我們給定ArrayBlockingQueue中容量大小為1,假設此時有兩個請求A,B。A請求過來,被加入ArrayBlockingQueue中,因為線程池内部判斷,目前正在執行線程個數為0是,會建立線程,是以A不會加入ArrayBlockingQueue中,而是直接被建立。這時B請求再過來時,我們假設A是個耗時操作,而這時線程池中無空閑線程,B被加入ArrayBlockingQueue,且處于無限等待狀态。

LinkedBlockingQueue的邏輯和ArrayBlockingQueue差不多,隻不過ArrayBlockingQueue容量可以無限大。具體邏輯這裡不在分析。

分發器的流程以及原理就到這裡,okhttp每個版本代碼可能會略有不同,本文基于okhttp3.6.0源碼進行分析。下一篇我們将介紹okhttp中的攔截器是如何實作的呢。