从 http协议角度解析okhttp

Okhttp 介绍

OkHttp 是 Square 公司开源的一款网络框架,封装了一个高性能的 http 请求库。java

声明

  • 支持 spdy、http2.0、websocket 等协议
  • 支持同步、异步请求
  • 封装了线程池,封装了数据转换,提升性能。
  • 在 Android 6.0 中自带的网络请求 API 的底层就是使用了 okhttp 来进行的
  • 使用 okhttp 比较接近真正的 HTTP 协议的框架

其余优势见:Android 网络框架比较(后面更新)
提及 okhttp 的介绍,介绍完这几个关键类就能够了!web

Okhttp 中几个重要类的介绍

OkHttpClient

这个类主要是用来配置 okhttp 这个框架的,通俗一点讲就是这个类是管理这个框架的各类设置的。json

Call 类的工厂,经过 OkHttpClient 才能获得 Call 对象。api

OkHttpClient 使用注意

OkHttpClient 应该被共享,使用 okhttp 这个框架的时候,最好要将 OkHttpClient 设置成单例模式,全部的 HTTP 在进行请求的时候都要使用这一个 Client 。由于每一个 OkHttpClient 都对应了本身的链接池和线程池。减小使用链接池和线程池能够减小延迟和内存的使用。相反的若是每一个请求都建立一个 OkHttpClient 的话会很浪费内存资源。缓存

OkHttpClient的建立

OkHttpClient 有三个建立方法bash

第一个方法:直接使用 new OkHttpClient() 来建立一个实例对象就能够了,这个实例对象有默认的配置。默认请求链接超时时间 10 s ,读写超时时间 10 s,链接不成功会自动再次链接。服务器

第二个方法:就是经过 Builder的方式来本身定义一个 OkHttpclient 。固然若是你直接 build 没有本身配置参数的话,效果和第一个方法是同样的。websocket

public final OkHttpClient = new OkHttpClient.Builder()
  .addInterceptor(new HttpLoggingInterceptor())
  .cache(new Cache(cacheDir,cacheSize))
  .等等配置
  .build();
复制代码

第三个方法:就是经过已有的 OkHttpClient 对象来复制一份共享线程池和其余资源的 OkHttpClient 对象。markdown

OkHttpClient agerClient = client.newBuilder()
  .readTimeout(500,TimeUnit.MILLSECONS)
  .build();
复制代码

这种方法的好处就是,当咱们有一个特殊的请求,有的配置有点不同,好比要求链接超过 1 s 就算超时,这个时候咱们就可使用这个方法来生成一个新的实例对象,不过他们共用不少其余的资源,不会对资源形成浪费。网络

关于 OkHttpClient 的配置改变都在 Builder 中进行

不须要了能够关闭

其实持有的线程池和链接池将会被自定释放若是他们保持闲置的话。

你也能够自动释放,释放后未来再调用 call 的时候会被拒接。

client.dispatcher().excurorService().shutdown()

清除链接池,注意清除后,链接池的守护线程可能会马上退出。

client.connectionPool().evictAll()

若是 Client 有缓存,能够关闭。注意:再次调用一个被关闭的 cache 会发生错误。也会形成 crash。

client.cache().close();

OkHttp 在 HTTP/2 链接的时候也会使用守护线程。他们闲置的时候将自动退出。

知道有这么一回事就行,通常不会主动调用。

Call 类

Call 这个类就是用来发送 HTTP 请求和读取 HTTP 响应的一个类

Call类方法.png

这个类的方法不多,从上到下依次是:放弃请求、异步执行请求、同步执行请求。

Request 类

这个类就是至关于 http 请求中的请求报文,是用来表达请求报文的,因此这里能够设置请求的 url、请求头、请求体等等和请求报文有关的内容。

主要方法罗列:

// 获取请求 url
public HttpUrl url();
// 获取请求方法类型
public String method();
// 获取请求头
public Headers headers();
//获取请求体
public RequestBody body();
// 获取 tag
public Object tag();
// 返回缓存控制指令,永远不会是 null ,即便响应不包含 Cache-Control 响应头
public CacheControl cacheControl();
// 是不是 https 请求
public boolean isHttps();
// Resquest{method=" ",url=" ",tag = " "}
public String toString();
复制代码

request_builder.png

这是它的 Builder 中提供的方法,只设置 .url() 的时候默认是 post 请求。

RequestBody

介绍完请求报文就要介绍请求体了,这都是和 http协议紧密联系的。

RequestBody 就是用来设置请求体的,它的主要方法就是下面这个几个静态方法,用来生成对应的请求体:

request_body.png

就是经过这几个方法来产生对应的不一样的请求体。MediaType 是用来描述请求体或者响应体类型的。好比请求体类型是 json 串格式的,那对应的 MediaType 就是MediaType.parse("application/json; charset=utf-8"); ,若是上传的是文件那么对应的就是 application/octet-stream,还有几个经常使用的类型 text/plain imge/png text/x-markdown 等等。

它还有两个子类:

request_body.png

FormBody 这个请求体是咱们平时最经常使用的,就是咱们平时使用 post 请求的时候,参数是键值对的形式。就是使用这个请求体最简单了。

说深一点,对应的请求报文是:

POST /test HTTP/1.1   请求行
Host: 32.106.24.148:8080  下面都是请求头
Content-Type: application/x-www-form-urlencoded 用于指明请求体的类型。
User-Agent: PostmanRuntime/7.15.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 954bda0d-dbc2-4193-addf-a7631cab2cfa,5ba2ebed-90b4-4f35-bcf5-80c4777de471
Host: 39.106.24.148:8080
accept-encoding: gzip, deflate
content-length: 133
Connection: keep-alive
cache-control: no-cache

key0=value0&key1=value1  请求体(也是咱们的参数)
复制代码

这是发送的原始的报文格式,用代码实现的话就是

// 建立客户端
OkHttpClient client = new OkHttpclient();
// 创建请求体 
FormBody formBody = new FormBody.Builder()
                    .add("key0", "value0")
  					.add("key1","value1")
                    .build();
// 创建请求报文
Request request = new Request.Builder
								.post(formBody)
  								.url("请求url")
  								.addHeader("Content-Type", "application/x-www-form-urlencoded")
  .addHeader("User-Agent", "PostmanRuntime/7.15.0")
  .addHeader("Accept", "*/*")
  .addHeader("Cache-Control", "no-cache")
  .addHeader("Postman-Token", "954bda0d-dbc2-4193-addf-a7631cab2cfa,af7c027c-a7ba-4560-98ae-3a2a473ab88a")
  .addHeader("Host", "39.106.24.148:8080")
  .addHeader("accept-encoding", "gzip, deflate")
  .addHeader("content-length", "133")
  .addHeader("Connection", "keep-alive")
  .addHeader("cache-control", "no-cache")
  .build();
// 发起请求
client.newCall(request).excute();
复制代码

上面是使用了 FormBody 的形式,若是使用 RequestBody 的话就要更麻烦一些。

OkHttpClient client = new OkHttpClient();

MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "key0=value0&key1=value1");
Request request = new Request.Builder()
  .url("http://39.106.24.148:8080/test")
  .post(body)
  .addHeader("Content-Type", "application/x-www-form-urlencoded")
  .addHeader("User-Agent", "PostmanRuntime/7.15.0")
  .addHeader("Accept", "*/*")
  .addHeader("Cache-Control", "no-cache")
  .addHeader("Postman-Token", "954bda0d-dbc2-4193-addf-a7631cab2cfa,af7c027c-a7ba-4560-98ae-3a2a473ab88a")
  .addHeader("Host", "39.106.24.148:8080")
  .addHeader("accept-encoding", "gzip, deflate")
  .addHeader("content-length", "133")
  .addHeader("Connection", "keep-alive")
  .addHeader("cache-control", "no-cache")
  .build();
Response response = client.newCall(request).execute();
复制代码

固然平时咱们使用的时候,不用拼上这么多的请求头,我这样写的目的就是为了更加还原请求报文。

还有一个子类 MultipartBody这个能够用来构建比较复杂的请求体。

1995 年 Content-Type 的类型扩充了 multipart/form-data 用来支持向服务器发送二进制数据。若是一次提交多种类型的数据,好比:一张图片和一个文字,这个时候引入了 boundaryboundary使得 POST 能够知足这种提交多种不一样的数据类型。经过 boundary 能够实现多个不一样类型的数据同时存在在一个 Request 中。两个 boundary 之间就是一个类型的数据,而且能够从新设置 Content-Type

与 HTML 文件上传形式兼容。每块请求体都是一个请求体,能够定义本身的请求头。这些请求头能够用来描述这块请求。例如,他们的 Content-Disposition。若是 Content-Length 和 Content-Type 可用的话,他们会被自动添加到请求头中。

来看一下这种类型的请求报文是什么样的:

POST /web/UploadServlet HTTP/1.1
Content-Type: multipart/form-data; boundary=e1b05ca4-fc4e-4944-837d-cc32c43c853a
Content-Length: 66089
Host: localhost.tt.com:8080
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.5.0

–e1b05ca4-fc4e-4944-837d-cc32c43c853a
Content-Disposition: form-data; name=”file”; filename=”**.png”
Content-Type: image/png
Content-Length: 65744

fdPNG
IHDR�0B7M�iM�M�CCPIM�CC ProfileH��……………………IEND�B`�
–e1b05ca4-fc4e-4944-837d-cc32c43c853a
Content-Disposition: form-data; name=”comment”
Content-Length: 30

上传一个图
–e1b05ca4-fc4e-4944-837d-cc32c43c853a–
复制代码

第一个数据是一张 png 的图,从新设置了 Content-Type:image/png 中间的乱码就是图片的数据。这一堆数据前有一个空行,表示上下分别是请求头、请求体。

第二个数据,就是一个文本数据。

这样它们一块儿构成了请求体。

讲起来可能比较复杂,就记住,当既须要上传参数,又须要上传文件的时候用这种请求体。

MediaType mediaType = MediaType.parse("image/png");
        RequestBody requestBody = new MultipartBody.Builder()
          			// 须要设置成表单形式不然没法上传键值对参数
                .setType(MultipartBody.FORM)
                .addPart(Headers.of("Content-Disposition", "form-data;name=\"title\""),
                        RequestBody.create(null, "Square Logo"))
                .addPart(
                        Headers.of("Content-Disposition", "form-data;name=\"imge\""),
                        RequestBody.create(mediaType, new File("路径/logo.png"))
                ).
                        build();
        Request request = new Request.Builder()
                .post(requestBody)
                .url("https://api.imgur.com/3/image")
                .build();
        try {
            mOkHttpClient.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
复制代码

简化写法:

MediaType mediaType = MediaType.parse("image/png");
        RequestBody requestBody = new MultipartBody.Builder()
          		 .setType(MultipartBody.FORM)
               .addFormDataPart("title","logo")
                .addFormDataPart("img","logo.png",RequestBody.create(mediaType,new File("路径/logo.png")))
                .build();
复制代码

Content-Disposition 能够用在消息体的子部分中,用来给出其对应字段的相关信息。做为 multipart body 中的消息头,第一个参数老是固定不变的 form-data; 附加的参数不区分大小写,而且拥有参数值,参数名与参数值用等号链接,参数之间用分号分隔。参数值用双引号括起来

// 好比这样,就是这种固定的格式
"Content-Disposition","form-data;name=\"mFile\";filename=\"xxx.mp4\""

复制代码

到这里关于请求的几个重要的类就讲完了。

总结一下

只要掌握 http 请求的原理,使用起 okhttp 来也就不是什么问题了。

首先 OkHttpClient 是用来设置关于请求工具的一些参数的,好比超时时间、是否缓存等等。

Call 对象是发起 Http 请求的对象,经过 Call 对象来发起请求。

发起请求的时候,须要有请求报文,Request 对象就是对应的请求报文,能够添加对应的请求行、请求头、请求体。

提及请求体就是对应了 RequestBody 了。而后这个网络请求过程就完成了!

相关文章
相关标签/搜索