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

Accessing Headers

典型的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的

标签一样工作,键值对将以兼容HTML的URL编码方式被组织。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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());
}

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 {
// 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是一个便利的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; // 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));
}

阻止使用缓存的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") // 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);
}
}

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") // This URL is served with a 2 second delay.
.build(); Response response = client.newCall(request).execute();
System.out.println("Response completed: " + response);
}

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") // This URL is served with a 1 second delay.
.build(); try {
OkHttpClient cloned = client.clone(); // Clone to make a customized OkHttp for this request.
cloned.setReadTimeout(500, TimeUnit.MILLISECONDS); Response response = cloned.newCall(request).execute();
System.out.println("Response 1 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 1 failed: " + e);
} try {
OkHttpClient cloned = client.clone(); // Clone to make a customized OkHttp for this request.
cloned.setReadTimeout(3000, TimeUnit.MILLISECONDS); Response response = cloned.newCall(request).execute();
System.out.println("Response 2 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 2 failed: " + e);
}
}

新版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") // This URL is served with a 1 second delay.
.build(); try {
// Copy to customize OkHttp for this request.
OkHttpClient copy = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build(); Response response = copy.newCall(request).execute();
System.out.println("Response 1 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 1 failed: " + e);
} try {
// Copy to customize OkHttp for this request.
OkHttpClient copy = client.newBuilder()
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build(); Response response = copy.newCall(request).execute();
System.out.println("Response 2 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 2 failed: " + e);
}
}

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; // 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());
}

身份验证失效的时候就别多次retry了,return null放弃就好。

1
2
3
if (credential.equals(response.request().header("Authorization"))) {
return null; // If we already failed with these credentials, don't retry.
}

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; // If we've failed 3 times, give up.
} private int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}

Android OkHttp经验小结的更多相关文章

  1. Android okHttp网络请求之Json解析

    前言: 前面两篇文章介绍了基于okHttp的post.get请求,以及文件的上传下载,今天主要介绍一下如何和Json解析一起使用?如何才能提高开发效率? okHttp相关文章地址: Android o ...

  2. Android okHttp网络请求之Get/Post请求

    前言: 之前项目中一直使用的Xutils开源框架,从xutils 2.1.5版本使用到最近的xutils 3.0,使用起来也是蛮方便的,只不过最近想着完善一下app中使用的开源框架,由于Xutils里 ...

  3. Android okHttp网络请求之文件上传下载

    前言: 前面介绍了基于okHttp的get.post基本使用(http://www.cnblogs.com/whoislcj/p/5526431.html),今天来实现一下基于okHttp的文件上传. ...

  4. Android okHttp网络请求之缓存控制Cache-Control

    前言: 前面的学习基本上已经可以完成开发需求了,但是在项目中有时会遇到对请求做个缓存,当没网络的时候优先加载本地缓存,基于这个需求我们来学习一直okHttp的Cache-Control. okHttp ...

  5. Unity3d 经验小结

      Unity3d 经验小结 文本教程 你是第2541个围观者 0条评论 供稿者:Jamesgary 标签:unity3d教程 Fbx.贴图导入Unity时的注意事项: 在导出Fbx之前,Maya中已 ...

  6. Android okHttp网络请求之Retrofit+Okhttp+RxJava组合

    前言: 通过上面的学习,我们不难发现单纯使用okHttp来作为网络库还是多多少少有那么一点点不太方便,而且还需自己来管理接口,对于接口的使用的是哪种请求方式也不能一目了然,出于这个目的接下来学习一下R ...

  7. Android OkHttp完全解析 --zz

    参考文章 https://github.com/square/okhttp http://square.github.io/okhttp/ 泡网OkHttp使用教程 Android OkHttp完全解 ...

  8. Android OkHttp完全解析 是时候来了解OkHttp了

    Android OkHttp完全解析 是时候来了解OkHttp了 标签: AndroidOkHttp 2015-08-24 15:36 316254人阅读 评论(306) 收藏 举报  分类: [an ...

  9. Android OkHttp文件上传与下载的进度监听扩展

    http://www.loongwind.com/archives/290.html 上一篇文章介绍了用Retrofit实现文件的上传与下载,但是我们发现没办法监听上传下载的进度,毕竟我们在做开发的时 ...

随机推荐

  1. 【Surrounded Regions】cpp

    题目: Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A region is capt ...

  2. 【Luogu P2781】 传教

    这题是可以用线段树做的. 虽然$n\leq 10^9$ 可以发现,真正需要用到的节点很少,故动态开点,只有需要用到的时候才新建节点. 这里我在下放标记的时候新建节点,因为每操作/查询一个节点都需要先下 ...

  3. C# 引用访问权限

    同样代码表现的不同行为 创建基类(Super)和派生类(Sub)每个类有一个字段field和一个公共方法getField,并且使用内联的方式初始化为1,方法getField返回字段field.C#和J ...

  4. ios开发学习笔记004-进制与位运算

    进制 二进制   0 1组成,封2进1 八进制 0-7组成,封8进1 十进制 0-9组成,封10进1 十六进制 0-15组成,封16进1 printf以不同进制形式进行输出 变量的内存地址形式 变量在 ...

  5. 5个最佳的Android测试框架(带示例)

    谷歌的Android生态系统正在不断地迅速扩张.有证据表明,新的移动OEM正在攻陷世界的每一个角落,不同的屏幕尺寸.ROM /固件.芯片组以及等等等等,层出不穷.于是乎,对于Android开发人员而言 ...

  6. linux实用命令-待补充

    - du 查看目录大小 - du -h 带有单位显示目录信息 - df 查看磁盘大小 - df -h 带有单位显示磁盘信息 - netstat 显示网络状态信息 - 清除僵尸进程 ps -eal | ...

  7. python 小练习1

    _input = ['I',6,6,'love','python',6] _str = '' _sum = 0 for item in _input: if isinstance(item,str): ...

  8. mongodb使用1

    首先官网下载mongodb放在根目录下.新建db文件夹,在命令行中进入bin路径,然后运行mongod开启命令,同时用--dbpath指定数据存放地点为“db”文件夹 mongod --dbpath= ...

  9. react 基础语法复习2- react入门以及JSX

    引入 react 以及 ReactDom import React from 'react'; import ReactDOM from 'react-dom'; 将react组件渲染到真实dom节点 ...

  10. 标准IO与文件IO 的区别

    先来了解下什么是标准IO以及文件IO. 标准IO:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性.标准IO库处理很多细节.例如缓存 ...