当前位置:   article > 正文

OKHttp3的使用和详解_java okhttp3

java okhttp3

一、概述

OKHttp是处理网络请求的开源框架,Andorid当前最火热的网络框架,Retrofit的底层也是OKHttp,用于替换HttpUrlConnection和Apache HttpClient(API23 6.0已经移除)。

概况起来说OKHttp是一款优秀HTTP框架,它支持GET和POST请求,支持Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟的问题。

OKHttp的优点:

1.支持HTTP2/SPDY,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接。

2.如果HTTP2/SPDY不可用OkHttp,会使用连接池来复用连接以提高效率。

3.提供了对 GZIP 的默认支持来降低传输内容的大小

4.提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求

5.当网络出现问题时,OkHttp会自动重试一个主机的多个 IP 地址

二、基本的使用

2.1、配置工程

(1)首先在清单文件AndroidManifest.xml中添加网络权限

  1. <uses-permission android:name="android.permission.INTERNET"/>
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  3. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

(2)在build.gradle文件中添加okhttp依赖:

implementation 'com.squareup.okhttp3:okhttp:4.2.0'

2.2、使用步骤:

(1)创建OkHttpClient实例

(2)通过Builder辅助类构建Request请求对象

(3)OkHttpClient实例回调请求,得到Call对象

(4)同步/异步执行请求,获取Response对象

2.3、方法详解

(1)创建OkHttpClient实例

  1. //方式一:创建OkHttpClient实例,使用默认构造函数,创建默认配置OkHttpClient(官方建议全局只有一个实例)
  2. OkHttpClient okHttpClient = new OkHttpClient();
  3. //方式二:通过new OkHttpClient.Builder() 一步步配置一个OkHttpClient实例
  4. OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();
  5. //方式三:如果要求使用现有的实例,可以通过newBuilder().build()方法进行构造
  6. OkHttpClient client = okHttpClient.newBuilder().build();

使用默认构造函数创建OkHttpClient实例,创建默认配置OkHttpClient(官方建议全局只有一个实例)。也可以通过new OkHttpClient.Builder() 一步步配置一个OkHttpClient实例,如果要求使用现有的实例,可以通过newBuilder().build()方法进行构造。

(2)超时设置

我们在创建OkHttpClient实例的时候也会设置相关的属性,通过.Builder().build()的形式设置,比如超时时间设置:

  1. //1.构建OkHttpClient实例
  2. final OkHttpClient okHttpClient = new OkHttpClient.Builder()
  3. .connectTimeout(2, TimeUnit.SECONDS)//链接超时为2秒,单位为秒
  4. .writeTimeout(2, TimeUnit.SECONDS)//写入超时
  5. .readTimeout(2, TimeUnit.SECONDS)//读取超时
  6. .build();
  7. //2.通过Builder辅助类构建请求对象
  8. final Request request = new Request.Builder()
  9. .url("http://httpbin.org/delay/10")//URL地址
  10. .build();//构建
  11. //创建线程,在子线程中运行
  12. new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. try {
  16. //3.通过mOkHttpClient调用请求得到Call
  17. final Call call = okHttpClient.newCall(request);
  18. //4.执行同步请求,获取响应体Response对象
  19. Response response = call.execute();
  20. Log.e(TAG, "请求(超时)==" + response);
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. Log.e(TAG, "请求(超时)==" + e.toString());
  24. }
  25. }
  26. }).start();

我这里设置了请求10秒后才请求成功,请求超时时间为2秒,读写超时时间为2秒,网络请求是耗时的请求操作,需要另外开子线程运行,抛出了超时异常,测试一下效果:

这里引出了http请求的几个重要的角色,Request是OKHttp的访问请求,Builder是访问辅助类,Response是OKHttp的请求响应

Request(请求):每一个HTTP请求中,都应该包含一个URL,一个GET或POST方法,以及Header和其他参数,还可以包含特定内容类型的数据流。

Responses(响应):响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。
另外可以根据response.code()获取返回的状态码。

OKHttp:简单来说,通过OkHttpClient可以发送一个http请求,并且可以读取该请求的响应,它是一个生产Call的工厂。收益于一个共享的响应缓存/线程池/复用的链接等因素,绝大多数应用使用一个OKHttpClient实例,便可满足整个应用的Http请求

(3)HTTP头部的设置和读取

HTTP头部的数据结构是Map<String,List<String>>类型,也就是说,对于每个HTTP的头,可能有多个值,但是大部分的HTTP头只有一个值,只有少部分HTTP头允许多个值,至于name的取值说明请参考:HTTP头部参数说明

OKHTTP的处理方式是:

  • header(name, value)            来设置HTTP头的唯一值, 如果请求中已经存在响应的信息那么直接替换掉;
  • addHeader(name, value)     来补充新值,如果请求头中已经存在name的name-value, 那么还会继续添加,请求头中便会存在多个name相同value不同的“键值对”;
  • header(name, value)            读取唯一值或多个值的最后一个值;
  • headers(name)                     获取所有值。
  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("Server", "application/json; q=0.5")//设置新值
  5. .addHeader("Server", "application/vnd.github.v3+json")//设置新值
  6. .build();
  7. mOkHttpClient.newCall(request).enqueue(new Callback() {
  8. @Override
  9. public void onFailure(@NotNull Call call, @NotNull IOException e) {
  10. Log.e(TAG, "Post请求(HTTP头)异步响应failure==" + e.getMessage());
  11. }
  12. @Override
  13. public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
  14. Log.e(TAG, "header:Date==" + response.header("Date"));
  15. Log.e(TAG, "header:User-Agent==" + response.header("User-Agent"));
  16. Log.e(TAG, "headers:Server==" + response.headers("Server"));
  17. Log.e(TAG, "headers:Vary==" + response.headers("Vary"));
  18. Log.e(TAG, "Post请求(HTTP头)异步响应Success==" + response.body().string());
  19. }
  20. });

Request请求中通过.Builder().build()的形式设置url(请求地址),也可以设置该请求的头部信息,response的body有很多种输出方法,string()只是其中之一,注意是string()不是toString()。如果是下载文件就是response.body().bytes()。我们来看看打印出来的结果:

(4)GET请求(同步)

  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. //通过Builder辅助类构建请求对象
  5. Request request = new Request.Builder()
  6. .get()//get请求
  7. .url("https://www.baidu.com")//请求地址
  8. .build();//构建
  9. try {
  10. //通过mOkHttpClient调用请求得到Call
  11. final Call call = mOkHttpClient.newCall(request);
  12. //执行同步请求,获取Response对象
  13. Response response = call.execute();
  14. if (response.isSuccessful()) {//如果请求成功
  15. String string = response.body().string();
  16. Log.e(TAG, "get同步请求success==" + string);
  17. //响应体的string()对于小文档来说十分方便高效,但是如果响应体太大(超过1M),应避免使用string()方法,
  18. //因为它会把整个文档加载到内存中,对用超多1M的响应body,应该使用流的方式来处理。
  19. //response.body().bytes();//字节数组类型
  20. //response.body().byteStream();//字节流类型
  21. //response.body().charStream();//字符流类型
  22. printHeads(response.headers());
  23. } else {
  24. Log.e(TAG, "get同步请求failure==");
  25. }
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }).start();
  31. /**
  32. * 打印请求头信息
  33. *
  34. * @param headers 请求头集合
  35. */
  36. private void printHeads(Headers headers) {
  37. if (headers == null) return;
  38. for (int i = 0; i < headers.size(); i++) {
  39. Log.e(TAG, "请求头==" + headers.name(i) + ":" + headers.value(i));
  40. }
  41. }

在请求Request中声明为GET请求,设置url请求地址,调用实例mOkHttpClient.new Call(request)调用请求,返回Call,通过同步方法call.execute()同步执行,这里需要开启一个子线程运行。需要手动通过response.isSuccessful()判断请求是否成功,同时通过响应体得到Heander打印了请求头的相关信息,看看得出的结果:

(5)GET请求(异步)

同步和异步不一样的地方是Call对象调用的方法,call.enqueue(callback)实现函数回调成功和失败两方法,异步方法可以不用开启子线程执行。

注意:同步是阻塞式的,串联执行,同步发生在当前线程内。异步是并发式的,会再次创建子线程处理耗时操作。

  1. //通过Builder辅助类构建Request对象,链式编程
  2. Request request = new Request.Builder()
  3. .url("https://www.baidu.com")
  4. .get()
  5. .build();
  6. //异步
  7. mOkHttpClient.newCall(request).enqueue(new Callback() {
  8. @Override
  9. public void onFailure(@NotNull Call call, @NotNull IOException e) {
  10. e.printStackTrace();
  11. Log.e(TAG, "get异步响应失败==" + e.toString());
  12. }
  13. @Override
  14. public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
  15. //主线程中更新UI
  16. runOnUiThread(new Runnable() {
  17. @Override
  18. public void run() {
  19. //TODO 在主线程中更新UI的操作
  20. }
  21. });
  22. Log.e(TAG, "get异步当前线程,线程id==" + Thread.currentThread().getId());
  23. String result = response.body().string();
  24. Log.e(TAG, "get异步响应成功==" + result);
  25. printHeads(response.headers());
  26. }
  27. });
  28. Log.e(TAG, "主线程,线程id==" + Thread.currentThread().getId());

打印log如下:

onFailure()onResponse()分别是在请求失败和成功时会调用的方法。这里有个要注意的地方,onFailure()onResponse()是在异步线程里执行的,所以如果你在Android把更新UI的操作写在这两个方法里面是会报错的,这个时候可以用runOnUiThread这个方法进行更新UI操作。

(6)POST提交String请求(同步)

post请求创建request和get是一样的,只是post请求需要提交一个表单,就是RequestBody。表单的格式有好多种,post请求提交参数需要构建RequestBody对象,post提交的过程需要将提交的内容封装到一个RequestBody中,同时需要设置类型MediaType,MediaType用于描述Http请求和响应体的内容类型,也就是Content-Type,通过.Builder().build()的形式设置URL(请求地址),RequestBody(参数容器)。

  1. //构建RequestBody对象,post提交的过程需要将提交的内容封装到一个RequestBody中
  2. //MediaType用于描述Http请求和响应体的内容类型,也就是Content-Type
  3. MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
  4. RequestBody requestBody = RequestBody.create("提交的内容", mediaType);
  5. final Request request = new Request.Builder()
  6. .post(requestBody)
  7. .url("https://api.github.com/markdown/raw")
  8. .build();
  9. new Thread(new Runnable() {
  10. @Override
  11. public void run() {
  12. try {
  13. Response response = mOkHttpClient.newCall(request).execute();
  14. if (response.isSuccessful()) {
  15. Log.e(TAG, "Post请求String同步响应success==" + response.body().string());
  16. } else {
  17. Log.e(TAG, "Post请求String同步响应failure==" + response.body().string());
  18. }
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. Log.e(TAG, "Post请求String同步响应failure==" + e.getMessage());
  22. }
  23. }
  24. }).start();

请求结果如下:

RequestBody的数据格式都要指定Content-Type,常见的有三种:

  • application/x-www-form-urlencoded 数据是个普通表单;
  • multipart/form-data                           数据里有文件;
  • application/json                                 数据是个json。
  1. MediaType mediaType = MediaType.parse("image/png");
  2. RequestBody requestBody = RequestBody.create(xxx.png, mediaType);

改变MediaType 中的内容即可设置不同的内容类型,比如image/png表示便携式网络图形(Portable Network Graphics,PNG)是一种无损压缩的位图图形格式,支持索引、灰度、RGB三种颜色方案以及Alpha通道等特性。

(7)POST提交String请求(异步)

  1. RequestBody requestBody = RequestBody.create("提交内容", MediaType.parse("text/plain; charset=utf-8"));
  2. Request request = new Request.Builder()
  3. .url("https://api.github.com/markdown/raw")
  4. .post(requestBody)
  5. .build();
  6. mOkHttpClient.newCall(request).enqueue(new Callback() {
  7. @Override
  8. public void onFailure(@NotNull Call call, @NotNull IOException e) {
  9. Log.e(TAG, "Post请求String异步响应failure==" + e.getMessage());
  10. }
  11. @Override
  12. public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
  13. String string = response.body().string();
  14. Log.e(TAG, "Post请求String异步响应success==" + string);
  15. }
  16. });

异步请求主要是有回调方法,和其他部分和同步差不多,我们来看看效果:

(8)POST提交键值对请求(异步)

请求参数提交键值对需要用到FormBody,FormBody继承自RequestBody,通过.add("key", "value")形式添加:

  1. //提交键值对需要用到FormBody,FormBody继承自RequestBody
  2. FormBody formBody = new FormBody.Builder()
  3. //添加键值对(通多Key-value的形式添加键值对参数)
  4. .add("key", "value")
  5. .build();
  6. final Request request = new Request.Builder()
  7. .post(formBody)
  8. .url("url")
  9. .build();
  10. mOkHttpClient.newCall(request).enqueue(new Callback() {
  11. @Override
  12. public void onFailure(@NotNull Call call, @NotNull IOException e) {
  13. Log.e(TAG, "Post请求(键值对)异步响应failure==" + e.getMessage());
  14. }
  15. @Override
  16. public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
  17. String result = response.body().string();
  18. Log.e(TAG, "Post请求(键值对)异步响应Success==" + result);
  19. }
  20. });

(9)POST提交文件请求(异步)

post提交文件,将文件传入RequestBody中即可:

  1. RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), new File("text.txt"));
  2. Request request = new Request.Builder()
  3. .post(requestBody)
  4. .url("https://api.github.com/markdown/raw")
  5. .build();
  6. mOkHttpClient.newCall(request).enqueue(new Callback() {
  7. @Override
  8. public void onFailure(@NotNull Call call, @NotNull IOException e) {
  9. Log.e(TAG, "Post请求(文件)异步响应failure==" + e.getMessage());
  10. }
  11. @Override
  12. public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
  13. String result = response.body().string();
  14. Log.e(TAG, "Post请求(文件)异步响应Success==" + result);
  15. }

请求效果如下:源码中有例子

(10)POST提交表单请求(异步)

 使用FormEncodingBuilder来构建和HTML标签相同效果的请求体,键值对将使用一种HTML兼容形式的URL编码来进行编码

  1. //使用FormEncodingBuilder来构建和HTML标签相同效果的请求体,键值对将使用一种HTML兼容形式的URL编码来进行编码
  2. FormBody formBody = new FormBody.Builder()
  3. .add("search", "Jurassic Park")
  4. .build();
  5. Request request = new Request.Builder()
  6. .url("https://en.wikipedia.org/w/index.php")
  7. .post(formBody)
  8. .build();
  9. mOkHttpClient.newCall(request).enqueue(new Callback() {
  10. @Override
  11. public void onFailure(@NotNull Call call, @NotNull IOException e) {
  12. Log.e(TAG, "Post请求(表单)异步响应failure==" + e.getMessage());
  13. }
  14. @Override
  15. public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
  16. String result = response.body().string();
  17. Log.e(TAG, "Post请求(表单)异步响应Success==" + result);
  18. }
  19. });

(11)POST提交流请求(同步)

以流的方式Post提交请求体,请求体的内容由流写入产生,这里是流直接写入OKIO的BufferedSink。你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取,这里需要重写RequestBody中的几个方法,将本地数据放入到Http协议的请求体中,然后发送到服务器。

  1. //以流的方式Post提交请求体,请求体的内容由流写入产生,这里是流直接写入OKIO的BufferedSink。
  2. // 你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取
  3. final MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
  4. //重写RequestBody中的几个方法,将本地数据放入到Http协议的请求体中,然后发送到服务器
  5. final RequestBody requestBody = new RequestBody() {
  6. @Nullable
  7. @Override
  8. public MediaType contentType() {
  9. //返回内容类型
  10. return mediaType;
  11. }
  12. @Override
  13. public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
  14. //输入数据头
  15. bufferedSink.writeUtf8("Numbers\\n");
  16. bufferedSink.writeUtf8("-------\\n");
  17. //构造数据
  18. for (int i = 2; i < 997; i++) {
  19. bufferedSink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
  20. }
  21. }
  22. };
  23. //构建请求
  24. final Request request = new Request.Builder().
  25. url("https://api.github.com/markdown/raw")
  26. .post(requestBody)
  27. .build();
  28. //开启线程
  29. new Thread(new Runnable() {
  30. @Override
  31. public void run() {
  32. try {
  33. Response response = mOkHttpClient.newCall(request).execute();
  34. if (response.isSuccessful()) {
  35. String result = response.body().toString();
  36. Log.e(TAG, "Post请求(流)异步响应Success==" + result);
  37. } else {
  38. Log.e(TAG, "Post请求(流)异步响应failure==" + response);
  39. throw new IOException("Unexpected code " + response);
  40. }
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }).start();
  46. private String factor(int n) {
  47. for (int i = 2; i < n; i++) {
  48. int x = n / i;
  49. if (x * i == n) {
  50. return factor(x) + "x" + i;
  51. }
  52. }
  53. return Integer.toString(n);
  54. }

请求结果如下:

(12)POST提交分块请求(异步)

MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容,多块请求头中的每个请求体都是一个亲求体,可以定义自己的请求体,这些请求体可以用来描述这块请求,例如他的Content-Disposition,如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。

  1. MediaType mediaType = MediaType.parse("image/png");
  2. String IMGUR_CLIENT_ID = "...";
  3. //构建body
  4. MultipartBody multipartBody = new MultipartBody.Builder()
  5. .setType(MultipartBody.FORM)
  6. .addFormDataPart("title", "Square Logo")
  7. .addFormDataPart("image", "logo-square.png", RequestBody.create(mediaType,
  8. new File("website/static/logo-square.png")))
  9. .build();
  10. //构建请求
  11. Request request = new Request.Builder()
  12. .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
  13. .url("https://api.imgur.com/3/image")
  14. .post(multipartBody)
  15. .build();
  16. //执行请求
  17. mOkHttpClient.newCall(request).enqueue(new Callback() {
  18. @Override
  19. public void onFailure(@NotNull Call call, @NotNull IOException e) {
  20. Log.e(TAG, "Post请求(分块)异步响应failure==" + e.getMessage());
  21. }
  22. @Override
  23. public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
  24. String result = response.body().string();
  25. Log.e(TAG, "Post请求(分块)异步响应Success==" + result);
  26. }
  27. });

请求结果如下:

如果你上传一个文件不是一张图片,但是MediaType.parse("image/png")里的"image/png"不知道该填什么,可以参考下这个页面

源码地址


相关文章:

Retrofit2详解和简单使用

  • Retrofit2的介绍和简单使用

OKHttp3的使用和详解

  • OKHttp3的用法介绍和解析

OKHttp3源码详解

  • 从源码角度解释OKHttp3的关键流程和重要操作

转载:https://blog.csdn.net/m0_37796683/article/details/101029208

原文链接:OKHttp3的使用和详解_okhttp3使用_苏火火丶的博客-CSDN博客

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

闽ICP备14008679号