OkHttp 同步異步操作
OkHttp是一個Java和Android的HTTP和HTTP/2的客戶端,負責發送HTTP請求以及接受HTTP響應。
一、使用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(); }
上面的代碼先創建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()); } });
同樣是創建OkHttpClient、Request和Call,只是調用了enqueue方法並在回調中處理響應。
上面介紹了同步、異步獲取請求的步驟,都是比較簡單的。
1.3、Request、Response、Call
上面的代碼中涉及到幾個常用的類:Request、Response和Call。下面分別介紹:
Request
每一個HTTP請求包含一個URL、一個方法(GET或POST或其他)、一些HTTP頭。請求還可能包含一個特定內容類型的數據類的主體部分。
Response
響應是對請求的回復,包含狀態碼、HTTP頭和主體部分。
重寫請求
當將Request提交給OkHttp後,出於正確性和效率的考慮,OkHttp在傳輸請求之前會重寫請求。
有些請求可能有緩存的響應。當緩存響應過時時,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; } }
上面的代碼中,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(); }
可以看到通過調用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(); }
下面是運行結果:
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/}
可以發現,攔截器運行了兩次。一次是初始請求”http://www.taobao.com“,一次是請求重定向”https://www.taobao.com“。
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(); } }; } }
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(); } };
三、總結
本篇文章主要介紹了OkHttp進行GET的同步、異步請求,對於HTTP其他方法,比如POST等都是可以進行的,這兒就不過多介紹了,想了解的朋友可以到OkHttp Github地址查看.
OkHttp 同步異步操作