对整个OkHttp框架的介绍,会分为使用篇和源码分析篇两个部分进行介绍:
这里是使用篇的目录:
(一)-基本使用
(二)-常用类介绍
(三)-Interceptor
OkHttp3(后续简称为OkHttp)是一个处理网络请求的开源库,由Square公司贡献。由于其高效的特性,所以非常流行。
为什么其能被广泛的使用,并且有替代HttpUrlConnetion之势呢,这就不得不说其具有的几个优点:
通过OkHttp进行网络请求的时候,一般会通过如下流程
这里列出流程,只是让大家心里大致有个印象就成。后面的文章会详细分析执行过程。
我们可以看到OkHttp的请求信息(如url,method,header等)都是封装在Request中的,而且也是通过Builder模式进行构建。
在我们发起请求后,通过一系列的Interceptor操作,最终会返回Response里面就封装了返回信息,里面包含了code, message,header,body等信息。
到这里,我们队OkHttp有了一个大致的认识后,就进入主题,怎么样去使用吧。
compile 'com.squareup.okhttp3:okhttp:3.10.0'
OkHttpClient是一个生产Call的工厂,通过Call发起Http请求,并获取返回信息。
而且,绝大多数情况使用一个全局的单例OkHttpClient实例,便可以满足整个应用的Http请求。
这里创建OkHttpClient实例有三种方法:
OkHttpClient client = new OkHttpClient();
new OkHttpClient.Builder()
.addInterceptor(new TokenInterceptor())
.addInterceptor(new CacheHeaderInterceptor())
.addInterceptor(new loggingInterceptor)
.cache(cache)
.connectTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
.build()
这里通过Builder添加了一些自定义的Interceptor、超时时间配置、缓存配置等。具体用法后续会有详细讲解。
OkHttpClient client = createClient(); // 模拟之前创建好的client
……
// 由于某个接口需要重新设置超时时间(其他配置不变)
client.newBuilder()
.connectTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
上述例子中,假设之前通过createClient()方法已经创建了OkHttpClient实例,但是某个接口需要单独设置超时时间,但是其他配置不变,这个时候,我们不需要重新再去配置以便,只需要通过client.newBuilder()方法就能获取Builder对象,并且复用之前的配置,然后再重新配置需要单独处理的配置项即可。
HttpClient创建好之后,就可以发起请求了,后续的例子中很多都是用的官方示例,所以推荐大家还是多看看官方文档
public class GetExample {
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
public static void main(String[] args) throws IOException {
GetExample example = new GetExample();
String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
System.out.println(response);
}
}
这里直接通过new OkHttpClient
创建Client对象后,通过newCall(request)
获得Call对象,然后通过excute()
方法执行请求,获得返回结果。
这里的client.newCall(request).execute()
调用就是OkHttpClient发起请求的过程了。
POST请求和GET请求,请求的流程都一样,都是通过client.newCall(request).execute()
发起请求。只是在唯一的差别是在构建Request
的时候。对比如下:
// GET请求
Request request = new Request.Builder()
.url(url)
.build();
// POST请求
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody)) // 通过post方法讲过请求体封装进Request中。
.build();
接下来我们就针对平时开发中使用POST提交的常用数据格式分别介绍POST请求。
public final class PostString {
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.get("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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
public static void main(String... args) throws Exception {
new PostString().run();
}
}
这里通过RequestBody.create()
方法创建请求体。这里第一个参数是MediaType
类型,这个就是HTPP协议的知识了,这里不展开说,可以通过MediaType.get()
方法获得。
另外,create()
方法有多个重载方法,也很简单,这里就不展开介绍了,感兴趣的朋友可以自己去看看。
其实POST请求的整体流程就是这样,
MediaType.get()
获取到合适的MediaType类型RequestBody.create()
方法,创建请求体(RequestBody)Request.Builder
的post()
方法,将请求体封装进Request中。public final class PostForm {
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody formBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
public static void main(String... args) throws Exception {
new PostForm().run();
}
}
这里对比提交字符串,更显简单,直接通过FormBody.Builder()
来构建表单提交的内容,然后post提交即可。
public final class PostStreaming {
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.get("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody requestBody = new RequestBody() {
// 返回合适的MediaType
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
// 重写该方法,将需要提交的流写入sink中(这里使用Okio的东西,不熟悉的请自行百度)
@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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
public static void main(String... args) throws Exception {
new PostStreaming().run();
}
}
流的提交方式稍显复杂一点,一把我们需要自定义一个RequestBody,然后重写RequestBody
中的几个方法,具体看上述代码和注释。代码中是直接采用匿名类的形式,你也可以按照你自己的方式自定义。最后也是通过post提交即可。
这里既然可以直接提交流,所以也就可以通过该方式上传文件了。我们只是需要将文件流写入sink即可。
但是该方式上传文件有个注意点,由于该方法会将所有的流写入内存,所以不能上传大文件
public final class PostMultipart {
/**
* The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
* these examples, please request your own client ID! https://api.imgur.com/oauth2
*/
private static final String IMGUR_CLIENT_ID = "9199fdef135c122";
private static final MediaType MEDIA_TYPE_PNG = MediaType.get("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 MultipartBody.Builder()
.setType(MultipartBody.FORM)
// 添加标题部分请求体
.addFormDataPart("title", "Square Logo")
// 在添加图片部分请求体
.addFormDataPart("image", "logo-square.png",
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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
public static void main(String... args) throws Exception {
new PostMultipart().run();
}
}
也比较简单,通过MultipartBody.Builder()
添加不同部分的请求体,最后通过post提交即可。
public final class PostFile {
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.get("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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
public static void main(String... args) throws Exception {
new PostFile().run();
}
}
起始在上面提交分块提交中,已经使用到了提交文件的东西。就是通过RequestBody.create()
这个重载方法(第二个参数是File类型)来创建RequestBody对象,然后通过post提交即可。
这里只是上传了文件,如果你需要制制定该文件流的key的话,还需要通过以下方式进行封装:
MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body)
这里的第一个参数就是你需要为该文件流指定的paramKey, 第二个参数是文件名,第三个参数就是通过RequestBody.create(MEDIA_TYPE_MARKDOWN, file)
方法创建的RequestBody对象了。
如果你在上传文件的时候,同时还需要提交其他普通参数,就可以通过2.4.4的分块提交的方式了。
HTTP 头的数据结构是Map
类型。也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。至于name的取值说明,可以自行搜索,网上有很多资料。
OkHttp的处理方式是:
使用header(name,value)
来设置HTTP头的唯一值,如果请求中已经存在响应的信息那么直接替换掉。
使用addHeader(name,value)
来补充新值,如果请求头中已经存在name的name-value,那么还会继续添加,请求头中便会存在多个name相同而value不同的“键值对”。
使用header(name)
读取唯一值或多个值的最后一个值
使用headers(name)
获取所有值
我们在上面GET和POST请求方式的介绍中,都是直接使用excute()
方式发起请求,这个就是OkHttp中的同步请求方式。我们实际开发中大多数时候都是需要异步的,异步请求非常简单,只需要将excute()
方法变为enqueue()
即可,具体使用参考:
public final class AsynchronousGet {
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(Call call, IOException e) {
e.printStackTrace();
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(responseBody.string());
}
}
});
}
public static void main(String... args) throws Exception {
new AsynchronousGet().run();
}
}
这里也有个注意点,虽然是异步的,但是其回调并不一定在主线程哦,所以我们在Android开发的时候要特别注意这个问题,不要在回调线程中直接进行UI操作。
这里我们的基本使用就介绍完了,上面在使用的时候涉及到了很多类如(Request,RequestBody,Response,ResponseBody)等,下篇文章就会进步对其一些使用过程中涉及到重要的类做一下说明。