赞
踩
okhttp是我们在Android开发中十分常用的一个网络请求框架。
下面将介绍okhttp3中的一些常用的api(okhttp3完整文档如下,可自行参阅:https://square.github.io/okhttp/3.x/okhttp/)。
OkHttpClient可以理解为是一个构造Call对象的“工厂”。OkHttpClient实例创建方式有两种:
(1)直接通过new的方式实例化:
- // The singleton HTTP client.
- public final OkHttpClient client = new OkHttpClient();
(2)通过OkHttpClient.Builder构造实例,此种方式支持自定义部分行为:
- // The singleton HTTP client.
- public final OkHttpClient client = new OkHttpClient.Builder()
- .addInterceptor(new HttpLoggingInterceptor())
- .cache(new Cache(cacheDir, cacheSize))
- .build();
我们在使用OkHttpClient实例时,最好确保它在整个应用中都是唯一的单例。client实例会持有它自己的连接池和线程池,确保client的全局唯一可以让我们在所有的请求中复用这些连接和线程,这有助于减少内存资源的消耗。
为此,官方还提供了newBuilder()的api来帮助我们创建支持自定义参数的可全局共享的OkHttpClient实例:
- OkHttpClient eagerClient = client.newBuilder()
- .readTimeout(500, TimeUnit.MILLISECONDS)
- .build();
- Response response = eagerClient.newCall(request).execute();
之后,官方文档中还提到,OkHttp中被挂起的线程和连接都将会在保持空闲时自动回收。如果我们想要主动释放资源,可以使用如下方式,之后所有的Call对象执行请求都将被拒绝。
client.dispatcher().executorService().shutdown();
清理连接池可以使用如下方式(连接池的守护线程可能不会立即退出):
client.connectionPool().evictAll();
如果希望关闭缓存,可以使用如下方式:
client.cache().close();
Request对象是http请求的抽象(包括请求地址,请求方法,请求头,请求体等内容)。通常情况下,我们可以通过Request.Builder来创建该对象:
- 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")
- .build();
我们通过request对象的url,method,headers,body等方法能够获取到请求的对应信息。
Response对象是http响应的抽象(包括响应头,响应体等内容)。
ResponseBody抽象的是响应体(包括服务端响应的原始字节信息),我们可以通过response.body()来获取该对象。
需要注意的是,ResponseBody对象必须在我们使用完后关闭。因为每一个ResponseBody都是由有限的资源提供支持,例如套接字(实时网络响应)和打开的文件(用于缓存响应体)。如果没有关闭ResponseBody,将会导致资源的泄露,严重时可能会使得应用变得缓慢或者是崩溃。ResponseBody和Response均实现了closeable接口,我们可以通过如下方法来关闭:
- Response.close();
- Response.body().close();
- Response.body().source().close();
- Response.body().charStream().close();
- Response.body().byteStream().close();
对于同步调用,我们可以使用try代码块,编译器会帮我们在隐含的finally代码块中调用close方法。例如:
- Call call = client.newCall(request);
- try (Response response = call.execute()) {
- ... // Use the response.
- }
异步调用中也是类似的:
- Call call = client.newCall(request);
- call.enqueue(new Callback() {
- public void onResponse(Call call, Response response) throws IOException {
- try (ResponseBody responseBody = response.body()) {
- ... // Use the response.
- }
- }
-
- public void onFailure(Call call, IOException e) {
- ... // Handle the failure.
- }
- });
Call对象抽象的是一个正在准备执行的请求。它代表着一次完整的请求和响应,可以被取消,但是不能被执行两次。
以下例子均来源于官网。
通过Get请求去服务器上获取helloworld.txt的文本内容:
- private final OkHttpClient client = new OkHttpClient();
-
- public void run() throws Exception {
- Request request = new Request.Builder()
- .url("https://publicobject.com/helloworld.txt")
- .build();
-
- // client.newCall(request)会返回一个Call对象,通过调用Call对象的execute方法就会执行同步请求,并返回一个Response对象
- try (Response response = client.newCall(request).execute()) {
- if (!response.isSuccessful()) {
- throw new IOException("Unexpected code: " + response);
- }
-
- // 获取响应头的集合
- Headers responseHeaders = reponse.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().string()会默认将整个响应体内容加载到内存中,此时我们最好使用字节流的形式来读取。
在3.1中,我们学会了如何通过okhttp来发送同步请求,不过大家应该都知道,Android中的UI线程是不允许发送同步请求的,因为这会阻塞主线程,从而导致ANR。下面将介绍如何在okhttp中通过异步的方式来发送请求:
- private final OkHttpClient client = new OkHttpClient();
-
- public void run() throws Exception {
- Request request = new Request.Builder()
- .url("http://publicobject.com/helloworld.txt")
- .build();
-
- // 通过调用Call对象的enqueue方法就会执行异步请求,并且会根据请求成功与否执行相应的回调
- 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 {
- // 非主线程,如果要执行UI操作,需要使用runOnUiThread
- 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());
- }
- }
- });
- }

下面的例子展示了如何设置请求头以及获取响应头:
- private final OkHttpClient client = new OkHttpClient();
-
- public void run() throws Exception {
- // 请求头的设置
- // header()会覆盖之前已设置的同名请求头,而addHeader()不会
- 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();
-
- try (Response response = client.newCall(request).execute()) {
- if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
-
- // response.header(headerName)获取的是响应头对应字段的最新值
- System.out.println("Server: " + response.header("Server"));
- System.out.println("Date: " + response.header("Date"));
-
- // 获取响应头集合
- System.out.println("Vary: " + response.headers("Vary"));
- }
- }

下面的例子中通过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 {
- 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());
- }
- }

需要注意的是,整个请求体对象Request都是在内存中的,所以要避免提交的字符串内容过多(不要超过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 {
- 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();
-
- try (Response response = client.newCall(request).execute()) {
- if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
-
- System.out.println(response.body().string());
- }
- }

- 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();
-
- try (Response response = client.newCall(request).execute()) {
- if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
-
- System.out.println(response.body().string());
- }
- }

通过FormBody.Builder我们可以构建表单参数形式的请求体(类似于HTML中的<form>标签),键值对将以兼容HTML表单的方式进行编码。
- 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());
- }
- }

此种方式Post的请求头中Content-Type字段的值是:application/x-www-form-urlencoded。
- /**
- * 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 = "...";
- 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 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());
- }
- }

此种方式Post的请求头中Content-Type字段的值是:multipart/form-data,常用于以表单形式上传文件。详细区别可以参见这篇博客:深入解析 multipart/form-data。
通过调用Call对象的cancel方法可以立刻停止一个进行中的请求,如果某个线程当前正在执行请求或读取响应,它将会收到一个IOException的异常。
无论是同步请求还是异步请求都能够被取消,在恰当的时候取消请求可以让我们节省网络资源。
- 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);
-
- System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
- try (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);
- }
- }

okhttp请求超时设置方法如下:
- private final OkHttpClient client;
-
- public ConfigureTimeouts() throws Exception {
- client = new OkHttpClient.Builder()
- .connectTimeout(10, TimeUnit.SECONDS)
- .writeTimeout(10, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .build();
- }
-
- 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();
-
- try (Response response = client.newCall(request).execute()) {
- System.out.println("Response completed: " + response);
- }
- }

官方文档:https://square.github.io/okhttp/
OkHttp使用完全教程:https://www.jianshu.com/p/ca8a982a116b
Okhttp3基本使用:https://www.jianshu.com/p/da4a806e599b
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。