官方介绍
github源码
OkHttp支持Android 2.3及其以上版本。
对于Java, JDK1.7以上。
官方介绍页面有链接位置。这里把下载链接也写在下面。
OkHttp
Okio
1
2
3
4
5
6
7
8
|
OkHttpClient client =
new
OkHttpClient();
String run(String url) throws IOException {
Request request =
new
Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
if
(response.isSuccessful()) {
return
response.body().string();
}
else
{
throw
new
IOException(
"Unexpected code "
+ response);
}
}
|
Request是OkHttp中访问的请求,Builder是辅助类。Response即OkHttp中的响应。
1
2
3
|
public boolean isSuccessful()
Returns
true
if
the code is
in
[200..300),
which means the request was successfully received, understood, and accepted.
|
可以方便的获取string
1
2
3
4
|
public final String string() throws IOException
Returns the response as a string decoded
with
the charset of the Content-Type header. If that header is either absent or lacks a charset,
this
will attempt to decode the response body as UTF-8.Throws:
IOException
|
当然也能获取到流的形式:
1
|
public final InputStream byteStream()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static final MediaType JSON = MediaType.parse(
"application/json; charset=utf-8"
);
OkHttpClient client =
new
OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request =
new
Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
f (response.isSuccessful()) {
return
response.body().string();
}
else
{
throw
new
IOException(
"Unexpected code "
+ response);
}
}
|
使用Request的post方法来提交请求体RequestBody
很多时候我们会需要通过POST方式把键值对数据传送到服务器。 OkHttp提供了很方便的方式来做这件事情。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
OkHttpClient client =
new
OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody formBody =
new
FormEncodingBuilder()
.add(
"platform"
,
"android"
)
.add(
"name"
,
"bug"
)
.add(
"subject"
,
"XXXXXXXXXXXXXXX"
)
.build();
Request request =
new
Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
if
(response.isSuccessful()) {
return
response.body().string();
}
else
{
throw
new
IOException(
"Unexpected code "
+ response);
}
}
|
通过上面的例子我们可以发现,OkHttp在很多时候使用都是很方便的,而且很多代码也有重复,因此特地整理了下面的工具类。
注意:
OkHttp官方文档并不建议我们创建多个OkHttpClient,因此全局使用一个。 如果有需要,可以使用clone方法,再进行自定义。这点在后面的高级教程里会提到。
enqueue为OkHttp提供的异步方法,入门教程中并没有提到,后面的高级教程里会有解释。
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import cn.wiz.sdk.constant.WizConstant;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
public class OkHttpUtil {
private static final OkHttpClient mOkHttpClient =
new
OkHttpClient();
static{
mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS);
}
/**
* 该不会开启异步线程。
* @param request
* @return
* @throws IOException
*/
public static Response execute(Request request) throws IOException{
return
mOkHttpClient.newCall(request).execute();
}
/**
* 开启异步线程访问网络
* @param request
* @param responseCallback
*/
public static void enqueue(Request request, Callback responseCallback){
mOkHttpClient.newCall(request).enqueue(responseCallback);
}
/**
* 开启异步线程访问网络, 且不在意返回结果(实现空callback)
* @param request
*/
public static void enqueue(Request request){
mOkHttpClient.newCall(request).enqueue(
new
Callback() {
@Override
public void onResponse(Response arg0) throws IOException {
}
@Override
public void onFailure(Request arg0, IOException arg1) {
}
});
}
public static String getStringFromServer(String url) throws IOException{
Request request =
new
Request.Builder().url(url).build();
Response response = execute(request);
if
(response.isSuccessful()) {
String responseUrl = response.body().string();
return
responseUrl;
}
else
{
throw
new
IOException(
"Unexpected code "
+ response);
}
}
private static final String CHARSET_NAME =
"UTF-8"
;
/**
* 这里使用了HttpClinet的API。只是为了方便
* @param params
* @return
*/
public static String formatParams(List<BasicNameValuePair> params){
return
URLEncodedUtils.format(params, CHARSET_NAME);
}
/**
* 为HttpGet 的 url 方便的添加多个name value 参数。
* @param url
* @param params
* @return
*/
public static String attachHttpGetParams(String url, List<BasicNameValuePair> params){
return
url +
"?"
+ formatParams(params);
}
/**
* 为HttpGet 的 url 方便的添加1个name value 参数。
* @param url
* @param name
* @param value
* @return
*/
public static String attachHttpGetParam(String url, String name, String value){
return
url +
"?"
+ name +
"="
+ value;
}
}
|
高级属性其实用的不多,这里主要是对OkHttp github官方教程进行了翻译。
下载一个文件,打印他的响应头,以string形式打印响应体。
响应体的 string()
方法对于小文档来说十分方便、高效。但是如果响应体太大(超过1MB),应避免适应 string()
方法 ,因为他会将把整个文档加载到内存中。
对于超过1MB的响应body,应使用流的方式来处理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());
}
|
在一个工作线程中下载文件,当响应可读时回调Callback接口。读取响应时会阻塞当前线程。OkHttp现阶段不提供异步api来接收响应体。
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, 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访问。
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"
));
}
|
使用HTTP POST提交请求到服务。这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown。因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)。
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提交请求体。请求体的内容由流写入产生。这个例子是流直接写入Okio的BufferedSink。你的程序可能会使用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());
}
|
以文件作为请求体是十分简单的。
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());
}
|
使用FormEncodingBuilder
来构建和HTML<form>
标签相同效果的请求体。键值对将使用一种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());
}
|
MultipartBuilder
可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition
。如果Content-Length
和Content-Type
可用的话,他们会被自动添加到请求头中。
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和Java对象之间转换非常方便的api。这里我们用Gson来解析Github API的JSON响应。
注意:ResponseBody.charStream()
使用响应头Content-Type
指定的字符集来解析响应体。默认是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;
}
|
为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制。这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容。
一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttp()
,在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。
响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600
,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600
。
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));
}
|