您现在的位置是:首页 > 文章详情

OkHttp3源码详解(一) Request类

日期:2019-11-17点击:419



每一次网络请求都是一个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/

原文链接:https://yq.aliyun.com/articles/727358
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章