阅读 675

从 http协议角度解析okhttp

Okhttp 介绍

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

声明

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

其他优点见:Android 网络框架比较(后面更新)
说起 okhttp 的介绍,介绍完这几个关键类就可以了!

Okhttp 中几个重要类的介绍

OkHttpClient

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

Call 类的工厂,通过 OkHttpClient 才能得到 Call 对象。

OkHttpClient 使用注意

OkHttpClient 应该被共享,使用 okhttp 这个框架的时候,最好要将 OkHttpClient 设置成单例模式,所有的 HTTP 在进行请求的时候都要使用这一个 Client 。因为每个 OkHttpClient 都对应了自己的连接池和线程池。减少使用连接池和线程池可以减少延迟和内存的使用。相反的如果每个请求都创建一个 OkHttpClient 的话会很浪费内存资源。

OkHttpClient的创建

OkHttpClient 有三个创建方法

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

第二个方法:就是通过 Builder的方式来自己定义一个 OkHttpclient 。当然如果你直接 build 没有自己配置参数的话,效果和第一个方法是一样的。

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

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

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 了。然后这个网络请求过程就完成了!

关注下面的标签,发现更多相似文章
评论