OkHttp3源码详解(一) Request类

简介: 阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680每一次网络请求都是一个Request,Request是对url,method,header,body的封装,也是对Http协议中请求行,请求头,实体内容的封装 p.



每一次网络请求都是一个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字符串:

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

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

1.  HttpUrl url =  new  HttpUrl.Builder()
2.  .scheme("https")
3.  .host("www.google.com")
4.  .addPathSegment("search")
5.  .addQueryParameter("q",  "maplejaw")
6.  .build();

2.Headers

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

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

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

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

(2)构建者模式创建:``

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

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

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

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

1.  @Override
2.  public  String toString()  {
3.  StringBuilder result =  new  StringBuilder();
4.  , size = size(); i < size; i++)  {
5.  result.append(name(i)).append(": ").append(value(i)).append("\n");
6.  }
7.  return result.toString();
8.  }

10.  /** Returns the field at {@code position} or null if that is out of range. */
11.  public  String name(int index)  {
12.  ;
13.  || nameIndex >= namesAndValues.length)  {
14.  return  null;
15.  }
16.  return namesAndValues[nameIndex];
17.  }

19.  /** Returns the value at {@code index} or null if that is out of range. */
20.  public  String value(int index)  {
21.  +  ;
22.  || valueIndex >= namesAndValues.length)  {
23.  return  null;
24.  }
25.  return namesAndValues[valueIndex];
26.  }

3.RequestBody

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

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

1.  public  static  final  MediaType TEXT =  MediaType.parse("text/plain; charset=utf-8");
2.  public  static  final  MediaType STREAM =  MediaType.parse("application/octet-stream");
3.  public  static  final  MediaType JSON =  MediaType.parse("application/json; charset=utf-8");

5.  //构建字符串请求体
6.  RequestBody body1 =  RequestBody.create(TEXT,  string);
7.  //构建字节请求体
8.  RequestBody body2 =  RequestBody.create(STREAM,  byte);
9.  //构建文件请求体
10.  RequestBody body3 =  RequestBody.create(STREAM, file);
11.  //post上传json
12.  RequestBody body4 =  RequestBody.create(JSON, json);//json为String类型的

14.  //将请求体设置给请求方法内
15.  Request request =  new  Request.Builder()
16.  .url(url)
17.  .post(xx)// xx表示body1,body2,body3,body4中的某一个
18.  .build();

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

1.  //构建表单RequestBody
2.  RequestBody formBody=new  FormBody.Builder()
3.  .add("name","maplejaw")
4.  .add(")
5.  ...
6.  .build();

(3)构建分块表单请求体:(OkHttp3取消了MultipartBuilder,取而代之的是MultipartBody.Builder()

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

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

OkHttp3中RequestBody有三种创建方式

①方式一:

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

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

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

1.  RequestBody formBody=new  FormBody.Builder()
2.  .add("name","maplejaw")
3.  .add(")
4.  ...
5.  .build();

FormBody源码

1.  public  final  class  FormBody  extends  RequestBody  {
2.  private  static  final  MediaType CONTENT_TYPE =
3.  MediaType.parse("application/x-www-form-urlencoded");

5.  private  final  List<String> encodedNames;
6.  private  final  List<String> encodedValues;

8.  private  FormBody(List<String> encodedNames,  List<String> encodedValues)  {
9.  this.encodedNames =  Util.immutableList(encodedNames);
10.  this.encodedValues =  Util.immutableList(encodedValues);
11.  }

13.  /** The number of key-value pairs in this form-encoded body. */
14.  public  int size()  {
15.  return encodedNames.size();
16.  }

18.  public  String encodedName(int index)  {
19.  return encodedNames.get(index);
20.  }

22.  public  String name(int index)  {
23.  return percentDecode(encodedName(index),  true);
24.  }

26.  public  String encodedValue(int index)  {
27.  return encodedValues.get(index);
28.  }

30.  public  String value(int index)  {
31.  return percentDecode(encodedValue(index),  true);
32.  }

34.  @Override  public  MediaType contentType()  {
35.  return CONTENT_TYPE;
36.  }

38.  @Override  public  long contentLength()  {
39.  return writeOrCountBytes(null,  true);
40.  }

42.  @Override  public  void writeTo(BufferedSink sink)  throws  IOException  {
43.  writeOrCountBytes(sink,  false);
44.  }

46.  /**
47.  * Either writes this request to {@code sink} or measures its content length. We have one method
48.  * do double-duty to make sure the counting and content are consistent, particularly when it comes
49.  * to awkward operations like measuring the encoded length of header strings, or the
50.  * length-in-digits of an encoded integer.
51.  */
52.  private  long writeOrCountBytes(BufferedSink sink,  boolean countBytes)  {
53.  long byteCount =  0L;

55.  Buffer buffer;
56.  if  (countBytes)  {
57.  buffer =  new  Buffer();
58.  }  else  {
59.  buffer = sink.buffer();
60.  }

62.  , size = encodedNames.size(); i < size; i++)  {
63.  ) buffer.writeByte('&');
64.  buffer.writeUtf8(encodedNames.get(i));
65.  buffer.writeByte('=');
66.  buffer.writeUtf8(encodedValues.get(i));
67.  }

69.  if  (countBytes)  {
70.  byteCount = buffer.size();
71.  buffer.clear();
72.  }

74.  return byteCount;
75.  }

77.  public  static  final  class  Builder  {
78.  private  final  List<String> names =  new  ArrayList<>();
79.  private  final  List<String> values =  new  ArrayList<>();

81.  public  Builder add(String name,  String value)  {
82.  names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET,  false,  false,  true,  true));
83.  values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET,  false,  false,  true,  true));
84.  return  this;
85.  }

87.  public  Builder addEncoded(String name,  String value)  {
88.  names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET,  true,  false,  true,  true));
89.  values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET,  true,  false,  true,  true));
90.  return  this;
91.  }

93.  public  FormBody build()  {
94.  return  new  FormBody(names, values);
95.  }
96.  }
97.  }

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

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

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

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

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

1.  public  static  Part createFormData(String name,  String filename,  RequestBody body)  {
2.  if  (name ==  null)  {
3.  throw  new  NullPointerException("name == null");
4.  }
5.  StringBuilder disposition =  new  StringBuilder("form-data; name=");
6.  appendQuotedString(disposition, name);

8.  if  (filename !=  null)  {
9.  disposition.append("; filename=");
10.  appendQuotedString(disposition, filename);
11.  }

13.  return create(Headers.of("Content-Disposition", disposition.toString()), body);
14.  }

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

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

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

1.  private  long writeOrCountBytes(BufferedSink sink,  boolean countBytes)  throws  IOException  {
2.  long byteCount =  0L;

4.  Buffer byteCountBuffer =  null;
5.  if  (countBytes)  {
6.  //如果是计算大小的话,就new个
7.  sink = byteCountBuffer =  new  Buffer();
8.  }
9.  //循环块
10.  , partCount = parts.size(); p < partCount; p++)  {
11.  Part part = parts.get(p);
12.  //获取每个块的头
13.  Headers headers = part.headers;
14.  //获取每个块的请求体
15.  RequestBody body = part.body;

17.  //写 --xxxxxxxxxx 边界
18.  sink.write(DASHDASH);
19.  sink.write(boundary);
20.  sink.write(CRLF);

22.  //写块的头
23.  if  (headers !=  null)  {
24.  , headerCount = headers.size(); h < headerCount; h++)  {
25.  sink.writeUtf8(headers.name(h))
26.  .write(COLONSPACE)
27.  .writeUtf8(headers.value(h))
28.  .write(CRLF);
29.  }
30.  }

32.  //写块的Content_Type
33.  MediaType contentType = body.contentType();
34.  if  (contentType !=  null)  {
35.  sink.writeUtf8("Content-Type: ")
36.  .writeUtf8(contentType.toString())
37.  .write(CRLF);
38.  }

40.  //写块的大小
41.  long contentLength = body.contentLength();
42.  )  {
43.  sink.writeUtf8("Content-Length: ")
44.  .writeDecimalLong(contentLength)
45.  .write(CRLF);
46.  }  else  if  (countBytes)  {
47.  // We can't measure the body's size without the sizes of its components.
48.  //如果有个块没有这名大小,就返回-1.
49.  byteCountBuffer.clear();
50.  return  -1L;
51.  }

53.  sink.write(CRLF);

55.  //如果是计算大小就累加,否则写入BufferedSink
56.  if  (countBytes)  {
57.  byteCount += contentLength;
58.  }  else  {
59.  body.writeTo(sink);
60.  }

62.  sink.write(CRLF);
63.  }

65.  //写 --xxxxxxxxxx-- 结束边界
66.  sink.write(DASHDASH);
67.  sink.write(boundary);
68.  sink.write(DASHDASH);
69.  sink.write(CRLF);

71.  if  (countBytes)  {
72.  byteCount += byteCountBuffer.size();
73.  byteCountBuffer.clear();
74.  }

76.  return byteCount;
77.  }

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类

①常用的函数

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

②CacheControl的两个常量:

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

③请求时如何使用:

1.  final  CacheControl.Builder builder =  new  CacheControl.Builder();
2.  builder.maxAge(,  TimeUnit.MILLISECONDS);
3.  CacheControl cache = builder.build();
4.  final  Request request =  new  Request.Builder().cacheControl(cache).url(requestUrl).build();
5.  final  Call call = mOkHttpClient.newCall(request);//
6.  call.enqueue(new  Callback()  {
7.  @Override
8.  public  void onFailure(Call call,  IOException e)  {
9.  failedCallBack("访问失败", callBack);
10.  Log.e(TAG, e.toString());
11.  }

13.  @Override
14.  public  void onResponse(Call call,  Response response)  throws  IOException  {
15.  if  (response.isSuccessful())  {
16.  String  string  = response.body().string();
17.  Log.e(TAG,  "response ----->"  +  string);
18.  successCallBack((T)  string, callBack);
19.  }  else  {
20.  failedCallBack("服务器错误", callBack);
21.  }
22.  }
23.  });
24.  return call;
25.  }  catch  (Exception e)  {
26.  Log.e(TAG, e.toString());
27.  }

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

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

原文链接 https://www.bbsmax.com/A/MAzArw9OJ9/

相关文章
|
9月前
|
Java Android开发
okhttp工具类封装
okhttp工具类封装
137 0
|
JSON 程序员 数据格式
retrofit动态设置URL采用反射机制解决
前几天开发项目的时候碰到一件比较头疼的事,就是获取json数据的主域名不一样,最简单粗暴的方法就是重新写一份retrofit,但这是十分低效的,写重复性代码就程序员最不想做的一件事之一,所以找了好多文章,最后找到了一个反射机制解决此问题的
117 0
|
XML 网络安全 Android开发
Volley学习笔记 | 关于源码中Request、Response、Listener泛型的理解(附XMLRequest自定义代码)
Volley学习笔记 | 关于源码中Request、Response、Listener泛型的理解(附XMLRequest自定义代码)
|
Java API Python
Java:retrofit2发送http网络请求
Java:retrofit2发送http网络请求
206 0
|
Java 网络安全
JAVA HttpClient 远程调用接口doGet、doPost工具类
JAVA HttpClient 远程调用接口doGet、doPost工具类
850 0
|
域名解析 存储 缓存
【OkHttp】OkHttp 源码分析 ( OkHttpClient.Builder 构造器源码分析 )
【OkHttp】OkHttp 源码分析 ( OkHttpClient.Builder 构造器源码分析 )
309 0
OkHttp3源码详解(三) 拦截器
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/4743806801.构造Demo 首先构造一个简单的异步网络访问Demo: 1. OkHttpClient client = new OkHttpClient(); 2.
Okhttp3源码解析(2)-Request分析
前言 前面我们讲了Okhttp的基本用法Okhttp3源码解析(1)-OkHttpClient分析 今天主要分析下Request源码! Request初始化 当我们构建完OkHttpClient对象,需要构造Request对象,构造方式如下: 1.
947 0
Okhttp3源码解析(1)-OkHttpClient分析
前言 上篇文章我们讲了Okhttp的基本用法,今天根据上节讲到请求流程来分析源码,那么第一步就是实例化OkHttpClient对象,所以我们今天主要分析下OkHttpClient源码! 初始化-构造方式 创建 OkHttpClient实例的两种方式 1.
2824 0
|
JSON Android开发 数据格式
Okhttp3-基本用法
前言 Okhttp官网Okhttp-Github android网络框架之OKhttp一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,由移动支付Square公司贡献(该公司还贡献了Picasso) 用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient) 官网的解释如下: 基本用法 1.集成 1.1.依赖 implementation 'com.squareup.okhttp3:okhttp:3.11.0' 可以去Okhttp-Github 查看并依赖最新的版本。
3080 0