当前位置:   article > 正文

okhttp3使用总结

okhttp3

1. 简介

okhttp是我们在Android开发中十分常用的一个网络请求框架。

2. 常用API总结

下面将介绍okhttp3中的一些常用的api(okhttp3完整文档如下,可自行参阅:https://square.github.io/okhttp/3.x/okhttp/)。

2.1 OkHttpClient

OkHttpClient可以理解为是一个构造Call对象的“工厂”。OkHttpClient实例创建方式有两种:

(1)直接通过new的方式实例化:

  1. // The singleton HTTP client.
  2. public final OkHttpClient client = new OkHttpClient();

 (2)通过OkHttpClient.Builder构造实例,此种方式支持自定义部分行为:

  1. // The singleton HTTP client.
  2. public final OkHttpClient client = new OkHttpClient.Builder()
  3. .addInterceptor(new HttpLoggingInterceptor())
  4. .cache(new Cache(cacheDir, cacheSize))
  5. .build();

 我们在使用OkHttpClient实例时,最好确保它在整个应用中都是唯一的单例。client实例会持有它自己的连接池和线程池,确保client的全局唯一可以让我们在所有的请求中复用这些连接和线程,这有助于减少内存资源的消耗。

为此,官方还提供了newBuilder()的api来帮助我们创建支持自定义参数的可全局共享的OkHttpClient实例:

  1. OkHttpClient eagerClient = client.newBuilder()
  2. .readTimeout(500, TimeUnit.MILLISECONDS)
  3. .build();
  4. Response response = eagerClient.newCall(request).execute();

 之后,官方文档中还提到,OkHttp中被挂起的线程和连接都将会在保持空闲时自动回收。如果我们想要主动释放资源,可以使用如下方式,之后所有的Call对象执行请求都将被拒绝。

 client.dispatcher().executorService().shutdown();

 清理连接池可以使用如下方式(连接池的守护线程可能不会立即退出):

 client.connectionPool().evictAll();

 如果希望关闭缓存,可以使用如下方式:

client.cache().close();

2.2 Request

Request对象是http请求的抽象(包括请求地址,请求方法,请求头,请求体等内容)。通常情况下,我们可以通过Request.Builder来创建该对象:

  1. Request request = new Request.Builder()
  2. .url("https://api.github.com/repos/square/okhttp/issues")
  3. .header("User-Agent", "OkHttp Headers.java")
  4. .addHeader("Accept", "application/json; q=0.5")
  5. .build();

 我们通过request对象的url,method,headers,body等方法能够获取到请求的对应信息。

2.3 Response

Response对象是http响应的抽象(包括响应头,响应体等内容)。

2.4 ResponseBody

ResponseBody抽象的是响应体(包括服务端响应的原始字节信息),我们可以通过response.body()来获取该对象。

需要注意的是,ResponseBody对象必须在我们使用完后关闭。因为每一个ResponseBody都是由有限的资源提供支持,例如套接字(实时网络响应)和打开的文件(用于缓存响应体)。如果没有关闭ResponseBody,将会导致资源的泄露,严重时可能会使得应用变得缓慢或者是崩溃。ResponseBody和Response均实现了closeable接口,我们可以通过如下方法来关闭:

  1. Response.close();
  2. Response.body().close();
  3. Response.body().source().close();
  4. Response.body().charStream().close();
  5. Response.body().byteStream().close();

对于同步调用,我们可以使用try代码块,编译器会帮我们在隐含的finally代码块中调用close方法。例如:

  1. Call call = client.newCall(request);
  2. try (Response response = call.execute()) {
  3. ... // Use the response.
  4. }

 异步调用中也是类似的:

  1. Call call = client.newCall(request);
  2. call.enqueue(new Callback() {
  3. public void onResponse(Call call, Response response) throws IOException {
  4. try (ResponseBody responseBody = response.body()) {
  5. ... // Use the response.
  6. }
  7. }
  8. public void onFailure(Call call, IOException e) {
  9. ... // Handle the failure.
  10. }
  11. });

2.5 Call

Call对象抽象的是一个正在准备执行的请求。它代表着一次完整的请求和响应,可以被取消,但是不能被执行两次。

3. 使用案例

以下例子均来源于官网

3.1 同步Get请求

通过Get请求去服务器上获取helloworld.txt的文本内容:

  1. private final OkHttpClient client = new OkHttpClient();
  2. public void run() throws Exception {
  3. Request request = new Request.Builder()
  4. .url("https://publicobject.com/helloworld.txt")
  5. .build();
  6. // client.newCall(request)会返回一个Call对象,通过调用Call对象的execute方法就会执行同步请求,并返回一个Response对象
  7. try (Response response = client.newCall(request).execute()) {
  8. if (!response.isSuccessful()) {
  9. throw new IOException("Unexpected code: " + response);
  10. }
  11. // 获取响应头的集合
  12. Headers responseHeaders = reponse.headers();
  13. // 遍历并打印响应头的内容
  14. for (int i = 0; i < responseHeaders.size(); i++) {
  15. System.out.println(responseHeaders.name(i) + " : " + responseHeaders.value(i));
  16. }
  17. // 打印响应体
  18. System.out.println(response.body().string());
  19. }
  20. }

在上面的代码中,我们通过response.body().string()获取到了响应体的完整内容。但是如果在响应体的内容(字节数)比较大的情况下,就不建议采用这种方法来获取,因为response.body().string()会默认将整个响应体内容加载到内存中,此时我们最好使用字节流的形式来读取。

3.2 异步Get请求

在3.1中,我们学会了如何通过okhttp来发送同步请求,不过大家应该都知道,Android中的UI线程是不允许发送同步请求的,因为这会阻塞主线程,从而导致ANR。下面将介绍如何在okhttp中通过异步的方式来发送请求:

  1. private final OkHttpClient client = new OkHttpClient();
  2. public void run() throws Exception {
  3. Request request = new Request.Builder()
  4. .url("http://publicobject.com/helloworld.txt")
  5. .build();
  6. // 通过调用Call对象的enqueue方法就会执行异步请求,并且会根据请求成功与否执行相应的回调
  7. client.newCall(request).enqueue(new Callback() {
  8. @Override
  9. public void onFailure(Call call, IOException e) {
  10. // 非主线程
  11. e.printStackTrace();
  12. }
  13. @Override
  14. public void onResponse(Call call, Response response) throws IOException {
  15. // 非主线程,如果要执行UI操作,需要使用runOnUiThread
  16. try (ResponseBody responseBody = response.body()) {
  17. if (!response.isSuccessful()) {
  18. throw new IOException("Unexpected code " + response);
  19. }
  20. Headers responseHeaders = response.headers();
  21. for (int i = 0, size = responseHeaders.size(); i < size; i++) {
  22. System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
  23. }
  24. System.out.println(responseBody.string());
  25. }
  26. }
  27. });
  28. }

3.3 请求头和响应头

下面的例子展示了如何设置请求头以及获取响应头:

  1. private final OkHttpClient client = new OkHttpClient();
  2. public void run() throws Exception {
  3. // 请求头的设置
  4. // header()会覆盖之前已设置的同名请求头,而addHeader()不会
  5. Request request = new Request.Builder()
  6. .url("https://api.github.com/repos/square/okhttp/issues")
  7. .header("User-Agent", "OkHttp Headers.java")
  8. .addHeader("Accept", "application/json; q=0.5")
  9. .addHeader("Accept", "application/vnd.github.v3+json")
  10. .build();
  11. try (Response response = client.newCall(request).execute()) {
  12. if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
  13. // response.header(headerName)获取的是响应头对应字段的最新值
  14. System.out.println("Server: " + response.header("Server"));
  15. System.out.println("Date: " + response.header("Date"));
  16. // 获取响应头集合
  17. System.out.println("Vary: " + response.headers("Vary"));
  18. }
  19. }

3.4 Post请求(提交字符串)

下面的例子中通过Post请求提交了一串字符串到服务端:

  1. public static final MediaType MEDIA_TYPE_MARKDOWN
  2. = MediaType.parse("text/x-markdown; charset=utf-8");
  3. private final OkHttpClient client = new OkHttpClient();
  4. public void run() throws Exception {
  5. String postBody = ""
  6. + "Releases\n"
  7. + "--------\n"
  8. + "\n"
  9. + " * _1.0_ May 6, 2013\n"
  10. + " * _1.1_ June 15, 2013\n"
  11. + " * _1.2_ August 11, 2013\n";
  12. Request request = new Request.Builder()
  13. .url("https://api.github.com/markdown/raw")
  14. .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
  15. .build();
  16. try (Response response = client.newCall(request).execute()) {
  17. if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
  18. System.out.println(response.body().string());
  19. }
  20. }

需要注意的是,整个请求体对象Request都是在内存中的,所以要避免提交的字符串内容过多(不要超过1MB)。

3.5 Post请求(以字节流的形式提交请求体)

  1. public static final MediaType MEDIA_TYPE_MARKDOWN
  2. = MediaType.parse("text/x-markdown; charset=utf-8");
  3. private final OkHttpClient client = new OkHttpClient();
  4. public void run() throws Exception {
  5. RequestBody requestBody = new RequestBody() {
  6. @Override public MediaType contentType() {
  7. return MEDIA_TYPE_MARKDOWN;
  8. }
  9. @Override public void writeTo(BufferedSink sink) throws IOException {
  10. sink.writeUtf8("Numbers\n");
  11. sink.writeUtf8("-------\n");
  12. for (int i = 2; i <= 997; i++) {
  13. sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
  14. }
  15. }
  16. private String factor(int n) {
  17. for (int i = 2; i < n; i++) {
  18. int x = n / i;
  19. if (x * i == n) return factor(x) + " × " + i;
  20. }
  21. return Integer.toString(n);
  22. }
  23. };
  24. Request request = new Request.Builder()
  25. .url("https://api.github.com/markdown/raw")
  26. .post(requestBody)
  27. .build();
  28. try (Response response = client.newCall(request).execute()) {
  29. if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
  30. System.out.println(response.body().string());
  31. }
  32. }

3.6 Post请求(提交文件)

  1. public static final MediaType MEDIA_TYPE_MARKDOWN
  2. = MediaType.parse("text/x-markdown; charset=utf-8");
  3. private final OkHttpClient client = new OkHttpClient();
  4. public void run() throws Exception {
  5. File file = new File("README.md");
  6. Request request = new Request.Builder()
  7. .url("https://api.github.com/markdown/raw")
  8. .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
  9. .build();
  10. try (Response response = client.newCall(request).execute()) {
  11. if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
  12. System.out.println(response.body().string());
  13. }
  14. }

3.7 Post请求(以表单形式提交参数)

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

  1. private final OkHttpClient client = new OkHttpClient();
  2. public void run() throws Exception {
  3. RequestBody formBody = new FormBody.Builder()
  4. .add("search", "Jurassic Park")
  5. .build();
  6. Request request = new Request.Builder()
  7. .url("https://en.wikipedia.org/w/index.php")
  8. .post(formBody)
  9. .build();
  10. try (Response response = client.newCall(request).execute()) {
  11. if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
  12. System.out.println(response.body().string());
  13. }
  14. }

此种方式Post的请求头中Content-Type字段的值是:application/x-www-form-urlencoded。

3.8 Post请求(以MultiPart的格式提交文件,分块请求)

  1. /**
  2. * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
  3. * these examples, please request your own client ID! https://api.imgur.com/oauth2
  4. */
  5. private static final String IMGUR_CLIENT_ID = "...";
  6. private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
  7. private final OkHttpClient client = new OkHttpClient();
  8. public void run() throws Exception {
  9. // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
  10. RequestBody requestBody = new MultipartBody.Builder()
  11. .setType(MultipartBody.FORM)
  12. .addFormDataPart("title", "Square Logo")
  13. .addFormDataPart("image", "logo-square.png",
  14. RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
  15. .build();
  16. Request request = new Request.Builder()
  17. .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
  18. .url("https://api.imgur.com/3/image")
  19. .post(requestBody)
  20. .build();
  21. try (Response response = client.newCall(request).execute()) {
  22. if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
  23. System.out.println(response.body().string());
  24. }
  25. }

此种方式Post的请求头中Content-Type字段的值是:multipart/form-data,常用于以表单形式上传文件。详细区别可以参见这篇博客:深入解析 multipart/form-data

3.9 取消一个请求

通过调用Call对象的cancel方法可以立刻停止一个进行中的请求,如果某个线程当前正在执行请求或读取响应,它将会收到一个IOException的异常。

无论是同步请求还是异步请求都能够被取消,在恰当的时候取消请求可以让我们节省网络资源。

  1. private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  2. private final OkHttpClient client = new OkHttpClient();
  3. public void run() throws Exception {
  4. Request request = new Request.Builder()
  5. .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
  6. .build();
  7. final long startNanos = System.nanoTime();
  8. final Call call = client.newCall(request);
  9. // Schedule a job to cancel the call in 1 second.
  10. executor.schedule(new Runnable() {
  11. @Override public void run() {
  12. System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
  13. call.cancel();
  14. System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
  15. }
  16. }, 1, TimeUnit.SECONDS);
  17. System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
  18. try (Response response = call.execute()) {
  19. System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
  20. (System.nanoTime() - startNanos) / 1e9f, response);
  21. } catch (IOException e) {
  22. System.out.printf("%.2f Call failed as expected: %s%n",
  23. (System.nanoTime() - startNanos) / 1e9f, e);
  24. }
  25. }

3.10 超时设置

okhttp请求超时设置方法如下:

  1. private final OkHttpClient client;
  2. public ConfigureTimeouts() throws Exception {
  3. client = new OkHttpClient.Builder()
  4. .connectTimeout(10, TimeUnit.SECONDS)
  5. .writeTimeout(10, TimeUnit.SECONDS)
  6. .readTimeout(30, TimeUnit.SECONDS)
  7. .build();
  8. }
  9. public void run() throws Exception {
  10. Request request = new Request.Builder()
  11. .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
  12. .build();
  13. try (Response response = client.newCall(request).execute()) {
  14. System.out.println("Response completed: " + response);
  15. }
  16. }

4. 参考文章

官方文档:https://square.github.io/okhttp/

OkHttp使用完全教程:https://www.jianshu.com/p/ca8a982a116b

Okhttp3基本使用:https://www.jianshu.com/p/da4a806e599b

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/45629?site
推荐阅读
相关标签
  

闽ICP备14008679号