Android網絡框架OkHttp之get請求(源碼初識)

歸納

OkHttp如今很火呀。因而上個星期就一直在學習OkHttp框架,雖說起來已經有點晚上手了,貌似是2013年就推出了。可是如今它版本更加穩定了呀。這不,說着說着,OkHttp3.3版本在這幾天又發佈了。如下以OkHttp3.2版本爲準,沒辦法,上個星期看的時候仍是以3.2爲最新版本的。首先,咱們要先了解一些背景,OkHttp這個框架是有Square公司推出的,進入官網。若是想看API,點擊進入API。大概瞭解了OkHttp以後,咱們應該知道OkHttp是一個網絡框架,想一想之前在開發中,網絡框架通常用的是什麼?很快咱們就會想到剛學習Android開發的時候接觸的HttpURLConnection和Apache提供的HttpClient這兩個類,而後就是後面推出的一些第三方網絡框架,好比2013年google推出的Volley框架、android-async-http框架、2014年很火的Xutils、以及如今不少人用的Retrofit等等。這麼多,到底選哪一個?一開始我也暈。後來看了一些資料,彷佛懂了一個概念:OkHttp是用來替換HttpURLConnection的,聽說android4.4源碼的HttpURLConnection就替換成了OkHttp。因此咱們別拿OkHttp和這些網絡框架比,這些網絡框架也只是基於HttpURLConnection進行一些封裝,使咱們的代碼更加簡潔方便。懂了這點,咱們應該就懂了爲何網上那麼多OkHttp和Volley或者Retrofit等等這些框架結合使用了,實際上是一個道理。那麼我用的HttpUrlConnection或者HttpClient用的好好的,幹嗎要用你的OkHttp?這裏就來比較下HttpURLConnection和OkHttp。至於HttpClient嘛,android6.0已經把它的API廢除了。用它還要引入org.apache.http.legacy.jar包,不值得,並且okhttp也已經提供了對應的okhttp-apache 模塊。php

HttpURLConnection和OkHttp的比較

  • HttpURLConnection有的API,OkHttp基本上都有(你有我有全都有呀,哈哈哈)
  • HttpURLConnection和OkHttp都支持Https,流的上傳和下載,超時,IP六、鏈接池等等
  • OkHttp比HttpURLConnection具備更好的同步異步請求、緩存機制,支持HttpDNS、重定向、Gzip壓縮,平臺適應性、很好的服務器IP的轉換、直接Socket通訊,支持攔截器等等。css

    看到這麼多機制,是否是以爲很強大,經過Socket直接通訊,以及很好的緩存機制,Gzip對於Http頭部的壓縮傳輸。天然對於網絡請求這塊使應用更加省流量、請求的更快。OkHttp對於Https和HttpDNS的支持,使得應用的網絡通訊安全性更高。固然說了它的好,如今也來講說它的 
    很差之處html

  • OkHttp不支持優先級請求
  • OkHttp不支持自簽名證書
  • OkHttp header中不能傳中文

雖然是很差的地方,可是OkHttp已經比較成熟了,網上解決這幾個問題的資料也不少了。因此這些都不是問題。java

一個簡單的Get請求例子

這裏咱們就以經典的官網提供的Get請求的例子來學習下,說大概的代碼。android

  • 先在manifest加個網絡權限,養成良好習慣
<uses-permission android:name="android.permission.INTERNET"/>
  • 1
  • 1

而後在build.gradle文件的dependencies添加庫以下:git

dependencies { compile 'com.squareup.okhttp3:okhttp:3.2.0' compile 'com.squareup.okio:okio:1.7.0' }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

同步Get請求:github

final OkHttpClient okHttpClient = new OkHttpClient()
                .newBuilder()
                .build();
        final Request request = new Request.Builder()
                .url("https://www.publicobject.com/helloworld.txt")
                .header("User-Agent","OkHttp Example")
                .build();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = okHttpClient.newCall(request).execute();
                    Log.d("zgx","response====="+response.body().string());
                    response.body().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

這個例子網上說爛了,沒啥說的,來看下結果 
這裏寫圖片描述算法

很漂亮的樣子。呵呵sql

異步Get請求: 
修改上面部分代碼,Call類調用enqueue方法。代碼以下:apache

new Thread(new Runnable() {
            @Override
            public void run() {
                    Call call = okHttpClient.newCall(request);
                    call.enqueue(new Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {
                            Log.d("zgx","response====="+e.getMessage());
                        }

                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            Log.d("zgx","response====="+response.body().string());
                            response.body().close();
                        }
                    });
            }
        }).start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

運行結果和上面同樣。只是多了一個CallBack

其實還有Post請求,文件上傳下載,圖片加載,攔截器的使用,支持session的保持這裏就先不說了。之後有時間再學習下。下面就是簡單的來看下他的源碼,只是以我的理解來分析下,看源碼前先來了解一些基本的知識。

一些基本的知識

  • Http

    Http是一種基於TCP/IP鏈接的一套網絡通訊協議,它是一種一應一答的請求,它分爲Get和Post請求,Get請求獲取得是靜態頁面,它能夠把參數放在URL字符串後面。而Post請求就不一樣了,它是把參數放在Http請求的正文的。 
    Get請求咱們會這樣請求:

private void HttpURLConnection_Get(){  
        try{  
            //經過openConnection 鏈接 
            URL url = new java.net.URL(URL);  
            urlConn=(HttpURLConnection)url.openConnection();  
            //設置輸入和輸出流 
            urlConn.setDoOutput(true);  
            urlConn.setDoInput(true);  
            //關閉鏈接 
            urlConn.disconnect();  
        }catch(Exception e){  
            resultData = "鏈接超時";  
        }  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

而後把獲取到的urlConn鏈接的數據經過IO流把讀取出來:

InputStreamReader in = new InputStreamReader(urlConn.getInputStream());    
                    BufferedReader buffer = new BufferedReader(in);    
                    String inputLine = null;    
                    while (((inputLine = buffer.readLine()) != null)){  
                        resultData += inputLine + "\n";    
                    }  
                    System.out.println(resultData);  
                    in.close();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Post請求則會這樣:

private void HttpURLConnection_Post(){  
        try{  
            //經過openConnection 鏈接 
            URL url = new java.net.URL(URL_Post);  
            urlConn=(HttpURLConnection)url.openConnection();  
            //設置輸入和輸出流 
            urlConn.setDoOutput(true);  
            urlConn.setDoInput(true);  

            urlConn.setRequestMethod("POST");  
            urlConn.setUseCaches(false);  
            // 配置本次鏈接的Content-type,配置爲application/x-www-form-urlencoded的 
            urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");    
            // 鏈接,從postUrl.openConnection()至此的配置必需要在connect以前完成, 
            // 要注意的是connection.getOutputStream會隱含的進行connect。 
            urlConn.connect();  
            //DataOutputStream流 
            DataOutputStream out = new DataOutputStream(urlConn.getOutputStream());  
            //要上傳的參數 
            String content = "par=" + URLEncoder.encode("ylx_Post+中正", "UTF_8");   
            //將要上傳的內容寫入流中 
            out.writeBytes(content);     
            //刷新、關閉 
            out.flush();  
            out.close();     

        }catch(Exception e){  
            resultData = "鏈接超時";  
        }  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

而後同上把獲取到的urlConn鏈接的數據經過IO流把讀取出來,大概的代碼就是這樣。

  • HTTPS 
    HTTP加入SSL便是HTTPS,它安全性更高,HTTP使用得端口號是80,而HTTPS使用的端口號是443。HTTP協議以明文方式發送內容,不提供任何方式的數據加密。HTTPS協議通常須要到CA申請證書,固然也能夠自簽名證書,和12306同樣。這裏就說下HTTPS的核心SSL和TLS。 
    首先咱們來看一張簡單的模型圖

這裏寫圖片描述

從這張圖咱們能夠看出,最左邊爲經典的ISO7層模型圖,右邊咱們能夠看到有一個SSL層,它又叫作安全套捷字層,它分爲SSL記錄協議和SSL握手協議。SSL位於傳輸層和應用層之間,其中SSL記錄 層協議位於傳輸層協議之上,而SSL握手協議又在SSL記錄協議之上。SSL記錄協議能夠爲高層協議進行加密,壓縮,封裝等功能,而SSL握手協議進行的是身份認證,協商加密算法、交換加密密鑰等。其中TLS和SSL相似,它創建在SSL3.0協議之上。主要的不一樣在於他們的加密算法不一樣,其餘功能做用相似。想要詳情看他們的區別,請看這篇文章SSL與TLS的區別以及介紹

基礎基本上講完了,如今就來講說OkHttp涉及到的一些知識了。支持SPDY協議和HTTP2.0協議,同步和異步請求,攔截機制,請求和響應的邏輯處理,緩存機制,重連和重定向機制,鏈接池,Gzip壓縮,安全性,平臺適應性等等。下面咱們就來經過源碼來一步步的學習。

源碼分析:

  • 支持哪些協議 
    既然是與服務器之間的通訊協議,咱們應該會想到Protocol這個類,這個類是幹嗎的呢?它主要是配置與遠程服務器的通訊協議。既然這樣,那咱們去源碼找下這個類。其實在OkHttpClient源碼裏面第一個屬性咱們就能夠看到
private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
      Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
  • 1
  • 2
  • 1
  • 2

再進去Protocol類

public enum Protocol {
  HTTP_1_0("http/1.0"),
  HTTP_1_1("http/1.1"),
  SPDY_3("spdy/3.1"),
  HTTP_2("h2");
  //省略部分代碼
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

進入這個類,咱們發現,這是一個枚舉類,它定義了一些和遠程服務器通訊的協議名稱,如上面四種。 
而後回到OkHttpClient這個類,跟蹤protocols這個屬性,咱們會找到這個方法:

public Builder protocols(List<Protocol> protocols) {
      protocols = Util.immutableList(protocols);
      if (!protocols.contains(Protocol.HTTP_1_1)) {
        throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
      }
      if (protocols.contains(Protocol.HTTP_1_0)) {
        throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols);
      }
      if (protocols.contains(null)) {
        throw new IllegalArgumentException("protocols must not contain null");
      }
      this.protocols = Util.immutableList(protocols);
      return this;
    }

    public Builder connectionSpecs(List<ConnectionSpec> connectionSpecs) {
      this.connectionSpecs = Util.immutableList(connectionSpecs);
      return this;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

咱們會發現,OkHttp是支持http/1.1版本的,可是不支持http/1.0版本的協議,支持h2協議,以及spdy/3.1協議。並且協議名稱不能爲null。既然支持h2,就說明服務端支持 ALPN的,它將能夠協商到協商到 HTTP/2。這個很好呀,好在哪裏呢,咱們能夠看下這篇文章,爲何咱們應該儘快支持 ALPN

  • 同步和異步請求 
    這個其實上面已經說到過了,OkHttp能夠經過調用execute()實現同步請求,而後經過enqueue()方法能夠實現異步請求。從上面的請求代碼咱們能夠知道,它是經過okHttpClient.newCall(request)獲得一個Call對象來調用的。那如今咱們就先來看下Call這個類。
public interface Call {
  //初始化這個請求而且返回這個請求
  Request request();
  //同步方法
  Response execute() throws IOException;
  //異步方法
  void enqueue(Callback responseCallback);
  //取消請求,完成的請求則不能取消
  void cancel();
//省略部分代碼
  interface Factory {
    Call newCall(Request request);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

它是一個接口類,裏面包含同步方法和異步方法,咱們還能夠看到定義了一個內部接口Factory ,實現這個接口,經過newCall方法返回Call對象,也就是上面咱們調用的OkHttpClient.newCall(Request request),由於OkHttpClient對象已經實現這個接口。那麼咱們就回到OkHttpClient對象裏面的newCall(Request request)方法裏面。

@Override 
public Call newCall(Request request) {
    return new RealCall(this, request);
  }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

建立了一個RealCall對象,那麼同步異步方法確定就在這裏面實現了,繼續來看看,RealCall實現了Call接口,果真是在這裏,接下來看同步和異步方法。 
同步方法:

@Override 
  public Response execute() throws IOException {
  //同步操做,若是這個請求已經請求完了,則直接拋異常返回
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
    //經過dispatcher類來實現同步請求
      client.dispatcher().executed(this);
      //攔截器,下文再說
      Response result = getResponseWithInterceptorChain(false);
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

先無論Dispatcher類,先來看看異步請求方法:

void enqueue(Callback responseCallback, boolean forWebSocket) {
 //同步操做,若是這個請求已經請求完了,則直接拋異常返回,省略這裏代碼
 //而後是發出異步請求,也是經過Dispatcher類。並且這裏增長了一個callback
    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

仍是等下來看Dispatcher類,先來看下AsyncCall接口,實現了NamedRunnable這個線程,這個線程會把當前」OkHttp 「+請求url設置爲當前線程名。接下來就是看下核心的execute抽象方法的實現

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
      //也是會來到攔截器裏面,下文說
        Response response = getResponseWithInterceptorChain(forWebSocket);
        //若是這個請求已經取消,則直接調用callback的失敗接口。
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
         //直接調用callback的成功接口。
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
         //異常,調用失敗接口
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
      //異常,調用完成接口
        client.dispatcher().finished(this);
      }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

其實主要仍是回到攔截器裏面,這裏先不說攔截器,其餘的也就是callback的邏輯處理。好了,如今就來講Dispatcher這個類了。上面咱們說了同步和異步方法裏面會去調用Dispatcher類的executed(this)和enqueue(AsyncCall call)方法。先來看下Dispatcher類的executed(this)

synchronized void executed(RealCall call) { runningSyncCalls.add(call);
  }
  synchronized void finished(Call call) { if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Dispatcher類經過一個同步隊列runningSyncCalls會幫我保存同步的請求Call。而後在完成請求以後再finished裏面remove掉,其實runningSyncCalls的做用就是統計當前有多少個同步請求,其餘做用還沒發現。 
再來看下enqueue(AsyncCall call)方法:

synchronized void enqueue(AsyncCall call) { //當前異步請求量小於請求量的最大值64,而且請求同一個host服務器地址小於5的條件下 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { //把請求的AsyncCall記錄到正在執行的請求隊列runningAsyncCalls,而且經過線程池去執行這個請求。 runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
    /*超過最大請求量則添加到後備隊列裏面,等前面請求完成的時候,也就是調用finished(AsyncCall call)的時候。經過promoteCalls方法把readyAsyncCalls的請求添加到runningAsyncCalls執行*/
      readyAsyncCalls.add(call);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

把readyAsyncCalls的請求添加到runningAsyncCalls執行方法以下

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.
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上面的代碼應該都看的懂。

  • 攔截器機制 
    先說說攔截器是用來幹嗎的吧。六個字:監控,修改,添加。能夠監控請求和響應日誌,能夠修改http請求信息,好比將域名替換爲ip地址,將請求頭中添加host屬性。能夠添加咱們應用中的一些公共參數,好比設備id、版本號等等。 
    先來個官網提供的監控請求和響應日誌吧
class LoggingInterceptors implements Interceptor{
        private final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());
        @Override
        public Response intercept(Chain chain) throws IOException {

            long t1 = System.nanoTime();
            Request request = chain.request();
            logger.info(String.format("Sending request %s on %s%n%s",
                    request.url(), chain.connection(), request.headers()));
            Response response = chain.proceed(request);
            long t2 = System.nanoTime();
            logger.info(String.format("Received response for %s in %.1fms%n%s",
                    response.request().url(), (t2 - t1) / 1e6d, response.headers()));
            return response;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

而後建立OkHttp的時候這樣建立就行了

new OkHttpClient()
                .newBuilder()
                .addNetworkInterceptor(new LoggingInterceptors())
                .build();
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

代碼就不解釋了,網上不少這個例子。 
再簡單說下添加公共參數的例子。

class LoggingInterceptors implements Interceptor{
        @Override
        public Response intercept(Chain chain) throws IOException {

            Request original = chain.request();
            //添加請求頭,能夠添加多個參數,或者在original.body()http的body裏面添加參數
            Request.Builder requestBuilder = original.newBuilder()
                    .addHeader("device", Build.DEVICE)
                    .method(original.method(),original.body());
            Request request = requestBuilder.build();
            return chain.proceed(request);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面只是一個簡單的參考,能夠參考Okhttp中如何在攔截器中的RequestBody添加參數?,咱們也能夠和源碼裏面同樣經過Builder的形式,向外公開一些add方法。 
其實網上也挺多這方面的例子的,攔截器仍是很好用的,下面咱們就來學下OkHttp源碼裏面的攔截器 
經過上面例子能夠知道chain.proceed(request)直接而後的是http的響應請求,那麼如今就來這個方法看下吧。

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;
  interface Chain {
    Request request();
    Response proceed(Request request) throws IOException;
    Connection connection();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

咱們能夠看到有proceed這個方法。有兩個攔截器實現這個接口,一個是應用攔截器ApplicationInterceptorChain,一個是網絡攔截器NetworkInterceptorChain。那麼怎麼理解這兩種攔截器呢,這裏咱們先來debug下。 
先來debug下addInterceptor這個方法,也就是應用攔截器。 
這裏寫圖片描述

斷點地方如圖,咱們能夠看下調用proceed()方法的地方只有一個,也就是ApplicationInterceptorChain。而ApplicationInterceptorChain的proceed方法裏面調用intercept方法,而NetworkInterceptorChain的proceed方法沒走,這也是爲何上面例子打印一次的緣由。若是不信的話,能夠去NetworkInterceptorChain的proceed斷點調試下,它是不走那裏的。接下來咱們就來看下.addNetworkInterceptor(),來調試下。

這裏寫圖片描述
這裏有兩個地方調用了proceed(),說明了ApplicationInterceptorChain的proceed方法裏面也調用intercept方法,能夠去ApplicationInterceptorChain的proceed斷點調試下,它仍是會走那裏。這也是上面例子打印兩次的緣由

因此能夠把它理解爲應用攔截器,是http發送請求的時候進行一次攔截,它不會走網絡進行請求的。而網絡攔截器咱們能夠把它看作是服務器響應的時候進行的一次攔截,它會走網絡請求的。能夠結合下圖來理解 
此圖來源於網絡 
來看下應用攔截器ApplicationInterceptorChain的process方法

@Override public Response proceed(Request request) throws IOException {
      //若是攔截鏈裏面還有攔截器,則調用這裏,也就是遞歸處理
      if (index < client.interceptors().size()) {
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        Interceptor interceptor = client.interceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }

      //若是沒有攔截器,則繼續進行http請求
      return getResponse(request, forWebSocket);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

繼續來看下網絡攔截器NetworkInterceptorChain的proceed方法。

@Override public Response proceed(Request request) throws IOException {
    //統計proceed調用次數
      calls++;

      if (index > 0) {
        Interceptor caller = client.networkInterceptors().get(index - 1);
        Address address = connection().route().address();

        //每次遞歸都會去判斷url是否包含host和port。
        if (!request.url().host().equals(address.url().host())
            || request.url().port() != address.url().port()) {
          throw new IllegalStateException("network interceptor " + caller
              + " must retain the same host and port");
        }

        // Confirm that this is the interceptor's first call to chain.proceed().
        if (calls > 1) {
          throw new IllegalStateException("network interceptor " + caller
              + " must call proceed() exactly once");
        }
      }
        //遞歸NetworkInterceptorChain,直到攔截鏈裏面沒有攔截器
      if (index < client.networkInterceptors().size()) {
        NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request);
        Interceptor interceptor = client.networkInterceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        // Confirm that the interceptor made the required call to chain.proceed().
        if (chain.calls != 1) {
          throw new IllegalStateException("network interceptor " + interceptor
              + " must call proceed() exactly once");
        }
        if (interceptedResponse == null) {
          throw new NullPointerException("network interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }
        //把request寫入http頭部裏面
      httpStream.writeRequestHeaders(request);

      //更新的networkRequest,可能攔截已經請求更新
      networkRequest = request;
        //這裏是對post請求進行的一些body寫入
      if (permitsRequestBody(request) && request.body() != null) {
        Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    //經過io包進行一些io流操做
      Response response = readNetworkResponse();

      int code = response.code();
      if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
        throw new ProtocolException(
            "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
      }

      return response;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

其實攔截器還有個stetho工具,集成它使用攔截器獲取日誌就更方便了。集成教程網上有不少。

  • 請求和響應的邏輯處理 
    其實上面咱們在proceed方法裏面已經提到了這塊,它是經過getResponse()方法返回Response的,其實發送網絡請求和獲得返回的響應都是在這裏面進行邏輯處理的,若是中途取消請求的時候,getResponse()返回null
Response getResponse(Request request, boolean forWebSocket) throws IOException {
//...省略前面代碼
      try {
      //發送請求
        engine.sendRequest();
        //回覆的響應
        engine.readResponse();
        releaseConnection = false;
      }
//...省略後面代碼
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

從上面能夠知道,請求和響應的邏輯處理是在HttpEngine對象的,接下來就來看下HttpEngine對象 
這裏是發送一個請求方法

public void sendRequest() throws RequestException, RouteException, IOException {
  //...省略部分代碼
  //經過Request.Builder設置Request的配置信息,而後返回配置好的Request 對象
    Request request = networkRequest(userRequest);
    //...省略部分代碼
    //將request 傳入緩存的處理類裏面進行一些緩存處理,而後返回networkRequest ,其實和request 同樣
    networkRequest = cacheStrategy.networkRequest;
  //...省略部分代碼
    try {
    //真正的經過socket通訊發送請求出去
      httpStream = connect();
      httpStream.setHttpEngine(this);
        //若是是post或者帶有body的請求方式,執行下面部分寫出body
      if (writeRequestHeaders()) {
        long contentLength = OkHeaders.contentLength(request);
        if (bufferRequestBody) {
          if (contentLength > Integer.MAX_VALUE) {
            throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
                + "setChunkedStreamingMode() for requests larger than 2 GiB.");
          }

          if (contentLength != -1) {
            // Buffer a request body of a known length.
            httpStream.writeRequestHeaders(networkRequest);
            requestBodyOut = new RetryableSink((int) contentLength);
          } else {
            // Buffer a request body of an unknown length. Don't write request headers until the // entire body is ready; otherwise we can't set the Content-Length header correctly.
            requestBodyOut = new RetryableSink();
          }
        } else {
          httpStream.writeRequestHeaders(networkRequest);
          requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
        }
      }
      success = true;
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (!success && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

讀取響應的方法

public void readResponse() throws IOException {
 //...省略部分代碼
    if (forWebSocket) {
      httpStream.writeRequestHeaders(networkRequest);
      networkResponse = readNetworkResponse();
    } else if (!callerWritesRequestBody) {

      networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
    } else {
      // Emit the request body's buffer so that everything is in requestBodyOut.
      if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
        bufferedRequestBody.emit();
      }
 //...省略部分代碼
    //其實真正的咱們仍是經過這個方式來獲取響應數據的
      networkResponse = readNetworkResponse();
    }
 //...省略部分代碼
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

接下來就來看看readNetworkResponse這個方法

private Response readNetworkResponse() throws IOException {
    httpStream.finishRequest();
    //這裏經過io流去讀取響應的數據
    Response networkResponse = httpStream.readResponseHeaders()
        .request(networkRequest)
        .handshake(streamAllocation.connection().handshake())
        .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
        .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
        .build();

    if (!forWebSocket) {
      networkResponse = networkResponse.newBuilder()
          .body(httpStream.openResponseBody(networkResponse))
          .build();
    }

    if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
        || "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    return networkResponse;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

其實發送請求這塊還有不少源碼的處理,其餘的就先不看了,基本上是一些io流的處理了。

  • 緩存處理 
    先來看看OkHttp的緩存怎麼設置
int cacheSize = 10 * 1024 * 1024; // 10 MiB
   //cacheDirectory保存緩存的目錄,cacheSize緩存空間的大小
   Cache cache = new Cache(context.getCacheDir(), cacheSize);
   final OkHttpClient okHttpClient = new OkHttpClient()
           .newBuilder()
           .cache(cache)
           .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這樣咱們就設置好了。下面咱們就來分析下源碼,其實在上面發送請求和讀取響應方法裏面已經有緩存處理的邏輯。回到sendRequest()方法

public void sendRequest() throws RequestException, RouteException, IOException {
    //...省略部分代碼
    /*Internal是一個抽象類,定義了不少個抽象類,其中就有setCache(OkHttpClient.Builder builder, InternalCache internalCache)這個方法,而後.internalCache(client)其實它會去調用OkHttpClient裏的static塊裏的Internal的internalCache方法,返回一個InternalCache*/
    InternalCache responseCache = Internal.instance.internalCache(client);
    //Cache類獲取緩存裏面的響應數據
    Response cacheCandidate = responseCache != null
        ? responseCache.get(request)
        : null;

    long now = System.currentTimeMillis();
    //建立CacheStrategy.Factory對象,進行緩存配置
    cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
    //傳入的網絡鏈接
    networkRequest = cacheStrategy.networkRequest;
    //cacheCandidate 傳入CacheStrategy後獲得的緩存的響應數據
    cacheResponse = cacheStrategy.cacheResponse;

    if (responseCache != null) {
    //記錄當前請求是網絡發起仍是緩存發起
      responseCache.trackResponse(cacheStrategy);
    }
    //若是傳入CacheStrategy不可用而且cacheResponse 爲null,結束全部請求鏈接資源
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }
    // 若是網絡鏈接被禁止訪問而且緩存爲null的時候
    if (networkRequest == null && cacheResponse == null) {
      userResponse = new Response.Builder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_BODY)
          .build();
      return;
    }

    // 若是沒有網絡的狀況下,這時候緩存是不爲null的,因此這裏就去獲取緩存裏面的數據
    if (networkRequest == null) {
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .cacheResponse(stripBody(cacheResponse))
          .build();
      userResponse = unzip(userResponse);
      return;
    }

     //...省略部分代碼
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

上面的返回的InternalCache 接口定義在了Cache這個類裏面,咱們能夠看到OkHttp使用的緩存是DiskLruCache,詳細緩存處理就不說了。

public void readResponse() throws IOException {
    //...省略部分代碼
    if (cacheResponse != null) {
     //檢查緩存是否可用,若是可用。那麼就用當前緩存的Response,關閉網絡鏈接,釋放鏈接。
      if (validate(cacheResponse, networkResponse)) {
        userResponse = cacheResponse.newBuilder()
            .request(userRequest)
            .priorResponse(stripBody(priorResponse))
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();
        releaseStreamAllocation();

        // headers去掉Content-Encoding以後更新緩存
        InternalCache responseCache = Internal.instance.internalCache(client);
        responseCache.trackConditionalCacheHit();
        responseCache.update(cacheResponse, stripBody(userResponse));
        userResponse = unzip(userResponse);
        return;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    userResponse = networkResponse.newBuilder()
        .request(userRequest)
        .priorResponse(stripBody(priorResponse))
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (hasBody(userResponse)) {
      maybeCache();
      userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

其實緩存這塊還不是特別理解,因爲篇幅比較長了,並且這篇是初識篇,其實還有不少沒去學習,好比重連和重定向機制,鏈接池,Gzip壓縮,安全性,平臺適應性,cookie的保持等等的知識,下次抽時間再來學習下了。

歸納

OkHttp如今很火呀。因而上個星期就一直在學習OkHttp框架,雖說起來已經有點晚上手了,貌似是2013年就推出了。可是如今它版本更加穩定了呀。這不,說着說着,OkHttp3.3版本在這幾天又發佈了。如下以OkHttp3.2版本爲準,沒辦法,上個星期看的時候仍是以3.2爲最新版本的。首先,咱們要先了解一些背景,OkHttp這個框架是有Square公司推出的,進入官網。若是想看API,點擊進入API。大概瞭解了OkHttp以後,咱們應該知道OkHttp是一個網絡框架,想一想之前在開發中,網絡框架通常用的是什麼?很快咱們就會想到剛學習Android開發的時候接觸的HttpURLConnection和Apache提供的HttpClient這兩個類,而後就是後面推出的一些第三方網絡框架,好比2013年google推出的Volley框架、android-async-http框架、2014年很火的Xutils、以及如今不少人用的Retrofit等等。這麼多,到底選哪一個?一開始我也暈。後來看了一些資料,彷佛懂了一個概念:OkHttp是用來替換HttpURLConnection的,聽說android4.4源碼的HttpURLConnection就替換成了OkHttp。因此咱們別拿OkHttp和這些網絡框架比,這些網絡框架也只是基於HttpURLConnection進行一些封裝,使咱們的代碼更加簡潔方便。懂了這點,咱們應該就懂了爲何網上那麼多OkHttp和Volley或者Retrofit等等這些框架結合使用了,實際上是一個道理。那麼我用的HttpUrlConnection或者HttpClient用的好好的,幹嗎要用你的OkHttp?這裏就來比較下HttpURLConnection和OkHttp。至於HttpClient嘛,android6.0已經把它的API廢除了。用它還要引入org.apache.http.legacy.jar包,不值得,並且okhttp也已經提供了對應的okhttp-apache 模塊。

HttpURLConnection和OkHttp的比較

  • HttpURLConnection有的API,OkHttp基本上都有(你有我有全都有呀,哈哈哈)
  • HttpURLConnection和OkHttp都支持Https,流的上傳和下載,超時,IP六、鏈接池等等
  • OkHttp比HttpURLConnection具備更好的同步異步請求、緩存機制,支持HttpDNS、重定向、Gzip壓縮,平臺適應性、很好的服務器IP的轉換、直接Socket通訊,支持攔截器等等。

    看到這麼多機制,是否是以爲很強大,經過Socket直接通訊,以及很好的緩存機制,Gzip對於Http頭部的壓縮傳輸。天然對於網絡請求這塊使應用更加省流量、請求的更快。OkHttp對於Https和HttpDNS的支持,使得應用的網絡通訊安全性更高。固然說了它的好,如今也來講說它的 
    很差之處

  • OkHttp不支持優先級請求
  • OkHttp不支持自簽名證書
  • OkHttp header中不能傳中文

雖然是很差的地方,可是OkHttp已經比較成熟了,網上解決這幾個問題的資料也不少了。因此這些都不是問題。

一個簡單的Get請求例子

這裏咱們就以經典的官網提供的Get請求的例子來學習下,說大概的代碼。

  • 先在manifest加個網絡權限,養成良好習慣
<uses-permission android:name="android.permission.INTERNET"/>
  • 1
  • 1

而後在build.gradle文件的dependencies添加庫以下:

dependencies { compile 'com.squareup.okhttp3:okhttp:3.2.0' compile 'com.squareup.okio:okio:1.7.0' }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

同步Get請求:

final OkHttpClient okHttpClient = new OkHttpClient()
                .newBuilder()
                .build();
        final Request request = new Request.Builder()
                .url("https://www.publicobject.com/helloworld.txt")
                .header("User-Agent","OkHttp Example")
                .build();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = okHttpClient.newCall(request).execute();
                    Log.d("zgx","response====="+response.body().string());
                    response.body().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

這個例子網上說爛了,沒啥說的,來看下結果 
這裏寫圖片描述

很漂亮的樣子。呵呵

異步Get請求: 
修改上面部分代碼,Call類調用enqueue方法。代碼以下:

new Thread(new Runnable() {
            @Override
            public void run() {
                    Call call = okHttpClient.newCall(request);
                    call.enqueue(new Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {
                            Log.d("zgx","response====="+e.getMessage());
                        }

                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            Log.d("zgx","response====="+response.body().string());
                            response.body().close();
                        }
                    });
            }
        }).start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

運行結果和上面同樣。只是多了一個CallBack

其實還有Post請求,文件上傳下載,圖片加載,攔截器的使用,支持session的保持這裏就先不說了。之後有時間再學習下。下面就是簡單的來看下他的源碼,只是以我的理解來分析下,看源碼前先來了解一些基本的知識。

一些基本的知識

  • Http

    Http是一種基於TCP/IP鏈接的一套網絡通訊協議,它是一種一應一答的請求,它分爲Get和Post請求,Get請求獲取得是靜態頁面,它能夠把參數放在URL字符串後面。而Post請求就不一樣了,它是把參數放在Http請求的正文的。 
    Get請求咱們會這樣請求:

private void HttpURLConnection_Get(){  
        try{  
            //經過openConnection 鏈接 
            URL url = new java.net.URL(URL);  
            urlConn=(HttpURLConnection)url.openConnection();  
            //設置輸入和輸出流 
            urlConn.setDoOutput(true);  
            urlConn.setDoInput(true);  
            //關閉鏈接 
            urlConn.disconnect();  
        }catch(Exception e){  
            resultData = "鏈接超時";  
        }  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

而後把獲取到的urlConn鏈接的數據經過IO流把讀取出來:

InputStreamReader in = new InputStreamReader(urlConn.getInputStream());    
                    BufferedReader buffer = new BufferedReader(in);    
                    String inputLine = null;    
                    while (((inputLine = buffer.readLine()) != null)){  
                        resultData += inputLine + "\n";    
                    }  
                    System.out.println(resultData);  
                    in.close();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Post請求則會這樣:

private void HttpURLConnection_Post(){  
        try{  
            //經過openConnection 鏈接 
            URL url = new java.net.URL(URL_Post);  
            urlConn=(HttpURLConnection)url.openConnection();  
            //設置輸入和輸出流 
            urlConn.setDoOutput(true);  
            urlConn.setDoInput(true);  

            urlConn.setRequestMethod("POST");  
            urlConn.setUseCaches(false);  
            // 配置本次鏈接的Content-type,配置爲application/x-www-form-urlencoded的 
            urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");    
            // 鏈接,從postUrl.openConnection()至此的配置必需要在connect以前完成, 
            // 要注意的是connection.getOutputStream會隱含的進行connect。 
            urlConn.connect();  
            //DataOutputStream流 
            DataOutputStream out = new DataOutputStream(urlConn.getOutputStream());  
            //要上傳的參數 
            String content = "par=" + URLEncoder.encode("ylx_Post+中正", "UTF_8");   
            //將要上傳的內容寫入流中 
            out.writeBytes(content);     
            //刷新、關閉 
            out.flush();  
            out.close();     

        }catch(Exception e){  
            resultData = "鏈接超時";  
        }  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

而後同上把獲取到的urlConn鏈接的數據經過IO流把讀取出來,大概的代碼就是這樣。

  • HTTPS 
    HTTP加入SSL便是HTTPS,它安全性更高,HTTP使用得端口號是80,而HTTPS使用的端口號是443。HTTP協議以明文方式發送內容,不提供任何方式的數據加密。HTTPS協議通常須要到CA申請證書,固然也能夠自簽名證書,和12306同樣。這裏就說下HTTPS的核心SSL和TLS。 
    首先咱們來看一張簡單的模型圖

這裏寫圖片描述

從這張圖咱們能夠看出,最左邊爲經典的ISO7層模型圖,右邊咱們能夠看到有一個SSL層,它又叫作安全套捷字層,它分爲SSL記錄協議和SSL握手協議。SSL位於傳輸層和應用層之間,其中SSL記錄 層協議位於傳輸層協議之上,而SSL握手協議又在SSL記錄協議之上。SSL記錄協議能夠爲高層協議進行加密,壓縮,封裝等功能,而SSL握手協議進行的是身份認證,協商加密算法、交換加密密鑰等。其中TLS和SSL相似,它創建在SSL3.0協議之上。主要的不一樣在於他們的加密算法不一樣,其餘功能做用相似。想要詳情看他們的區別,請看這篇文章SSL與TLS的區別以及介紹

基礎基本上講完了,如今就來講說OkHttp涉及到的一些知識了。支持SPDY協議和HTTP2.0協議,同步和異步請求,攔截機制,請求和響應的邏輯處理,緩存機制,重連和重定向機制,鏈接池,Gzip壓縮,安全性,平臺適應性等等。下面咱們就來經過源碼來一步步的學習。

源碼分析:

  • 支持哪些協議 
    既然是與服務器之間的通訊協議,咱們應該會想到Protocol這個類,這個類是幹嗎的呢?它主要是配置與遠程服務器的通訊協議。既然這樣,那咱們去源碼找下這個類。其實在OkHttpClient源碼裏面第一個屬性咱們就能夠看到
private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
      Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
  • 1
  • 2
  • 1
  • 2

再進去Protocol類

public enum Protocol {
  HTTP_1_0("http/1.0"),
  HTTP_1_1("http/1.1"),
  SPDY_3("spdy/3.1"),
  HTTP_2("h2");
  //省略部分代碼
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

進入這個類,咱們發現,這是一個枚舉類,它定義了一些和遠程服務器通訊的協議名稱,如上面四種。 
而後回到OkHttpClient這個類,跟蹤protocols這個屬性,咱們會找到這個方法:

public Builder protocols(List<Protocol> protocols) {
      protocols = Util.immutableList(protocols);
      if (!protocols.contains(Protocol.HTTP_1_1)) {
        throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
      }
      if (protocols.contains(Protocol.HTTP_1_0)) {
        throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols);
      }
      if (protocols.contains(null)) {
        throw new IllegalArgumentException("protocols must not contain null");
      }
      this.protocols = Util.immutableList(protocols);
      return this;
    }

    public Builder connectionSpecs(List<ConnectionSpec> connectionSpecs) {
      this.connectionSpecs = Util.immutableList(connectionSpecs);
      return this;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

咱們會發現,OkHttp是支持http/1.1版本的,可是不支持http/1.0版本的協議,支持h2協議,以及spdy/3.1協議。並且協議名稱不能爲null。既然支持h2,就說明服務端支持 ALPN的,它將能夠協商到協商到 HTTP/2。這個很好呀,好在哪裏呢,咱們能夠看下這篇文章,爲何咱們應該儘快支持 ALPN

  • 同步和異步請求 
    這個其實上面已經說到過了,OkHttp能夠經過調用execute()實現同步請求,而後經過enqueue()方法能夠實現異步請求。從上面的請求代碼咱們能夠知道,它是經過okHttpClient.newCall(request)獲得一個Call對象來調用的。那如今咱們就先來看下Call這個類。
public interface Call {
  //初始化這個請求而且返回這個請求
  Request request();
  //同步方法
  Response execute() throws IOException;
  //異步方法
  void enqueue(Callback responseCallback);
  //取消請求,完成的請求則不能取消
  void cancel();
//省略部分代碼
  interface Factory {
    Call newCall(Request request);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

它是一個接口類,裏面包含同步方法和異步方法,咱們還能夠看到定義了一個內部接口Factory ,實現這個接口,經過newCall方法返回Call對象,也就是上面咱們調用的OkHttpClient.newCall(Request request),由於OkHttpClient對象已經實現這個接口。那麼咱們就回到OkHttpClient對象裏面的newCall(Request request)方法裏面。

@Override 
public Call newCall(Request request) {
    return new RealCall(this, request);
  }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

建立了一個RealCall對象,那麼同步異步方法確定就在這裏面實現了,繼續來看看,RealCall實現了Call接口,果真是在這裏,接下來看同步和異步方法。 
同步方法:

@Override 
  public Response execute() throws IOException {
  //同步操做,若是這個請求已經請求完了,則直接拋異常返回
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
    //經過dispatcher類來實現同步請求
      client.dispatcher().executed(this);
      //攔截器,下文再說
      Response result = getResponseWithInterceptorChain(false);
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

先無論Dispatcher類,先來看看異步請求方法:

void enqueue(Callback responseCallback, boolean forWebSocket) {
 //同步操做,若是這個請求已經請求完了,則直接拋異常返回,省略這裏代碼
 //而後是發出異步請求,也是經過Dispatcher類。並且這裏增長了一個callback
    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

仍是等下來看Dispatcher類,先來看下AsyncCall接口,實現了NamedRunnable這個線程,這個線程會把當前」OkHttp 「+請求url設置爲當前線程名。接下來就是看下核心的execute抽象方法的實現

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
      //也是會來到攔截器裏面,下文說
        Response response = getResponseWithInterceptorChain(forWebSocket);
        //若是這個請求已經取消,則直接調用callback的失敗接口。
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
         //直接調用callback的成功接口。
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
         //異常,調用失敗接口
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
      //異常,調用完成接口
        client.dispatcher().finished(this);
      }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

其實主要仍是回到攔截器裏面,這裏先不說攔截器,其餘的也就是callback的邏輯處理。好了,如今就來講Dispatcher這個類了。上面咱們說了同步和異步方法裏面會去調用Dispatcher類的executed(this)和enqueue(AsyncCall call)方法。先來看下Dispatcher類的executed(this)

synchronized void executed(RealCall call) { runningSyncCalls.add(call);
  }
  synchronized void finished(Call call) { if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Dispatcher類經過一個同步隊列runningSyncCalls會幫我保存同步的請求Call。而後在完成請求以後再finished裏面remove掉,其實runningSyncCalls的做用就是統計當前有多少個同步請求,其餘做用還沒發現。 
再來看下enqueue(AsyncCall call)方法:

synchronized void enqueue(AsyncCall call) { //當前異步請求量小於請求量的最大值64,而且請求同一個host服務器地址小於5的條件下 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { //把請求的AsyncCall記錄到正在執行的請求隊列runningAsyncCalls,而且經過線程池去執行這個請求。 runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
    /*超過最大請求量則添加到後備隊列裏面,等前面請求完成的時候,也就是調用finished(AsyncCall call)的時候。經過promoteCalls方法把readyAsyncCalls的請求添加到runningAsyncCalls執行*/
      readyAsyncCalls.add(call);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

把readyAsyncCalls的請求添加到runningAsyncCalls執行方法以下

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.
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上面的代碼應該都看的懂。

  • 攔截器機制 
    先說說攔截器是用來幹嗎的吧。六個字:監控,修改,添加。能夠監控請求和響應日誌,能夠修改http請求信息,好比將域名替換爲ip地址,將請求頭中添加host屬性。能夠添加咱們應用中的一些公共參數,好比設備id、版本號等等。 
    先來個官網提供的監控請求和響應日誌吧
class LoggingInterceptors implements Interceptor{
        private final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());
        @Override
        public Response intercept(Chain chain) throws IOException {

            long t1 = System.nanoTime();
            Request request = chain.request();
            logger.info(String.format("Sending request %s on %s%n%s",
                    request.url(), chain.connection(), request.headers()));
            Response response = chain.proceed(request);
            long t2 = System.nanoTime();
            logger.info(String.format("Received response for %s in %.1fms%n%s",
                    response.request().url(), (t2 - t1) / 1e6d, response.headers()));
            return response;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

而後建立OkHttp的時候這樣建立就行了

new OkHttpClient()
                .newBuilder()
                .addNetworkInterceptor(new LoggingInterceptors())
                .build();
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

代碼就不解釋了,網上不少這個例子。 
再簡單說下添加公共參數的例子。

class LoggingInterceptors implements Interceptor{
        @Override
        public Response intercept(Chain chain) throws IOException {

            Request original = chain.request();
            //添加請求頭,能夠添加多個參數,或者在original.body()http的body裏面添加參數
            Request.Builder requestBuilder = original.newBuilder()
                    .addHeader("device", Build.DEVICE)
                    .method(original.method(),original.body());
            Request request = requestBuilder.build();
            return chain.proceed(request);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面只是一個簡單的參考,能夠參考Okhttp中如何在攔截器中的RequestBody添加參數?,咱們也能夠和源碼裏面同樣經過Builder的形式,向外公開一些add方法。 
其實網上也挺多這方面的例子的,攔截器仍是很好用的,下面咱們就來學下OkHttp源碼裏面的攔截器 
經過上面例子能夠知道chain.proceed(request)直接而後的是http的響應請求,那麼如今就來這個方法看下吧。

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;
  interface Chain {
    Request request();
    Response proceed(Request request) throws IOException;
    Connection connection();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

咱們能夠看到有proceed這個方法。有兩個攔截器實現這個接口,一個是應用攔截器ApplicationInterceptorChain,一個是網絡攔截器NetworkInterceptorChain。那麼怎麼理解這兩種攔截器呢,這裏咱們先來debug下。 
先來debug下addInterceptor這個方法,也就是應用攔截器。 
這裏寫圖片描述

斷點地方如圖,咱們能夠看下調用proceed()方法的地方只有一個,也就是ApplicationInterceptorChain。而ApplicationInterceptorChain的proceed方法裏面調用intercept方法,而NetworkInterceptorChain的proceed方法沒走,這也是爲何上面例子打印一次的緣由。若是不信的話,能夠去NetworkInterceptorChain的proceed斷點調試下,它是不走那裏的。接下來咱們就來看下.addNetworkInterceptor(),來調試下。

這裏寫圖片描述
這裏有兩個地方調用了proceed(),說明了ApplicationInterceptorChain的proceed方法裏面也調用intercept方法,能夠去ApplicationInterceptorChain的proceed斷點調試下,它仍是會走那裏。這也是上面例子打印兩次的緣由

因此能夠把它理解爲應用攔截器,是http發送請求的時候進行一次攔截,它不會走網絡進行請求的。而網絡攔截器咱們能夠把它看作是服務器響應的時候進行的一次攔截,它會走網絡請求的。能夠結合下圖來理解 
此圖來源於網絡 
來看下應用攔截器ApplicationInterceptorChain的process方法

@Override public Response proceed(Request request) throws IOException {
      //若是攔截鏈裏面還有攔截器,則調用這裏,也就是遞歸處理
      if (index < client.interceptors().size()) {
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        Interceptor interceptor = client.interceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }

      //若是沒有攔截器,則繼續進行http請求
      return getResponse(request, forWebSocket);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

繼續來看下網絡攔截器NetworkInterceptorChain的proceed方法。

@Override public Response proceed(Request request) throws IOException {
    //統計proceed調用次數
      calls++;

      if (index > 0) {
        Interceptor caller = client.networkInterceptors().get(index - 1);
        Address address = connection().route().address();

        //每次遞歸都會去判斷url是否包含host和port。
        if (!request.url().host().equals(address.url().host())
            || request.url().port() != address.url().port()) {
          throw new IllegalStateException("network interceptor " + caller
              + " must retain the same host and port");
        }

        // Confirm that this is the interceptor's first call to chain.proceed().
        if (calls > 1) {
          throw new IllegalStateException("network interceptor " + caller
              + " must call proceed() exactly once");
        }
      }
        //遞歸NetworkInterceptorChain,直到攔截鏈裏面沒有攔截器
      if (index < client.networkInterceptors().size()) {
        NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request);
        Interceptor interceptor = client.networkInterceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        // Confirm that the interceptor made the required call to chain.proceed().
        if (chain.calls != 1) {
          throw new IllegalStateException("network interceptor " + interceptor
              + " must call proceed() exactly once");
        }
        if (interceptedResponse == null) {
          throw new NullPointerException("network interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }
        //把request寫入http頭部裏面
      httpStream.writeRequestHeaders(request);

      //更新的networkRequest,可能攔截已經請求更新
      networkRequest = request;
        //這裏是對post請求進行的一些body寫入
      if (permitsRequestBody(request) && request.body() != null) {
        Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    //經過io包進行一些io流操做
      Response response = readNetworkResponse();

      int code = response.code();
      if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
        throw new ProtocolException(
            "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
      }

      return response;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

其實攔截器還有個stetho工具,集成它使用攔截器獲取日誌就更方便了。集成教程網上有不少。

  • 請求和響應的邏輯處理 
    其實上面咱們在proceed方法裏面已經提到了這塊,它是經過getResponse()方法返回Response的,其實發送網絡請求和獲得返回的響應都是在這裏面進行邏輯處理的,若是中途取消請求的時候,getResponse()返回null
Response getResponse(Request request, boolean forWebSocket) throws IOException {
//...省略前面代碼
      try {
      //發送請求
        engine.sendRequest();
        //回覆的響應
        engine.readResponse();
        releaseConnection = false;
      }
//...省略後面代碼
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

從上面能夠知道,請求和響應的邏輯處理是在HttpEngine對象的,接下來就來看下HttpEngine對象 
這裏是發送一個請求方法

public void sendRequest() throws RequestException, RouteException, IOException {
  //...省略部分代碼
  //經過Request.Builder設置Request的配置信息,而後返回配置好的Request 對象
    Request request = networkRequest(userRequest);
    //...省略部分代碼
    //將request 傳入緩存的處理類裏面進行一些緩存處理,而後返回networkRequest ,其實和request 同樣
    networkRequest = cacheStrategy.networkRequest;
  //...省略部分代碼
    try {
    //真正的經過socket通訊發送請求出去
      httpStream = connect();
      httpStream.setHttpEngine(this);
        //若是是post或者帶有body的請求方式,執行下面部分寫出body
      if (writeRequestHeaders()) {
        long contentLength = OkHeaders.contentLength(request);
        if (bufferRequestBody) {
          if (contentLength > Integer.MAX_VALUE) {
            throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
                + "setChunkedStreamingMode() for requests larger than 2 GiB.");
          }

          if (contentLength != -1) {
            // Buffer a request body of a known length.
            httpStream.writeRequestHeaders(networkRequest);
            requestBodyOut = new RetryableSink((int) contentLength);
          } else {
            // Buffer a request body of an unknown length. Don't write request headers until the // entire body is ready; otherwise we can't set the Content-Length header correctly.
            requestBodyOut = new RetryableSink();
          }
        } else {
          httpStream.writeRequestHeaders(networkRequest);
          requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
        }
      }
      success = true;
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (!success && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

讀取響應的方法

public void readResponse() throws IOException {
 //...省略部分代碼
    if (forWebSocket) {
      httpStream.writeRequestHeaders(networkRequest);
      networkResponse = readNetworkResponse();
    } else if (!callerWritesRequestBody) {

      networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
    } else {
      // Emit the request body's buffer so that everything is in requestBodyOut.
      if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
        bufferedRequestBody.emit();
      }
 //...省略部分代碼
    //其實真正的咱們仍是經過這個方式來獲取響應數據的
      networkResponse = readNetworkResponse();
    }
 //...省略部分代碼
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

接下來就來看看readNetworkResponse這個方法

private Response readNetworkResponse() throws IOException {
    httpStream.finishRequest();
    //這裏經過io流去讀取響應的數據
    Response networkResponse = httpStream.readResponseHeaders()
        .request(networkRequest)
        .handshake(streamAllocation.connection().handshake())
        .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
        .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
        .build();

    if (!forWebSocket) {
      networkResponse = networkResponse.newBuilder()
          .body(httpStream.openResponseBody(networkResponse))
          .build();
    }

    if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
        || "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    return networkResponse;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

其實發送請求這塊還有不少源碼的處理,其餘的就先不看了,基本上是一些io流的處理了。

  • 緩存處理 
    先來看看OkHttp的緩存怎麼設置
int cacheSize = 10 * 1024 * 1024; // 10 MiB
   //cacheDirectory保存緩存的目錄,cacheSize緩存空間的大小
   Cache cache = new Cache(context.getCacheDir(), cacheSize);
   final OkHttpClient okHttpClient = new OkHttpClient()
           .newBuilder()
           .cache(cache)
           .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這樣咱們就設置好了。下面咱們就來分析下源碼,其實在上面發送請求和讀取響應方法裏面已經有緩存處理的邏輯。回到sendRequest()方法

public void sendRequest() throws RequestException, RouteException, IOException {
    //...省略部分代碼
    /*Internal是一個抽象類,定義了不少個抽象類,其中就有setCache(OkHttpClient.Builder builder, InternalCache internalCache)這個方法,而後.internalCache(client)其實它會去調用OkHttpClient裏的static塊裏的Internal的internalCache方法,返回一個InternalCache*/
    InternalCache responseCache = Internal.instance.internalCache(client);
    //Cache類獲取緩存裏面的響應數據
    Response cacheCandidate = responseCache != null
        ? responseCache.get(request)
        : null;

    long now = System.currentTimeMillis();
    //建立CacheStrategy.Factory對象,進行緩存配置
    cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
    //傳入的網絡鏈接
    networkRequest = cacheStrategy.networkRequest;
    //cacheCandidate 傳入CacheStrategy後獲得的緩存的響應數據
    cacheResponse = cacheStrategy.cacheResponse;

    if (responseCache != null) {
    //記錄當前請求是網絡發起仍是緩存發起
      responseCache.trackResponse(cacheStrategy);
    }
    //若是傳入CacheStrategy不可用而且cacheResponse 爲null,結束全部請求鏈接資源
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }
    // 若是網絡鏈接被禁止訪問而且緩存爲null的時候
    if (networkRequest == null && cacheResponse == null) {
      userResponse = new Response.Builder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_BODY)
          .build();
      return;
    }

    // 若是沒有網絡的狀況下,這時候緩存是不爲null的,因此這裏就去獲取緩存裏面的數據
    if (networkRequest == null) {
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .cacheResponse(stripBody(cacheResponse))
          .build();
      userResponse = unzip(userResponse);
      return;
    }

     //...省略部分代碼
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

上面的返回的InternalCache 接口定義在了Cache這個類裏面,咱們能夠看到OkHttp使用的緩存是DiskLruCache,詳細緩存處理就不說了。

public void readResponse() throws IOException {
    //...省略部分代碼
    if (cacheResponse != null) {
     //檢查緩存是否可用,若是可用。那麼就用當前緩存的Response,關閉網絡鏈接,釋放鏈接。
      if (validate(cacheResponse, networkResponse)) {
        userResponse = cacheResponse.newBuilder()
            .request(userRequest)
            .priorResponse(stripBody(priorResponse))
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();
        releaseStreamAllocation();

        // headers去掉Content-Encoding以後更新緩存
        InternalCache responseCache = Internal.instance.internalCache(client);
        responseCache.trackConditionalCacheHit();
        responseCache.update(cacheResponse, stripBody(userResponse));
        userResponse = unzip(userResponse);
        return;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    userResponse = networkResponse.newBuilder()
        .request(userRequest)
        .priorResponse(stripBody(priorResponse))
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (hasBody(userResponse)) {
      maybeCache();
      userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

其實緩存這塊還不是特別理解,因爲篇幅比較長了,並且這篇是初識篇,其實還有不少沒去學習,好比重連和重定向機制,鏈接池,Gzip壓縮,安全性,平臺適應性,cookie的保持等等的知識,下次抽時間再來學習下了。

相關文章
相關標籤/搜索