每一次网络请求都是一个Request,Request是对url,method,header,body的封装,也是对Http协议中请求行,请求头,实体内容的封装

 public final class Request {
private final HttpUrl url;
private final String method;
private final Headers headers;
private final RequestBody body;
private final Object tag; private volatile CacheControl cacheControl; // Lazily initialized.

1.HttpUrl

HttpUrl主要用来规范普通的url连接,并且解析url的组成部分

现通过下面的例子来示例httpUrl的使用
https://www.google.com/search?q=maplejaw
①使用parse解析url字符串:

HttpUrl url = HttpUrl.parse("https://www.google.com/search?q=maplejaw");

②通过构造者模式来常见:

 HttpUrl url = new HttpUrl.Builder()
.scheme("https")
.host("www.google.com")
.addPathSegment("search")
.addQueryParameter("q", "maplejaw")
.build();

2.Headers

Headers用于配置请求头,对于请求头配置大家一定不陌生吧,比如Content-Type,User-Agent和Cache-Control等等。

创建Headers也有两种方式。如下:
(1)of()创建:传入的数组必须是偶数对,否则会抛出异常。

Headers.of("name1","value1","name2","value2",.....);

还可以使用它的重载方法of(Map<String,String> map)方法来创建

(2)构建者模式创建:

Headers mHeaders=new Headers.Builder()
.set("name1","value1")//set表示name1是唯一的,会覆盖掉已经存在的
.add("name2","value2")//add不会覆盖已经存在的头,可以存在多个
.build();

我们来看一下Header的内部,源码就不粘贴了很简单,Headers内部是通过一个数组来保存header private final String[] namesAndValues;大家可能会有这样的疑问,为什么不用Map而用数组呢?因为Map的Key是唯一的,而header要求不唯一

另外,数组方便取数据吗?很方便,我们来看着三个方法

最后通过toString方法转变成String,方便写入请求头,

 @Override
public String toString() {
StringBuilder result = new StringBuilder();
for (int i = , size = size(); i < size; i++) {
result.append(name(i)).append(": ").append(value(i)).append("\n");
}
return result.toString();
} /** Returns the field at {@code position} or null if that is out of range. */
public String name(int index) {
int nameIndex = index * ;
if (nameIndex < || nameIndex >= namesAndValues.length) {
return null;
}
return namesAndValues[nameIndex];
} /** Returns the value at {@code index} or null if that is out of range. */
public String value(int index) {
int valueIndex = index * + ;
if (valueIndex < || valueIndex >= namesAndValues.length) {
return null;
}
return namesAndValues[valueIndex];
}

3.RequestBody

requestBody也就是请求实体内容,我们先来看一下如何来构建一个RequestBody

(1)Request.create()方法创建

 public static final MediaType TEXT = MediaType.parse("text/plain; charset=utf-8");
public static final MediaType STREAM = MediaType.parse("application/octet-stream");
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); //构建字符串请求体
RequestBody body1 = RequestBody.create(TEXT, string);
//构建字节请求体
RequestBody body2 = RequestBody.create(STREAM, byte);
//构建文件请求体
RequestBody body3 = RequestBody.create(STREAM, file);
//post上传json
RequestBody body4 = RequestBody.create(JSON, json);//json为String类型的 //将请求体设置给请求方法内
Request request = new Request.Builder()
.url(url)
.post(xx)// xx表示body1,body2,body3,body4中的某一个
.build();

(2)构建表单请求体,提交键值对(OkHttp3没有FormEncodingBuilder这个类,替代它的是功能更加强大的FormBody:)

 //构建表单RequestBody
RequestBody formBody=new FormBody.Builder()
.add("name","maplejaw")
.add("age","")
...
.build();

(3)构建分块表单请求体:(OkHttp3取消了MultipartBuilder,取而代之的是MultipartBody.Builder()
                  既可以添加表单,又可以也可以添加文件等二进制数据)

 public static final MediaType STREAM = MediaType.parse("application/octet-stream");
//构建表单RequestBody
RequestBody multipartBody=new MultipartBody.Builder()
.setType(MultipartBody.FORM)//指明为 multipart/form-data 类型
.addFormDataPart("age","") //添加表单数据
.addFormDataPart("avatar","111.jpg",RequestBody.create(STREAM,file)) //添加文件,其中avatar为表单名,111.jpg为文件名。
.addPart(..)//该方法用于添加RequestBody,Headers和添加自定义Part,一般来说以上已经够用
.build();

知道了RequestBody的创建,我们来看一下源码
RequestBody也就是请求实体内容,对于一个Get请求时没有实体内容的,Post提交才有,而且浏览器与服务器通信时基本上只有表单上传才会用到POST提交,所以RequestBody其实也就是封装了浏览器表单上传时对应的实体内容,对于实体内容是什么样还不清楚的可以去看一下我的一篇博客Android的Http协议的通信详解

OkHttp3中RequestBody有三种创建方式

①方式一:

 public static RequestBody create(MediaType contentType, String content) {
Charset charset = Util.UTF_8;
if (contentType != null) {
charset = contentType.charset();//MediaType的为请求头中的ContentType创建方式:public static final MediaType TEXT =
//MediaType.parse("text/plain; charset=utf-8")
if (charset == null) {
charset = Util.UTF_8;<span style="font-family:Microsoft YaHei;">//如果contentType中没有指定charset,默认使用UTF-8</span>
contentType = MediaType.parse(contentType + "; charset=utf-8");
}
}
byte[] bytes = content.getBytes(charset);
return create(contentType, bytes);
}

②方式二:FormBody表单创建,我们来看一下

FormBody用于普通post表单上传键值对,我们先来看一下创建的方法,再看源码

  RequestBody formBody=new FormBody.Builder()
.add("name","maplejaw")
.add("age","")
...
.build();

FormBody源码

 public final class FormBody extends RequestBody {
private static final MediaType CONTENT_TYPE =
MediaType.parse("application/x-www-form-urlencoded"); private final List<String> encodedNames;
private final List<String> encodedValues; private FormBody(List<String> encodedNames, List<String> encodedValues) {
this.encodedNames = Util.immutableList(encodedNames);
this.encodedValues = Util.immutableList(encodedValues);
} /** The number of key-value pairs in this form-encoded body. */
public int size() {
return encodedNames.size();
} public String encodedName(int index) {
return encodedNames.get(index);
} public String name(int index) {
return percentDecode(encodedName(index), true);
} public String encodedValue(int index) {
return encodedValues.get(index);
} public String value(int index) {
return percentDecode(encodedValue(index), true);
} @Override public MediaType contentType() {
return CONTENT_TYPE;
} @Override public long contentLength() {
return writeOrCountBytes(null, true);
} @Override public void writeTo(BufferedSink sink) throws IOException {
writeOrCountBytes(sink, false);
} /**
* Either writes this request to {@code sink} or measures its content length. We have one method
* do double-duty to make sure the counting and content are consistent, particularly when it comes
* to awkward operations like measuring the encoded length of header strings, or the
* length-in-digits of an encoded integer.
*/
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) {
long byteCount = 0L; Buffer buffer;
if (countBytes) {
buffer = new Buffer();
} else {
buffer = sink.buffer();
} for (int i = , size = encodedNames.size(); i < size; i++) {
if (i > ) buffer.writeByte('&');
buffer.writeUtf8(encodedNames.get(i));
buffer.writeByte('=');
buffer.writeUtf8(encodedValues.get(i));
} if (countBytes) {
byteCount = buffer.size();
buffer.clear();
} return byteCount;
} public static final class Builder {
private final List<String> names = new ArrayList<>();
private final List<String> values = new ArrayList<>(); public Builder add(String name, String value) {
names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true));
values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true));
return this;
} public Builder addEncoded(String name, String value) {
names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, true, false, true, true));
values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, true, false, true, true));
return this;
} public FormBody build() {
return new FormBody(names, values);
}
}
}

我们主要来看一下方法writeOrCountBytes,通过writeOrCountBytes来计算请求体大小和将请求体写入BufferedSink。

至于BufferSink和Buffer类,这两个类是Okio中的类,Buffer相当于一个缓存区,BufferedSink相当于OutputStream,它扩展了

OutputStream的功能,Okio的完整源码我后续也会写博客

③方式三:MultipartBody分块表单创建

MultipartBody, 既可以添加表单,又可以也可以添加文件等二进制数据,我们就看几个重要的方法

 public static Part createFormData(String name, String filename, RequestBody body) {
if (name == null) {
throw new NullPointerException("name == null");
}
StringBuilder disposition = new StringBuilder("form-data; name=");
appendQuotedString(disposition, name); if (filename != null) {
disposition.append("; filename=");
appendQuotedString(disposition, filename);
} return create(Headers.of("Content-Disposition", disposition.toString()), body);
}

我们来看这个方法,我们是addPart还是addFormDataPart最终都走到了这个方法,封装成一个Part对象,也就是实体内容中

的Content-Disposition跟文件二进制流或者键值对的值

MultipartBody和FormBody大体上相同,主要区别在于writeOrCountBytes方法,分块表单主要是将每个块的大小进行累加来求出请求体大小,如果其中有一个块没有指定大小,就会返回-1。所以分块表单中如果包含文件,默认是无法计算出大小的,除非你自己给文件的RequestBody指定contentLength。

  private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
long byteCount = 0L; Buffer byteCountBuffer = null;
if (countBytes) {
//如果是计算大小的话,就new个
sink = byteCountBuffer = new Buffer();
}
//循环块
for (int p = , partCount = parts.size(); p < partCount; p++) {
Part part = parts.get(p);
//获取每个块的头
Headers headers = part.headers;
//获取每个块的请求体
RequestBody body = part.body; //写 --xxxxxxxxxx 边界
sink.write(DASHDASH);
sink.write(boundary);
sink.write(CRLF); //写块的头
if (headers != null) {
for (int h = , headerCount = headers.size(); h < headerCount; h++) {
sink.writeUtf8(headers.name(h))
.write(COLONSPACE)
.writeUtf8(headers.value(h))
.write(CRLF);
}
} //写块的Content_Type
MediaType contentType = body.contentType();
if (contentType != null) {
sink.writeUtf8("Content-Type: ")
.writeUtf8(contentType.toString())
.write(CRLF);
} //写块的大小
long contentLength = body.contentLength();
if (contentLength != -) {
sink.writeUtf8("Content-Length: ")
.writeDecimalLong(contentLength)
.write(CRLF);
} else if (countBytes) {
// We can't measure the body's size without the sizes of its components.
//如果有个块没有这名大小,就返回-1.
byteCountBuffer.clear();
return -1L;
} sink.write(CRLF); //如果是计算大小就累加,否则写入BufferedSink
if (countBytes) {
byteCount += contentLength;
} else {
body.writeTo(sink);
} sink.write(CRLF);
} //写 --xxxxxxxxxx-- 结束边界
sink.write(DASHDASH);
sink.write(boundary);
sink.write(DASHDASH);
sink.write(CRLF); if (countBytes) {
byteCount += byteCountBuffer.size();
byteCountBuffer.clear();
} return byteCount;
}

4.CacheControl

( 1)  Cache-Control:

Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令有下几种:

  1. Public:所有内容都将被缓存(客户端和代理服务器都可缓存)。
  2. Private:内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)
  3. no-cache:请求或者响应消息不能缓存
  4. no-store:不使用缓存,也不存储缓存
  5. max-age:缓存的内容将在指定时间(秒)后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高
  6. 在 xxx 秒后,浏览器重新发送请求到服务器,指定时间(秒)内,客户端会直接返回cache而不会发起网络请求,若过期会自动发起网络请求
  7. min-fresh:指示客户端可以接收响应时间小于当前时间加上指定时间的响应。
  8. max-stale:指示客户端可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。

(2)CacheControl类

①常用的函数

 final CacheControl.Builder builder = new CacheControl.Builder();
builder.noCache();//不使用缓存,全部走网络
builder.noStore();//不使用缓存,也不存储缓存
builder.onlyIfCached();//只使用缓存
builder.noTransform();//禁止转码
builder.maxAge(, TimeUnit.MILLISECONDS);//指示客户机可以接收生存期不大于指定时间的响应。
builder.maxStale(, TimeUnit.SECONDS);//指示客户机可以接收超出超时期间的响应消息
builder.minFresh(, TimeUnit.SECONDS);//指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
CacheControl cache = builder.build();//cacheControl

②CacheControl的两个常量:

 public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();//不使用缓存
public static final CacheControl FORCE_CACHE = new Builder()
.onlyIfCached()
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
.build();//只使用缓存

③请求时如何使用:

final CacheControl.Builder builder = new CacheControl.Builder();
builder.maxAge(, TimeUnit.MILLISECONDS);
CacheControl cache = builder.build();
final Request request = new Request.Builder().cacheControl(cache).url(requestUrl).build();
final Call call = mOkHttpClient.newCall(request);//
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
failedCallBack("访问失败", callBack);
Log.e(TAG, e.toString());
} @Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String string = response.body().string();
Log.e(TAG, "response ----->" + string);
successCallBack((T) string, callBack);
} else {
failedCallBack("服务器错误", callBack);
}
}
});
return call;
} catch (Exception e) {
Log.e(TAG, e.toString());
}

以上如果Cache没有过期会直接返回cache而不会去发起网络请求,若过期自动发起网络请求,注意:如果您使用FORCE_CACHE和网络的响应需求,OkHttp则会返回一个504提示,告诉你不可满足请求响应,所以我们加一个判断在没有网络的情况下使用

     //判断网络是否连接
boolean connected = NetworkUtil.isConnected(context);
if (!connected) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}

OkHttp3源码详解(一) Request类的更多相关文章

  1. OkHttp3源码详解(五) okhttp连接池复用机制

    1.概述 提高网络性能优化,很重要的一点就是降低延迟和提升响应速度. 通常我们在浏览器中发起请求的时候header部分往往是这样的 keep-alive 就是浏览器和服务端之间保持长连接,这个连接是可 ...

  2. OkHttp3源码详解(六) Okhttp任务队列工作原理

    1 概述 1.1 引言 android完成非阻塞式的异步请求的时候都是通过启动子线程的方式来解决,子线程执行完任务的之后通过handler的方式来和主线程来完成通信.无限制的创建线程,会给系统带来大量 ...

  3. OkHttp3源码详解(三) 拦截器-RetryAndFollowUpInterceptor

    最大恢复追逐次数: ; 处理的业务: 实例化StreamAllocation,初始化一个Socket连接对象,获取到输入/输出流()基于Okio 开启循环,执行下一个调用链(拦截器),等待返回结果(R ...

  4. OkHttp3源码详解(三) 拦截器

    1.构造Demo 首先构造一个简单的异步网络访问Demo: OkHttpClient client = new OkHttpClient(); Request request = new Reques ...

  5. OkHttp3源码详解(二) 整体流程

    1.简单使用 同步: @Override public Response execute() throws IOException { synchronized (this) { if (execut ...

  6. RocketMQ源码详解 | Producer篇 · 其二:消息组成、发送链路

    概述 在上一节 RocketMQ源码详解 | Producer篇 · 其一:Start,然后 Send 一条消息 中,我们了解了 Producer 在发送消息的流程.这次我们再来具体下看消息的构成与其 ...

  7. RocketMQ源码详解 | Consumer篇 · 其一:消息的 Pull 和 Push

    概述 当消息被存储后,消费者就会将其消费. 这句话简要的概述了一条消息的最总去向,也引出了本文将讨论的问题: 消息什么时候才对被消费者可见? 是在 page cache 中吗?还是在落盘后?还是像 K ...

  8. RocketMQ源码详解 | Broker篇 · 其五:高可用之主从架构

    概述 对于一个消息中间件来讲,高可用功能是极其重要的,RocketMQ 当然也具有其对应的高可用方案. 在 RocketMQ 中,有主从架构和 Dledger 两种高可用方案: 第一种通过主 Brok ...

  9. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

随机推荐

  1. Maven(个人整理)(一,未完待续)

                                                      Maven 1. 什么是Maven? 乍一看,Maven看起来很多东西,但简而言之,Maven试图将 ...

  2. ubuntu下TFTP Server 的安装和使用方法

    tftp是一种于1981年在RFC 783中定义的简化的文件传输协议(FTP).小型文件传输协议非常简单,通过少量存储器就能轻松实现 ——这在当时是很重要的考虑因素.所以TFTP被用于引导计算机,例如 ...

  3. Maven - 实例-6-聚合与继承

    创建项目 xxx - 继承自testDep.PPP <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi= ...

  4. linux中Java项目占用cpu、内存过高时的排查经历

    一.使用top命令查看占用高资源的java项目的进程ID(pid): top 二.查看该进程中的线程所占用资源的情况:top -Hp pid 三.查看该线程对应的16进制:printf %x 1112 ...

  5. 由浅入深:CNN中卷积层与转置卷积层的关系

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由forrestlin发表于云+社区专栏 导语:转置卷积层(Transpose Convolution Layer)又称反卷积层或分数卷 ...

  6. php,vue,vue-ssr 做出来的页面有什么区别?

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由shirishiyue发表于云+社区专栏 目前我这边的web页面,都是采用php+smarty模板生成的,是一种比较早期的开发模式.好 ...

  7. docker使用技巧小记

    1.在使用docker的时候有很多人习惯使用官方镜像.有的人喜欢自己制作镜像,有的时候都是使用默认的配置启动的服务,或者自己在制作镜像的时候直接将配置文件打包到镜像里面了.有的时候会碰到要修改配置文件 ...

  8. 深入理解JavaScript的事件循环(Event Loop)

    一.什么是事件循环 JS的代码执行是基于一种事件循环的机制,之所以称作事件循环,MDN给出的解释为 因为它经常被用于类似如下的方式来实现 while (queue.waitForMessage()) ...

  9. PHP 九九乘法表的4种表达方式

    九九乘法表的四种不同表现形式 x轴对称: //第一种 for($i=1;$i<=9;$i++){ for($j=1;$j<=$i;$j++) { echo $i.'x'.$j.'='.$i ...

  10. [LeetCode解题报告] 502. IPO

    题目描述 假设 LeetCode 即将开始其 IPO.为了以更高的价格将股票卖给风险投资公司,LeetCode希望在 IPO 之前开展一些项目以增加其资本. 由于资源有限,它只能在 IPO 之前完成最 ...