OkHttp深入學習(一)——初探



    Android 4.4之後,HttpURLConnection底層實現已被OkHttp替換。可以見得OkHttp的性能已經被Google所認同。對於爲何會想深入瞭解該庫的原因:因爲它的最底層走到了java的Socket;利用向Socket寫入特定的Http協議數據包,實現網絡通信。學習該開源項目,對於網絡的學歷大有益處,除此之外OkHttp使用了緩存和線程池概念。總之個人覺得OkHttp開源項目可以作爲學習網絡通信一篇很好的教科書

OkHttp的特點:

  • 支持HTTP2/SPDY黑科技
  • socket自動選擇最好路線,並支持自動重連
  • 擁有自動維護的socket連接池,減少握手次數
  • 擁有隊列線程池,輕鬆寫併發
  • 擁有Interceptors輕鬆處理請求與響應(比如透明GZIP壓縮,LOGGING)
  • 實現基於Headers的緩存策略

OkHttp的使用:

  1. 創建OkHttpClient對象:OkHttpClient client = new OkHttpClient();
  2. 創建網絡請求:Request request = new Request.Builder()  .url("http://sethfeng.github.io/index.html") .build();
  3. 得到Call對象:Call call = client.newCall(request);  //實際創建的是一個RealCall對象,RealCall中有一個對client對象的引用
  4. 發送同步請求:Response response = call.excute();
  5. 發送異步請求:
    • call.enqueue(new Callback() {
          @Override
          public void onFailure(Request request, IOException e) {
              ...
          }
          @Override
          public void onResponse(Response response) throws IOException {
              ...
          }
      });


更多使用方法參考官方說明文檔

OkHttpClient工作原理初步分析

RealCall.class

先來看一下通過 client.newCall(request)得到的Call對象
newCall(request)@OkHttpClient.class
Call newCall(Request request) {
    return new RealCall(this, request);
}
方法很簡單就是利用調用newCall方法的OkHttpClient對象和newCall的輸入參數構造一個RealCall對象。接下來看一下RealCall的構造器。
RealCall()@RealCall.class
protected RealCall(OkHttpClient client, Request originalRequest) {
    this.client = client;
    this.originalRequest = originalRequest;
  }
構造器也很簡單,就是對RealCall中的OkHttpClient和Request域進行賦值。接下來看一下RealCall類中的execute和enqueue方法
public Response execute() throws IOException    
{
try {
            this.client.getDispatcher().executed(this); //同步請求不排隊
             //方法內部會執行synchronized void executed(Call call) {   this.executedCalls.add(call);  }  即把這次請求加入到分發器裏
            Response result = this.getResponseWithInterceptorChain(false);
            if(result == null) {
                throw new IOException("Canceled");
            }
            var2 = result;
 } finally {this.client.getDispatcher().finished(this); }
    ...
    return var2;
}
該方法完成一個同步請求,首先將這次的同步請求call添加到Dispatcher的工作隊列中,隨後調用getResponseWithInterceptorChain方法獲取request對應的response。最後返回得到的response。
public void enqueue(Callback responseCallback) {
    enqueue(responseCallback, false);
 }
實際調用下面方法
void enqueue(Callback responseCallback, boolean forWebSocket) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket)); //異步請求會排隊!
}
內部類[email protected]
final class AsyncCall extends NamedRunnable{
protected void execute() {
        Response response = getResponseWithInterceptorChain(forWebSocket);
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
       this.client.getDispatcher().finished(this);  
    }
}
enqueue則是將請求包裝成一個異步請求,異步請求繼承自Runnable的子接口,實現了一個execute異步方法,Dispatcher會在合適的時間調用該方法,注意這裏的execute方法和前面的execute不是同一個方法。在execute方法內部會調用 getResponseWithInterceptorChain方法獲得網絡請求的返回值,隨後利用回調方法,將結果發送給客戶。
Dispatcher負責對客戶請求進行處理,因此接下來分析一下Dispatcher的execute和enqueue方法。將在下一節分析getResponseWithInterceptorChain方法的底層實現。

Dispatcher.class

OkHttpClient中有一個Dispatcher類型的域,在構造OkHttpClient的時候會調用new Dispatcher()方法獲得一個Dispatcher對象,因此我們直接看Dispatcher的源碼。
Dispatcher()@Dispatcher.class
public Dispatcher() {
}
看到這個方法一臉的懵逼。什麼也沒做?不要方,既然這裏沒有做任何工作,那麼有兩種可能,Dispatcher的相關域要麼在類加載時或者聲明時就進行了初始化,要麼就會在使用的時候再臨時進行初始化,即延遲初始化,這也是一種優化。那我們直接看execute方法和enqueue方法
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call); 
}
該方法很簡單,就是將Recall請求添加到runningSyncCalls集合中。該集合在構造Dispatcher的時候就初始化好了Deque<RealCall> runningSyncCalls = new ArrayDeque<>();該集合代表着當前正在線程中運行的請求。有的童鞋是不是已經方了?這就完了?我們的請求在哪執行!!??不要忘了我們調用的RealCall的execute方法中,在this.client.getDispatcher().executed(this);語句後面繼續調用Response result = this.getResponseWithInterceptorChain(false);方法,而實際的網絡請求是在這裏進行的。可以說Dispatch主要負責對客戶的請求進行管理,留存,備份;重點在於利用線程池對異步請求的處理,同步請求它基本不幹活。下面接着看enqueue方法。
enqueue()@Dispatcher.class
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      // maxRequestsPerHost默認爲5 maxRequests默認64
      runningAsyncCalls.add(call);
      executorService().execute(call); //交給線程池去執行 call中的execute方法
    } else {
      readyAsyncCalls.add(call);  
      //存入等待隊列
      //對於存入這裏的請求,在方法promoteCalls()中會被取出,進行執行;
      //任務執行完成後,調用finished的promoteCalls()函數,不管是異步還是同步請求,它們在執行完execute方法過後都會調用Dispatcher的finished方法
    }
  }
這個方法代碼相對於execute就多了一些代碼,不過邏輯很簡單,首先判斷集合runningSyncCalls的大小,即當前運行的請求數是否小於maxRequest;同時判斷該請求的hostname對應的的請求個數是否小於maxRequestsPerHost。如果條件全爲真,則直接將該請求加入到集合runningSyncCalls中,隨後調用executorService().execute(call);對異步任務進行處理。否則將異步請求加入到等待異步執行隊列readyAsyncCalls中。下面看看executorService方法
executorService()@Dispatcher.class
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;
}
該方法返回了一個線程池,很熟悉吧;類似通過Executors.newCachedThreadPool()方法得到一個線程池。那麼調用executorService().execute(call);方法最終會執行異步請求的execute方法,而異步請求的execute方法內部會調用getResponseWithInterceptorChain()方法獲得response。
那麼到此爲止對於execute方法和enqueue方法的介紹就結束了。如果好奇點的童鞋,可能會問,如果異步請求被添加到readyAsyncCalls集合中,那麼它何時會被執行呢?注意到,在RealCall的execute和enqueue方法執行完後都會執行this.client.getDispatcher().finished(this);  這樣一條語句。那麼我們來看下Dispatcher的finished方法。
finished()@Dispatcher.class
synchronized void finished(AsyncCall call) {
    if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
    promoteCalls();
}
方法很簡單,將請求從runningAsyncCalls中移除出去,隨後執行 promoteCalls()方法,接着看該方法源碼
finished()@Dispatcher.class
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.
    }
  }
在當前運行任務數大於maxRequests和等待執行異步任務數爲空的兩種情況下直接返回不進行任何操作。否則從等待執行異步請求集合中獲取到請求,判斷該請求的hostname對應的的請求個數是否小於maxRequestsPerHost,爲真則將該任務從等待執行異步請求集合中移出,存入runningAsyncCalls集合中,最後調用線程池執行器執行該異步請求的execute方法。到此爲止我們對於Dispatcher的介紹就到此爲止了。
對Dispatcher的總結如下:
  • 該類中有兩個集合分別爲:runningAsyncCalls、readyAsyncCalls前者存放正在執行的請求,後者存放等待執行的請求
  • 該類中有一個newCachedThreadPool線程執行器,利用該執行器來執行異步請求的execute方法。也就是說異步請求發送在非當前工作線程,即創建異步請求的線程,而是從線程池中獲取一條線程執行網絡請求。同步請求則直接是在當前工作線程中執行。
  • 該類對異步請求的管理是通過maxRequests、maxRequestsPerHost進行控制的,前者控制線程池中同時運行的最大請求數,防止同時運行線程過多,造成OOM。後者限制了同一hostname下的請求數,防止一個應用佔用的網絡資源過多,優化用戶體驗。

文章的最後我們對okhttp中使用過程中遇到的Request、Response、OkHttpClient這幾個類進行一下介紹。

Request.class

該類中有如下域
  private final HttpUrl url;  //目標地址
  private final String method; //方法
  private final Headers headers; //請求頭,Headers.class裏面維護了一個private final String[] namesAndValues;數據集
  private final RequestBody body; //請求表單
  private final Object tag; //標籤
  private volatile URI javaNetUri; // Lazily initialized.
  private volatile CacheControl cacheControl; // Lazily initialized.
只能通過Builder方法構建Request對象。
public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
}
默認創建的是Get方法
public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
}
調用Request構造器,創建Request對象。

Response.class

類中有如下域
  private final Request request;  //對應的request
  private final Protocol protocol; //對應的Http協議
  private final int code; //返回狀態碼
  private final String message; //Http狀態對應的消息
  private final Handshake handshake; //TLS握手協議Transport Layer Security
  private final Headers headers; //返回響應頭
  private final ResponseBody body; //Http表單
  private Response networkResponse; //來源於網絡的Response,如果響應來自緩存,則該值爲null
  private Response cacheResponse; //來自緩存的響應
  private final Response priorResponse;  //在redirect或者授權改變的時候,該結果不爲空
  private volatile CacheControl cacheControl; // Lazily initialized.
Okhttp中有client.internalCache()和client.connectionPool()兩個重要的概念,前者管理網絡訪問的緩存信息,後者用於存儲已鏈接的RealConnection(該RealConnection已經跟對應的hostname完成了三次握手)。下面我們看一下創建Cache和ConnectionPool這兩個對象的OkHttpClient對象。

OkHttpClient.class

static {
    Internal.instance = new Internal() {
      @Override public void addLenient(Headers.Builder builder, String line) {
        builder.addLenient(line);
      }

      @Override public void addLenient(Headers.Builder builder, String name, String value) {
        builder.addLenient(name, value);
      }

      @Override public void setCache(OkHttpClient.Builder builder, InternalCache internalCache) {
        builder.setInternalCache(internalCache);
      }

      @Override public InternalCache internalCache(OkHttpClient client) {
        return client.internalCache();
      }

      @Override public boolean connectionBecameIdle(
          ConnectionPool pool, RealConnection connection) {
        return pool.connectionBecameIdle(connection);
      }

      @Override public RealConnection get(
          ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
        return pool.get(address, streamAllocation);
      }

      @Override public void put(ConnectionPool pool, RealConnection connection) {
        pool.put(connection);
      }

      @Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {
        return connectionPool.routeDatabase;
      }

      @Override
      public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {
        ((RealCall) call).enqueue(responseCallback, forWebSocket);
      }

      @Override public StreamAllocation callEngineGetStreamAllocation(Call call) {
        return ((RealCall) call).engine.streamAllocation;
      }

      @Override
      public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {
        tlsConfiguration.apply(sslSocket, isFallback);
      }

      @Override public HttpUrl getHttpUrlChecked(String url)
          throws MalformedURLException, UnknownHostException {
        return HttpUrl.getChecked(url);
      }
    };
  }
這一段代碼用static修飾,表明在加載OkHttpClient類時就會對Internal.instance進行初始化操作。
internalCache() @OkHttpClient.class
InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
}
cache域在我們構造OkHttpClient的時候是沒有被初始化的,因此如果我們沒有通過調用Builder的cache方法設置cache值的話,該方法返回的對象實際上是一個不支持任何緩存操作的對象,說着說該對象的所有方法爲空。因此如果需要OkHttpClient支持緩存,需要我們寫一個Cache對象並在構造OkHttpClient的時候將其傳給OkHttpClient。
cache()@Builder.class@OkHttpClient.class
public Builder cache(Cache cache) {
      this.cache = cache;
      this.internalCache = null;
      return this;
}
完成cache的初始化,如果不調用該方法那麼OkHttpClient默認不提供Cache功能。對於Cahce更爲詳細的介紹在後面的章節我們會進行詳細介紹。《OkHttp深入學習(三)——Cache》
connectionPool()@OkHttpClient.class
public ConnectionPool connectionPool() {
    return connectionPool; 
}
connectionPool的初始化是在構建OkHttpClient時創建的,調用的構造器爲new ConnectionPool()。對於ConnectionPool更爲詳細的介紹在後面的章節我們會進行詳細介紹。OkHttp深入學習(二)——網絡


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章