盤點常用的Android開發庫(6) -- OkHttp

一、簡介

關於okhttp相信不做介紹,大家也都知道是幹嘛的,因爲它真的很常見。這裏就稍作介紹,OkHttp是基於Http協議的網絡請求框架,也是android端最火熱的輕量級框架之一。

它的主要優勢是:

  • 允許連接到同一個主機地址的所有請求,提高請求效率
  • 共享Socket,減少對服務器的請求次數
  • 通過連接池,減少了請求延遲
  • 緩存響應數據來減少重複的網絡請求
  • 減少了對數據流量的消耗
  • 自動處理GZip壓縮

說白了就是可以更加有效快速的通過Http請求獲取數據,並且能夠節省流量減小帶寬。

 

二、使用

2.1 依賴注入

compile 'com.squareup.okhttp3:okhttp:3.10.0'

我這裏的OkHttp3不是最新的版本,最新的可以在github上Okhttp官網查看。

 

2.2 如何使用

第一步,創建一個okHttpClient實例。

OkHttpClient okHttpClient = new OkHttpClient();

第二步,通過Builder獲取一個Request,這一步需要傳入請求的url和請求方式,如果是Post請求還需要在之前定義RequestBody,並傳入到Post方法中。

    //Get請求
    Request request = new Request.Builder()
        .url(url)
        .get()
        .build();


    //Post請求
    MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
    String requestBody = "I am Jdqm.";
    Request request = new Request.Builder()
        .url(url)
        .post(RequestBody.create(mediaType, requestBody))
        .build();

第三步,通過第一步創建okHttpClient的newCall方法獲取一個Call,並通過Call中的enqueue()方同步請求法(異步請求)或者execute()(同步請求)獲取Response響應體。

        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d(TAG, "onFailure: ");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String body = response.body().string();
                Log.d(TAG, "onResponse: "+body);
            }
        });

最後我們只需要通過解析處理Response即可得到想要的數據,至此一個完整的OkHttp請求結束。當然OKHttp還有一些其他的用法,這裏就不再一一贅述了,基本用法就是這些。

 

2.3 攔截器

攔截器是OkHttp的核心部分,在OkHttp最核心的請求方法中就是通過一組攔截器和責任鏈來實現完整的請求的,其中攔截器集合中第一個攔截器就是用戶自定義的攔截器,後面的原理分析會講到,這裏我們來說說如何給你的請求追加自定義的攔截器。

第一步,創建一個MyIntercepter來實現OkHttp的Intercepter接口。並在其中實現你想做的事情。

/**
 * 自定義的攔截器
 */

public class MyIntercepter implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        //獲取責任鏈中的請求對象
        Request request = chain.request();

        //TODO 這裏可以做一些事情 比如攔截特定的Url,打印請求時間等

        //通過責任鏈請求獲取響應體
        Response response = chain.proceed(request);
        return response;
    }
}

第二步,在創建OkHttp實例的時候通過Builder來構造,並在其中添加自定義的攔截器。

        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new MyIntercepter())
                .build();

後面的步驟就一樣啦。

 

三、原理分析

接下來我們來分析分析OkHttp的原理,我們跟着前面的提到的使用步驟走。首先我們來看一下OkHttpClient的構造函數。

  public OkHttpClient() {
    this(new Builder());
  }

  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    ... ...
  }

可以看到OkHttpClient構造函數中其實自動使用了Builder來構造OkHttpClient實例,不需要用戶自己構造,當然如果我們需要添加攔截器或者手動配置一些東西,則需要手動builder。

下面我們看下Builder中有哪些重要的屬性。

public static final class Builder {
    Dispatcher dispatcher;      //調度器  用以對異步請求進行調度策略
    @Nullable Proxy proxy;      //代理    封裝了http和socket
    List<Protocol> protocols;   //Http通信協議的集合,我們常用的是http/1.1協議
    List<ConnectionSpec> connectionSpecs; //連接配置的集合
    final List<Interceptor> interceptors = new ArrayList<>(); //攔截器的集合,這個很重要,自定義的攔截器也是添加在這個集合裏的
    final List<Interceptor> networkInterceptors = new ArrayList<>(); //網絡攔截器的集合,同上
    EventListener.Factory eventListenerFactory; //事件監聽器  我們可以通過擴展此類,來監聽應用程序調用Http請求的數量,大小和持續時間
    ProxySelector proxySelector;   //代理選擇器 選擇連接到服務器時的代理服務器
    CookieJar cookieJar;        //提供HTTP cookie的策略和持久性。
    @Nullable Cache cache;      //緩存
    @Nullable InternalCache internalCache;    //內部緩存
    SocketFactory socketFactory;   //socket工廠
    @Nullable SSLSocketFactory sslSocketFactory;    //ssl socket工廠
    @Nullable CertificateChainCleaner certificateChainCleaner;  //從Java的內置TLS API返回的原始數組中計算有效證書鏈
    HostnameVerifier hostnameVerifier;  //用以驗證主機名的基本接口
    CertificatePinner certificatePinner;  //證書相關
    Authenticator proxyAuthenticator;  //響應來自遠程Web服務器或代理服務器的身份驗證質詢
    Authenticator authenticator;
    ConnectionPool connectionPool;   //連接池  很重要  是OkHttp策略之一
    Dns dns;
    boolean followSslRedirects;   //是否遵循SSL重定向
    boolean followRedirects;      //是否遵循重定向
    boolean retryOnConnectionFailure;    //是否失敗重連
    int connectTimeout;        //連接超時時長
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    ... ...

}

屬性有點兒多,瞭解下就行其中最重要的就是攔截器和Dispatcher。

接下來我們看看是如何創建Request的,首先還是看下builder中的幾個屬性,沒什麼好說的。這裏創建Request就是通過建造者模式來配置並創建的。

public static class Builder {
    HttpUrl url;               //url
    String method;             //請求方式
    Headers.Builder headers;   //請求頭
    RequestBody body;          //請求體
    Object tag;

    ... ...

}

接下來的過程則是通過OkHttpClient的newCall方法來獲取Call。首先我們來看下newCall方法。

  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

可以看到這裏是直接調用RealCall的newRealCall方法。而這個RealCall則是最終獲取到的Call實例。

final class RealCall implements Call {
  final OkHttpClient client;
  final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;

  ... ...
    
  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.eventListener = client.eventListenerFactory().create(call);
    return call;
  }  

  ... ...

}

RealCall實現了Call接口,並且實現了execute()、enqueue()等方法。其中enqueue()是異步請求,execute()是同步請求。我們接下來繼續跟進enqueue()方法。

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

這裏利用同步鎖避免重複請求,然後調用eventListener.callStart記錄這次請求的開始狀態、時間等,最後在通過前面提到的調度器dispatcher的enqueue()方法進行調度,繼續追蹤。

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

 這裏就是對任務隊列做個判斷,判斷正在執行的請求是否小於最大請求數,同主機地址正在執行請求數是否小於最大同主機地址請求數。如果同時滿足,則將這個請求加入隊列中,並立刻執行,否則就只加入隊列中,等待調度器調度。

  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;
  }

最後通過線程池來執行請求任務,最終執行AsyncCall中的execute()開啓線程。

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        ... ...
      } catch (IOException e) {
        ... ...
      } finally {
        client.dispatcher().finished(this);
      }
    }

這裏OkHttp的核心部分來了,執行getResponseWithInterceptorChain()來獲取我們所需要的Response。繼續追蹤該方法。

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();      //攔截器集合
    interceptors.addAll(client.interceptors());              //加入用戶自定義的攔截器
    interceptors.add(retryAndFollowUpInterceptor);           //超時重連和重定向的攔截器
    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, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

這裏首先創建了一個攔截器集合,並加入了以上幾個攔截器,之前我們也提到了,用戶自定義的攔截器也是在其中的。最後創建一個責任鏈RealIntercepterChain,並把攔截器的集合傳入其中,最後調用了chain.proceed()方法。我們看下proceed方法做了些什麼。Intercepter接口中Chain接口的抽象方法,因此我們需要結合RealInterceptorChain來看。

在RealInterceptorChain中的proceed方法是這樣的(簡化後的)。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    
    ......

    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    ......

    return response;
  }

這裏就是創建一個新的責任鏈,並且傳入的下標+1,然後獲取當前下標的攔截器,並執行攔截器的intercept方法獲取Response。這裏我們需要結合實際的攔截器來看看intercept方法,我們以BridgeInterceptor爲例。

public final class BridgeInterceptor implements Interceptor {
  private final CookieJar cookieJar;

  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    
    ......
    //這裏省略的部分是BridgeIntercepter具體的功能,也就是將我們的請求轉換成服務器能看懂的網絡請求。

    Response networkResponse = chain.proceed(requestBuilder.build());

    ......

    return responseBuilder.build();
  }

  ......

}

可以看到在intercept方法就是攔截器的具體功能的實現部分,這裏被我省略了,有興趣的可以自行查看源代碼。在這裏最關鍵的一句代碼就是調用chain.proceed方法。並傳入了該攔截器已經處理好的請求體。接下來就是不斷的遞歸過程,直到處理完最後一個攔截器。最終可以通過proceed方法拿到我們所需要的Response。

以上就是OkHttp請求的核心思想,至此原理也分析的差不多了。如果對幾個攔截器中具體實現敢興趣的可以自己看下源碼,也便於更好地理解OkHttp。

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