OKHttp是一款高效的HTTP用戶端,支援串連同一地址的連結共用同一個socket,通過串連池來減小響應延遲,還有透明的GZIP壓縮,請求緩衝等優勢。(GitHub頁:https://github.com/square/okhttp)
Android為我們提供了兩種HTTP互動的方式:HttpURLConnection 和 Apache HTTP Client,雖然兩者都支援HTTPS,流的上傳和下載,配置逾時,IPv6和串連池,已足夠滿足我們各種HTTP請求的需求。但更高效的使用HTTP 可以讓您的應用運行更快、更節省流量。而OkHttp庫就是為此而生。
OkHttp是一個高效的HTTP庫:
- 支援 SPDY ,共用同一個Socket來處理同一個伺服器的所有請求
- 如果SPDY不可用,則通過串連池來減少請求延時
- 無縫的支援GZIP來減少資料流量
- 緩衝響應資料來減少重複的網路請求
- 會從很多常用的串連問題中自動回復。
如果您的伺服器配置了多個IP地址,當第一個IP串連失敗的時候,OkHttp會自動嘗試下一個IP。OkHttp還處理了Proxy 伺服器問題和SSL握手失敗問題。
使用 OkHttp 無需重寫您程式中的網路代碼。OkHttp實現了幾乎和java.net.HttpURLConnection一樣的API。如果您用了 Apache HttpClient,則OkHttp也提供了一個對應的okhttp-apache 模組。
引入
可以通過下載jar包直接匯入工程地址
或者通過構建的方式匯入
MAVEN:
<dependency> <groupId>com.squareup.okhttp</groupId> <artifactId>okhttp</artifactId> <version>2.4.0</version></dependency>
GRADLE:
compile 'com.squareup.okhttp:okhttp:2.4.0'
用法
在向網路發起請求的時候,我們最常用的就是GET和POST,下面就來看看如何使用
1. GET
在OKHttp,每次網路請求就是一個Request,我們在Request裡填寫我們需要的url,header等其他參數,再通過Request構造出Call,Call內部去請求參數,得到回複,並將結果告訴調用者。
package com.jackchan.test.okhttptest;import android.os.Bundle;import android.support.v7.app.ActionBarActivity;import android.util.Log;import com.squareup.okhttp.Cache;import com.squareup.okhttp.Callback;import com.squareup.okhttp.OkHttpClient;import com.squareup.okhttp.Request;import com.squareup.okhttp.Response;import java.io.File;import java.io.IOException;public class TestActivity extends ActionBarActivity { private final static String TAG = "TestActivity"; private final OkHttpClient client = new OkHttpClient(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); new Thread(new Runnable() { @Override public void run() { try { execute(); } catch (Exception e) { e.printStackTrace(); } } }).start(); } public void execute() throws Exception { Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); Response response = client.newCall(request).execute(); if(response.isSuccessful()){ System.out.println(response.code()); System.out.println(response.body().string()); } }}
我們通過Request.Builder傳入url,然後直接execute執行得到Response,通過Response可以得到code,message等資訊。
這個是通過同步的方式去操作網路請求,而android本身是不允許在UI線程做網路請求操作的,因此我們需要自己開啟一個線程。
當然,OKHttp也支援非同步線程並且有回調返回,有了上面同步的基礎,非同步只要稍加改動即可
private void enqueue(){ Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { } @Override public void onResponse(Response response) throws IOException { //NOT UI Thread if(response.isSuccessful()){ System.out.println(response.code()); System.out.println(response.body().string()); } } }); }
就是在同步的基礎上講execute改成enqueue,並且傳入回調介面,但介面回調回來的代碼是在非UI線程的,因此如果有更新UI的操作記得用Handler或者其他方式。
2、POST
說完GET該介紹些如何使用POST,POST情況下我們一般需要傳入參數,甚至一些header,傳入參數或者header
比如傳入header
Request request = new Request.Builder() .url("https://api.github.com/repos/square/okhttp/issues") .header("User-Agent", "OkHttp Headers.java") .addHeader("Accept", "application/json; q=0.5") .addHeader("Accept", "application/vnd.github.v3+json") .build();
傳入POST參數
RequestBody formBody = new FormEncodingBuilder() .add("platform", "android") .add("name", "bug") .add("subject", "XXXXXXXXXXXXXXX") .build(); Request request = new Request.Builder() .url(url) .post(body) .build();
可以看出來,傳入header或者post參數都是傳到Request裡面,因此最後的調用方式也和GET方式一樣
Response response = client.newCall(request).execute(); if (response.isSuccessful()) { return response.body().string(); } else { throw new IOException("Unexpected code " + response); }
這個代碼是同步網路請求,非同步就改成enqueue就行了。
請求緩衝
在網路請求中,緩衝技術是一項應用比較廣泛的技術,需要對請求過的網路資源進行緩衝,而okhttp也支援這一技術,也使用十分方便,前文漲經常出現的OkHttpclient這個時候就要派送用場了。看下面代碼
package com.jackchan.test.okhttptest;import android.os.Bundle;import android.support.v7.app.ActionBarActivity;import android.util.Log;import com.squareup.okhttp.Cache;import com.squareup.okhttp.CacheControl;import com.squareup.okhttp.Call;import com.squareup.okhttp.Callback;import com.squareup.okhttp.OkHttpClient;import com.squareup.okhttp.Request;import com.squareup.okhttp.Response;import java.io.File;import java.io.IOException;public class TestActivity extends ActionBarActivity { private final static String TAG = "TestActivity"; private final OkHttpClient client = new OkHttpClient(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); File sdcache = getExternalCacheDir(); int cacheSize = 10 * 1024 * 1024; // 10 MiB client.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize)); new Thread(new Runnable() { @Override public void run() { try { execute(); } catch (Exception e) { e.printStackTrace(); } } }).start(); } public void execute() throws Exception { Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); Response response1 = client.newCall(request).execute(); if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1); String response1Body = response1.body().string(); System.out.println("Response 1 response: " + response1); System.out.println("Response 1 cache response: " + response1.cacheResponse()); System.out.println("Response 1 network response: " + response1.networkResponse()); Response response2 = client.newCall(request).execute(); if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2); String response2Body = response2.body().string(); System.out.println("Response 2 response: " + response2); System.out.println("Response 2 cache response: " + response2.cacheResponse()); System.out.println("Response 2 network response: " + response2.networkResponse()); System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body)); }}
okhttpclient有點像Application的概念,統籌著整個okhttp的大功能,通過它設定緩衝目錄,我們執行上面的代碼,得到的結果如下
response1 的結果在networkresponse,代表是從網路請求載入過來的,而response2的networkresponse 就為null,而cacheresponse有資料,因為我設定了緩衝因此第二次請求時發現緩衝裡有就不再去走網路請求了。
但有時候即使在有緩衝的情況下我們依然需要去後台請求最新的資源(比如資源更新了)這個時候可以使用強制走網路來要求必須請求網路資料
public void execute() throws Exception { Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); Response response1 = client.newCall(request).execute(); if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1); String response1Body = response1.body().string(); System.out.println("Response 1 response: " + response1); System.out.println("Response 1 cache response: " + response1.cacheResponse()); System.out.println("Response 1 network response: " + response1.networkResponse()); request = request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build(); Response response2 = client.newCall(request).execute(); if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2); String response2Body = response2.body().string(); System.out.println("Response 2 response: " + response2); System.out.println("Response 2 cache response: " + response2.cacheResponse()); System.out.println("Response 2 network response: " + response2.networkResponse()); System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body)); }
上面的代碼中
response2對應的request變成
request = request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build();
我們看看運行結果
response2的cache response為null,network response依然有資料。
同樣的我們可以使用 FORCE_CACHE 強制只要使用緩衝的資料,但如果請求必須從網路擷取才有資料,但又使用了FORCE_CACHE 策略就會返回504錯誤,代碼如下,我們去okhttpclient的緩衝,並設定request為FORCE_CACHE
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); File sdcache = getExternalCacheDir(); int cacheSize = 10 * 1024 * 1024; // 10 MiB //client.setCache(new Cache(sdcache.getAbsoluteFile(), cacheSize)); new Thread(new Runnable() { @Override public void run() { try { execute(); } catch (Exception e) { e.printStackTrace(); System.out.println(e.getMessage().toString()); } } }).start(); } public void execute() throws Exception { Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); Response response1 = client.newCall(request).execute(); if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1); String response1Body = response1.body().string(); System.out.println("Response 1 response: " + response1); System.out.println("Response 1 cache response: " + response1.cacheResponse()); System.out.println("Response 1 network response: " + response1.networkResponse()); request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build(); Response response2 = client.newCall(request).execute(); if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2); String response2Body = response2.body().string(); System.out.println("Response 2 response: " + response2); System.out.println("Response 2 cache response: " + response2.cacheResponse()); System.out.println("Response 2 network response: " + response2.networkResponse()); System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body)); }
取消操作
網路操作中,經常會使用到對請求的cancel操作,okhttp的也提供了這方面的介面,call的cancel操作。使用Call.cancel()可以立即停止掉一個正在執行的call。如果一個線程正在寫請求或者讀響應,將會引發IOException,同時可以通過Request.Builder.tag(Object tag)給請求設定一個標籤,並使用OkHttpClient.cancel(Object tag)來取消所有帶有這個tag的call。但如果該請求已經在做讀寫操作的時候,cancel是無法成功的,會拋出IOException異常。
public void canceltest() throws Exception { Request request = new Request.Builder() .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay. .build(); final long startNanos = System.nanoTime(); final Call call = client.newCall(request); // Schedule a job to cancel the call in 1 second. executor.schedule(new Runnable() { @Override public void run() { System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f); call.cancel(); System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f); } }, 1, TimeUnit.SECONDS); try { System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f); Response response = call.execute(); System.out.printf("call is cancel:" + call.isCanceled() + "%n"); System.out.printf("%.2f Call was expected to fail, but completed: %s%n", (System.nanoTime() - startNanos) / 1e9f, response); } catch (IOException e) { System.out.printf("%.2f Call failed as expected: %s%n", (System.nanoTime() - startNanos) / 1e9f, e); } }
成功取消
取消失敗