OkHttp使用進階 譯自OkHttp Github官方教程


版權聲明:

歡迎轉載,但請保留文章原始出處

作者:GavinCT

出處:http://www.cnblogs.com/ct2011/p/3997368.html

沒有使用過OkHttp的,可以先看OkHttp使用介紹

英文版原版地址

Recipes · square/okhttp Wiki

同步get

下載一個文件,打印他的響應頭,以string形式打印響應體。
響應體的 string() 方法對於小文檔來說十分方便、高效。但是如果響應體太大(超過1MB),應避免適應 string()方法 ,因為他會將把整個文檔加載到內存中。
對於超過1MB的響應body,應使用流的方式來處理body。

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	Request request = new Request.Builder()
	    .url("http://publicobject.com/helloworld.txt")
	    .build();

	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	Headers responseHeaders = response.headers();
	for (int i = 0; i < responseHeaders.size(); i++) {
	  System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
	}

	System.out.println(response.body().string());
}

異步get

在一個工作線程中下載文件,當響應可讀時回調Callback接口。讀取響應時會阻塞當前線程。OkHttp現階段不提供異步api來接收響應體。

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	Request request = new Request.Builder()
	    .url("http://publicobject.com/helloworld.txt")
	    .build();

	client.newCall(request).enqueue(new Callback() {
	  @Override public void onFailure(Request request, Throwable throwable) {
	    throwable.printStackTrace();
	  }

	  @Override public void onResponse(Response response) throws IOException {
	    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	    Headers responseHeaders = response.headers();
	    for (int i = 0; i < responseHeaders.size(); i++) {
	      System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
	    }

	    System.out.println(response.body().string());
	  }
	});
}

提取響應頭

典型的HTTP頭 像是一個 Map<String, String> :每個字段都有一個或沒有值。但是一些頭允許多個值,像Guava的Multimap。例如:HTTP響應里面提供的Vary響應頭,就是多值的。OkHttp的api試圖讓這些情況都適用。
當寫請求頭的時候,使用header(name, value)可以設置唯一的name、value。如果已經有值,舊的將被移除,然后添加新的。使用addHeader(name, value)可以添加多值(添加,不移除已有的)。
當讀取響應頭時,使用header(name)返回最后出現的name、value。通常情況這也是唯一的name、value。如果沒有值,那么header(name)將返回null。如果想讀取字段對應的所有值,使用headers(name)會返回一個list。
為了獲取所有的Header,Headers類支持按index訪問。

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	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();

	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	System.out.println("Server: " + response.header("Server"));
	System.out.println("Date: " + response.header("Date"));
	System.out.println("Vary: " + response.headers("Vary"));
}

Post方式提交String

使用HTTP POST提交請求到服務。這個例子提交了一個markdown文檔到web服務,以HTML方式渲染markdown。因為整個請求體都在內存中,因此避免使用此api提交大文檔(大於1MB)。

public static final MediaType MEDIA_TYPE_MARKDOWN
  = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	String postBody = ""
	    + "Releases\n"
	    + "--------\n"
	    + "\n"
	    + " * _1.0_ May 6, 2013\n"
	    + " * _1.1_ June 15, 2013\n"
	    + " * _1.2_ August 11, 2013\n";

	Request request = new Request.Builder()
	    .url("https://api.github.com/markdown/raw")
	    .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
	    .build();

	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	System.out.println(response.body().string());
}

Post方式提交流

以流的方式POST提交請求體。請求體的內容由流寫入產生。這個例子是流直接寫入Okio的BufferedSink。你的程序可能會使用OutputStream,你可以使用BufferedSink.outputStream()來獲取。

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	RequestBody requestBody = new RequestBody() {
	  @Override public MediaType contentType() {
	    return MEDIA_TYPE_MARKDOWN;
	  }

	  @Override public void writeTo(BufferedSink sink) throws IOException {
	    sink.writeUtf8("Numbers\n");
	    sink.writeUtf8("-------\n");
	    for (int i = 2; i <= 997; i++) {
	      sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
	    }
	  }

	  private String factor(int n) {
	    for (int i = 2; i < n; i++) {
	      int x = n / i;
	      if (x * i == n) return factor(x) + " × " + i;
	    }
	    return Integer.toString(n);
	  }
	};

	Request request = new Request.Builder()
	    .url("https://api.github.com/markdown/raw")
	    .post(requestBody)
	    .build();

	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	System.out.println(response.body().string());
}

Post方式提交文件

以文件作為請求體是十分簡單的。

public static final MediaType MEDIA_TYPE_MARKDOWN
  = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	File file = new File("README.md");

	Request request = new Request.Builder()
	    .url("https://api.github.com/markdown/raw")
	    .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
	    .build();

	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	System.out.println(response.body().string());
}

Post方式提交表單

使用FormEncodingBuilder來構建和HTML<form>標簽相同效果的請求體。鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼。

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	RequestBody formBody = new FormEncodingBuilder()
	    .add("search", "Jurassic Park")
	    .build();
	Request request = new Request.Builder()
	    .url("https://en.wikipedia.org/w/index.php")
	    .post(formBody)
	    .build();

	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	System.out.println(response.body().string());
}

Post方式提交分塊請求

MultipartBuilder可以構建復雜的請求體,與HTML文件上傳形式兼容。多塊請求體中每塊請求都是一個請求體,可以定義自己的請求頭。這些請求頭可以用來描述這塊請求,例如他的Content-Disposition。如果Content-LengthContent-Type可用的話,他們會被自動添加到請求頭中。

private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
	RequestBody requestBody = new MultipartBuilder()
	    .type(MultipartBuilder.FORM)
	    .addPart(
	        Headers.of("Content-Disposition", "form-data; name=\"title\""),
	        RequestBody.create(null, "Square Logo"))
	    .addPart(
	        Headers.of("Content-Disposition", "form-data; name=\"image\""),
	        RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
	    .build();

	Request request = new Request.Builder()
	    .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
	    .url("https://api.imgur.com/3/image")
	    .post(requestBody)
	    .build();

	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	System.out.println(response.body().string());
}

使用Gson來解析JSON響應

Gson是一個在JSON和Java對象之間轉換非常方便的api。這里我們用Gson來解析Github API的JSON響應。
注意:ResponseBody.charStream()使用響應頭Content-Type指定的字符集來解析響應體。默認是UTF-8。

private final OkHttpClient client = new OkHttpClient();
private final Gson gson = new Gson();

public void run() throws Exception {
	Request request = new Request.Builder()
	    .url("https://api.github.com/gists/c2a7c39532239ff261be")
	    .build();
	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
	for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
	  System.out.println(entry.getKey());
	  System.out.println(entry.getValue().content);
	}
}

static class Gist {
	Map<String, GistFile> files;
}

static class GistFile {
	String content;
}

響應緩存

為了緩存響應,你需要一個你可以讀寫的緩存目錄,和緩存大小的限制。這個緩存目錄應該是私有的,不信任的程序應不能讀取緩存內容。
一個緩存目錄同時擁有多個緩存訪問是錯誤的。大多數程序只需要調用一次new OkHttp(),在第一次調用時配置好緩存,然后其他地方只需要調用這個實例就可以了。否則兩個緩存示例互相干擾,破壞響應緩存,而且有可能會導致程序崩潰。
響應緩存使用HTTP頭作為配置。你可以在請求頭中添加Cache-Control: max-stale=3600 ,OkHttp緩存會支持。你的服務通過響應頭確定響應緩存多長時間,例如使用Cache-Control: max-age=9600

private final OkHttpClient client;

public CacheResponse(File cacheDirectory) throws Exception {
	int cacheSize = 10 * 1024 * 1024; // 10 MiB
	Cache cache = new Cache(cacheDirectory, cacheSize);

	client = new OkHttpClient();
	client.setCache(cache);
}

public void run() 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));
}

擴展

在這一節還提到了下面一句:
There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.

我不是很懂cache,平時用到的也不多,所以把Google在Android Developers一段相關的解析放到這里吧。

Force a Network Response

In some situations, such as after a user clicks a 'refresh' button, it may be necessary to skip the cache, and fetch data directly from the server. To force a full refresh, add the no-cache directive:

connection.addRequestProperty("Cache-Control", "no-cache");

If it is only necessary to force a cached response to be validated by the server, use the more efficient max-age=0 instead:

connection.addRequestProperty("Cache-Control", "max-age=0");

Force a Cache Response

Sometimes you'll want to show resources if they are available immediately, but not otherwise. This can be used so your application can show something while waiting for the latest data to be downloaded. To restrict a request to locally-cached resources, add the only-if-cached directive:

try {
     connection.addRequestProperty("Cache-Control", "only-if-cached");
     InputStream cached = connection.getInputStream();
     // the resource was cached! show it
  catch (FileNotFoundException e) {
     // the resource was not cached
 }
}

This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the max-stale directive with the maximum staleness in seconds:

int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);

以上信息來自:HttpResponseCache - Android SDK | Android Developers

取消一個Call

使用Call.cancel()可以立即停止掉一個正在執行的call。如果一個線程正在寫請求或者讀響應,將會引發IOException。當call沒有必要的時候,使用這個api可以節約網絡資源。例如當用戶離開一個應用時。不管同步還是異步的call都可以取消。
你可以通過tags來同時取消多個請求。當你構建一請求時,使用RequestBuilder.tag(tag)來分配一個標簽。之后你就可以用OkHttpClient.cancel(tag)來取消所有帶有這個tag的call。

private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private final OkHttpClient client = new OkHttpClient();

public void run() 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("%.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);
	}
}

超時

沒有響應時使用超時結束call。沒有響應的原因可能是客戶點鏈接問題、服務器可用性問題或者這之間的其他東西。OkHttp支持連接,讀取和寫入超時。

private final OkHttpClient client;

public ConfigureTimeouts() throws Exception {
	client = new OkHttpClient();
	client.setConnectTimeout(10, TimeUnit.SECONDS);
	client.setWriteTimeout(10, TimeUnit.SECONDS);
	client.setReadTimeout(30, TimeUnit.SECONDS);
}

public void run() throws Exception {
	Request request = new Request.Builder()
	    .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
	    .build();

	Response response = client.newCall(request).execute();
	System.out.println("Response completed: " + response);
}

每個call的配置

使用OkHttpClient,所有的HTTP Client配置包括代理設置、超時設置、緩存設置。當你需要為單個call改變配置的時候,clone 一個 OkHttpClient。這個api將會返回一個淺拷貝(shallow copy),你可以用來單獨自定義。下面的例子中,我們讓一個請求是500ms的超時、另一個是3000ms的超時。

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	Request request = new Request.Builder()
	    .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
	    .build();

	try {
	  Response response = client.clone() // Clone to make a customized OkHttp for this request.
	      .setReadTimeout(500, TimeUnit.MILLISECONDS)
	      .newCall(request)
	      .execute();
	  System.out.println("Response 1 succeeded: " + response);
	} catch (IOException e) {
	  System.out.println("Response 1 failed: " + e);
	}

	try {
	  Response response = client.clone() // Clone to make a customized OkHttp for this request.
	      .setReadTimeout(3000, TimeUnit.MILLISECONDS)
	      .newCall(request)
	      .execute();
	  System.out.println("Response 2 succeeded: " + response);
	} catch (IOException e) {
	  System.out.println("Response 2 failed: " + e);
	}
}

處理驗證

這部分和HTTP AUTH有關。
相關資料:HTTP AUTH 那些事 - 王紹全的博客 - 博客頻道 - CSDN.NET

OkHttp會自動重試未驗證的請求。當響應是401 Not Authorized時,Authenticator會被要求提供證書。Authenticator的實現中需要建立一個新的包含證書的請求。如果沒有證書可用,返回null來跳過嘗試。

public List<Challenge> challenges()
Returns the authorization challenges appropriate for this response's code. If the response code is 401 unauthorized, this returns the "WWW-Authenticate" challenges. If the response code is 407 proxy unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise this returns an empty list of challenges.

當需要實現一個Basic challenge, 使用Credentials.basic(username, password)來編碼請求頭。

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
	client.setAuthenticator(new Authenticator() {
	  @Override public Request authenticate(Proxy proxy, Response response) {
	    System.out.println("Authenticating for response: " + response);
	    System.out.println("Challenges: " + response.challenges());
	    String credential = Credentials.basic("jesse", "password1");
	    return response.request().newBuilder()
	        .header("Authorization", credential)
	        .build();
	  }

	  @Override public Request authenticateProxy(Proxy proxy, Response response) {
	    return null; // Null indicates no attempt to authenticate.
	  }
	});

	Request request = new Request.Builder()
	    .url("http://publicobject.com/secrets/hellosecret.txt")
	    .build();

	Response response = client.newCall(request).execute();
	if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

	System.out.println(response.body().string());
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM