1. 程式人生 > >okhttp 同步請求和非同步請求

okhttp 同步請求和非同步請求

一、使用OkHttp

OkHttp傳送請求後,可以通過同步或非同步地方式獲取響應。下面就同步和非同步兩種方式進行介紹。

1.1、同步方式

傳送請求後,就會進入阻塞狀態,知道收到響應。下面看一個下載百度首頁的例子:

OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        Request request = new Request.Builder().url("http://www.baidu.com")
                .get().build
(); Call call = client.newCall(request); try { Response response = call.execute(); System.out.println(response.body().string()); } catch (IOException e) { e.printStackTrace(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面的程式碼先建立OkHttpClient和Request物件,兩者均使用了Builder模式;然後將Request封裝成Call物件,然後呼叫Call的execute()同步傳送請求,最後列印響應。

1.2、非同步方式

非同步方式是在回撥中處理響應的,同樣看下載百度首頁的例子:

 OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        Request request = new Request.Builder().url("http://www.baidu.com")
                .get().build();
        Call call = client.newCall(request);
        call.enqueue
(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println("Fail"); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println(response.body().string()); } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

同樣是建立OkHttpClient、Request和Call,只是呼叫了enqueue方法並在回撥中處理響應。 
上面介紹了同步、非同步獲取請求的步驟,都是比較簡單的。

1.3、Request、Response、Call

上面的程式碼中涉及到幾個常用的類:Request、Response和Call。下面分別介紹: 
Request 
每一個HTTP請求包含一個URL、一個方法(GET或POST或其他)、一些HTTP頭。請求還可能包含一個特定內容型別的資料類的主體部分。 
Response 
響應是對請求的回覆,包含狀態碼、HTTP頭和主體部分。 
重寫請求 
當將Request提交給OkHttp後,出於正確性和效率的考慮,OkHttp在傳輸請求之前會重寫請求。 
OkHttp可能會在請求中新增缺少的請求頭,包括”Content-Length”,”Transfer-Encoding”,”User-Agent”,”HOST”,”Connection”和”Content-Type”等。 
有些請求可能有快取的響應。當快取響應過時時,OkHttp可以做一個額外的GET請求獲取最新的響應。這要求”If-Modified-Since”和”If-None-Match”頭被新增。 
重寫響應 
如果使用了透明壓縮,OkHttp會丟棄”Content-Encoding”和”Content-Length”頭,因為和解壓後的響應主體不匹配。 
如果一個額外的GET請求成功了,那麼網路和快取中的響應將會合並。 
請求重定向 
當請求的URL移動了,web伺服器會返回一個302的狀態碼並指明檔案的新地址。OkHttp將會重定向獲取最終的響應。 
請求重試 
有時連線會失敗,那麼OkHttp會重試別的路由。 
Call 
當重寫、重定向等時,一個請求可能會產生多個請求和響應。OkHttp使用Call抽象出一個滿足請求的模型,儘管中間可能會有多個請求或響應。執行Call有兩種方式,同步或非同步,這在上面已經介紹過了。 
Call可以在任何執行緒被取消。

二、攔截器

攔截器是一個監視、重寫、重試請求的強有力機制。攔截器可以串聯。 
攔截器原理 
從圖中可以看出,攔截器分為應用攔截器和網路攔截器兩種。應用攔截器是在傳送請求之前和獲取到響應之後進行操作的,網路攔截器是在進行網路獲取前進行操作的。

2.1、應用攔截器

下面定義一個應用攔截器,用於在請求傳送前列印URL以及接受到響應後列印內容。

public class LogInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {

        Request request = chain.request();

        System.out.println(request.toString());

        Response response = chain.proceed(request);

        System.out.println(response);

        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

上面的程式碼中,LogInterceptor實現了Interceptor介面。首先從chain中得到請求,然後列印請求;然後呼叫proceed方法處理請求得到響應,然後列印響應。呼叫程式碼如下:

 OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new LogInterceptor()).build();
        Request request = new Request.Builder().url("http://www.baidu.com")
                .get().build();
        Call call = okHttpClient.newCall(request);
        try {
            call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以看到通過呼叫addInterceptor方法新增應用攔截器。

2.2、網路攔截器

網路攔截器的使用和應用攔截器類似,只是呼叫OkHttpClient的addNetworkInterceptor方法即可。

OkHttpClient okHttpClient = new OkHttpClient.Builder().addNetworkInterceptor(new LogInterceptor()).build();
        Request request = new Request.Builder().url("http://www.taobao.com")
                .get().build();
        Call call = okHttpClient.newCall(request);
        try {
            call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

下面是執行結果:

Request{method=GET, url=http://www.taobao.com/, tag=Request{method=GET, url=http://www.taobao.com/, tag=null}}
Response{protocol=http/1.1, code=302, message=Found, url=http://www.taobao.com/}
Request{method=GET, url=https://www.taobao.com/, tag=Request{method=GET, url=http://www.taobao.com/, tag=null}}
Response{protocol=http/1.1, code=200, message=OK, url=https://www.taobao.com/}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

2.3、應用攔截器和網路攔截器的比較

每個攔截器由它各自的優勢。 
應用攔截器 
- 不需要考慮中間狀態的響應,比如重定向或者重試。 
- 只會被呼叫一次,甚至於HTTP響應儲存在快取中。 
- 觀察應用程式的原意。 
- 允許短路,可以不呼叫Chain.proceed()方法 
- 允許重試和傳送多條請求,呼叫Chain.proceed()方法 
網路攔截器 
- 可以操作中間狀態的響應,比如重定向和重試 
- 不呼叫快取的響應 
- 可以觀察整個網路上傳輸的資料 
- 獲得攜帶請求的Connection

2.4、重寫請求

攔截器可以新增、移除或者替換請求的頭資訊,也可以改變傳輸的主體部分。下面的一個攔截器對請求主體進行Gzip壓縮。

final class GzipRequestInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request originalRequest = chain.request();
    if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
      return chain.proceed(originalRequest);
    }

    Request compressedRequest = originalRequest.newBuilder()
        .header("Content-Encoding", "gzip")
        .method(originalRequest.method(), gzip(originalRequest.body()))
        .build();
    return chain.proceed(compressedRequest);
  }

  private RequestBody gzip(final RequestBody body) {
    return new RequestBody() {
      @Override public MediaType contentType() {
        return body.contentType();
      }

      @Override public long contentLength() {
        return -1; // We don't know the compressed length in advance!
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
        body.writeTo(gzipSink);
        gzipSink.close();
      }
    };
  }
}
  • 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
  • 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

2.5、重寫響應

同樣地,攔截器可以重寫響應的頭部以及主體部分。但是

/** Dangerous interceptor that rewrites the server's cache-control header. */
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Response originalResponse = chain.proceed(chain.request());
    return originalResponse.newBuilder()
        .header("Cache-Control", "max-age=60")
        .build();
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

備註:        

備註:

  • 同步互動:指傳送一個請求,需要等待返回,然後才能夠傳送下一個請求,有個等待過程;

  • 非同步互動:指傳送一個請求,不需要等待返回,隨時可以再發送下一個請求,即不需要等待。 

  • 區別:一個需要等待,一個不需要等待,在部分情況下,我們的專案開發中都會優先選擇不需要等待的非同步互動方式。

  • 哪些情況建議使用同步互動呢?比如銀行的轉賬系統,對資料庫的儲存操作等等,都會使用同步互動操作,其餘情況都優先使用非同步互動。

三、總結

本篇文章主要介紹了OkHttp進行GET的同步、非同步請求,對於HTTP其他方法,比如POST等都是可以進行的,這兒就不過多介紹了,想了解的朋友可以到OkHttp Github地址檢視.