OkHttp应该是目前最完善,也是相当流行的一个底层网络请求库。Google都在用,所以有必要深入了解一下,刚好最近在重构公司项目的网络层,就顺便梳理一下。
———–12.29————
最近暂时没有时间详细整理了。就简单过了一下官方文档。
以下取自官方文档。
网络请求
同步Get方法
以下样例代码下载一个文件,打印headers,打印字符串形式的 response body
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
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()); }
|
注意: response body中的string()
方法对小文件是很方便高效的,但是当response body 大于1M时,避免用string()
,因为它会加载整个文件到内存,此时应该把body用stream的形式来处理.
异步Get方法
在工作线程下载一个文件,并且当response准备好了的时候回调Callback。 response headers 准备好了的时候,就走回调。读response body 也会阻塞线程,OkHttp暂时没有提供额外的异步
APIs来获得response 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
|
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, IOException 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>
那样工作,每个field有一个值或者没有,但是有的headers允许有多个值,就像Guava’s Multimap.比如,一个HTTP response 提供多个多样的headers是合法且普遍的。 OkHttp’s APIs 兼容这两种情况。
注意:当写request headers时,header(name, value)
设置唯一的键值对,这会覆盖已有的值。而用方法 addHeader(name, value)
来添加header时,不会移除已有的header。
相应的header(name)
来获取最后的这个name的相应value,通常这也是唯一的,如果没有就返回null 。headers(name)
用来read所有的field的值,已list的形式。
想要看所有的headers,使用支持通过索引访问的类Headers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
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")); }
|
Posting a String
由于这个request body同时要完全加载到内存,所以避免用个API来posting大于1M的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
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 Streaming
一下示例代码使用到了Okio的buffered sink,你可能更喜欢使用OutputStream,你可以通过BufferedSink.outputStream()
来获得。
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
|
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()); }
|
Posting文件
简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
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()); }
|
Posting form parameters(格式化参数)
用FormEncodingBuilder来构建一个request body,像HTML的
Posting一个multipart request(复合请求)
MultipartBuilder可以构建复杂的request bodies,兼容html文件上传forms。复合请求的每个request body分别可以定义自己的headers,如果存在,这些headers分别描述相应的body,例如Content-Disposition。The Content-Length and Content-Type headers 会自动添加。
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
|
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 {
|
用Gson解析JSON返回结果
Gson是一个便利的API用来转换JSON和java对象。
Note that ResponseBody.charStream() uses the Content-Type response header to select which charset to use when decoding the response body. It defaults to UTF-8 if no charset is specified.
注意ResponseBody.charStream()
根据返回头的Content-Type
来选择相应的编码方式,来解码response body。如果没有定义charset,默认是用UTF-8。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
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; }
|
Response Caching
缓存请求结果,你需要一个你可以读写的限制大小的缓存目录,缓存目录应该是私有的,禁止未信任的程序随便读取。
Response caching uses HTTP headers for all configuration. You can add request headers like Cache-Control: max-stale=3600 and OkHttp’s cache will honor them. Your webserver configures how long responses are cached with its own response headers, like Cache-Control: max-age=9600. 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.
Response caching完全用HTTP headers来配置,你可以添加像这样的请求头:Cache-Control: max-stale=3600,你的web服务器可以这样的请求头Cache-Control: max-age=9600来配置请求结果缓存时间。可以设置相应的headers来强制缓存response,force a network response, or force the network response to be validated(经过验证的) with a conditional GET.
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
|
private final OkHttpClient client;
public CacheResponse(File cacheDirectory) throws Exception { int cacheSize = 10 * 1024 * 1024;
|
阻止使用缓存的response,使用CacheControl.FORCE_NETWORK。阻止使用网络,使用 CacheControl.FORCE_CACHE。注意,如果你使用FORCE_CACHE而response需要网络支持的话,OkHttp 将返回一个 504 Unsatisfiable Request response.
取消一个call
用Call.cancel()会立刻停止一个进行中的请求,如果一个线程正在writing一个request,或者在reading一个response,将会抛IOException。
如果你的用户操作离开了app,所有的同步异步请求都应该取消。
你可以用tags来同时取消多个请求,用RequestBuilder.tag(tag)在创建请求的时候指定一个tag,用OkHttpClient.cancel(tag)
取消所有有这个tag的请求。
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
|
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")
|
Timeouts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
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")
|
Per-call Configuration 某个请求特殊配置
所有的HTTP client配置都在OkHttpClient,包括代理,超时,缓存,你想为单个请求改变配置的话,clone the OkHttpClient,这个会返回一个浅(shallow)copy让你来单独定制,如下,你make了一个500ms超时的和3000ms超时的请求。
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
|
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception { Request request = new Request.Builder() .url("http://httpbin.org/delay/1")
|
新版3.0修改为如下:
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
|
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception { Request request = new Request.Builder() .url("http://httpbin.org/delay/1")
|
Handling authentication(身份验证)
OkHttp可以自动重操作没有验证通过的请求,当一个response是401 Not Authorized(未授权,未认可),表示认证者需要你提供相应证书,实现是应该新建一个带有相应缺失证书的请求,如果获取不到证书,返回null跳过retry。
Use Response.challenges() to get the schemes and realms of any authentication challenges.
When fulfilling a Basic challenge, use Credentials.basic(username, password) to encode the request header.
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
|
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;
|
身份验证失效的时候就别多次retry了,return null放弃就好。
1 2 3
|
if (credential.equals(response.request().header("Authorization"))) { return null;
|
You may also skip the retry when you’ve hit an application-defined attempt limit:
1 2 3 4 5 6 7 8 9 10 11
|
if (responseCount(response) >= 3) { return null;
|