每一次網絡請求都是一個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并不會修改另一個消息處理過程中的緩存處理過程。請求時的緩存指令有下幾種:
- Public:所有内容都将被緩存(用戶端和代理伺服器都可緩存)。
- Private:内容隻緩存到私有緩存中(僅用戶端可以緩存,代理伺服器不可緩存)
- no-cache:請求或者響應消息不能緩存
- no-store:不使用緩存,也不存儲緩存
- max-age:緩存的内容将在指定時間(秒)後失效, 這個選項隻在HTTP 1.1可用, 并如果和Last-Modified一起使用時, 優先級較高
- 在 xxx 秒後,浏覽器重新發送請求到伺服器,指定時間(秒)内,用戶端會直接傳回cache而不會發起網絡請求,若過期會自動發起網絡請求
- min-fresh:訓示用戶端可以接收響應時間小于目前時間加上指定時間的響應。
- 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/