OkHttp3源碼詳解(一) Request類


每一次網(wǎng)絡(luò)請(qǐng)求都是一個(gè)Request,Request是對(duì)url,method,header,body的封裝,也是對(duì)Http協(xié)議中請(qǐng)求行,請(qǐng)求頭,實(shí)體內(nèi)容的封裝
1 public final class Request { 2 private final HttpUrl url; 3 private final String method; 4 private final Headers headers; 5 private final RequestBody body; 6 private final Object tag; 7 8 private volatile CacheControl cacheControl; // Lazily initialized.
1.HttpUrl
HttpUrl主要用來(lái)規(guī)范普通的url連接,并且解析url的組成部分
現(xiàn)通過下面的例子來(lái)示例httpUrl的使用
https://www.google.com/search?q=maplejaw
①使用parse解析url字符串:
HttpUrl url = HttpUrl.parse("https://www.google.com/search?q=maplejaw");
②通過構(gòu)造者模式來(lái)常見:
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用于配置請(qǐng)求頭,對(duì)于請(qǐng)求頭配置大家一定不陌生吧,比如Content-Type,User-Agent和Cache-Control等等。
創(chuàng)建Headers也有兩種方式。如下:
(1)of()創(chuàng)建:傳入的數(shù)組必須是偶數(shù)對(duì),否則會(huì)拋出異常。
Headers.of("name1","value1","name2","value2",.....);
還可以使用它的重載方法of(Map<String,String> map)方法來(lái)創(chuàng)建
(2)構(gòu)建者模式創(chuàng)建:
Headers mHeaders=new Headers.Builder() .set("name1","value1")//set表示name1是唯一的,會(huì)覆蓋掉已經(jīng)存在的 .add("name2","value2")//add不會(huì)覆蓋已經(jīng)存在的頭,可以存在多個(gè) .build();
我們來(lái)看一下Header的內(nèi)部,源碼就不粘貼了很簡(jiǎn)單,Headers內(nèi)部是通過一個(gè)數(shù)組來(lái)保存header private final String[] namesAndValues;大家可能會(huì)有這樣的疑問,為什么不用Map而用數(shù)組呢?因?yàn)镸ap的Key是唯一的,而header要求不唯一
另外,數(shù)組方便取數(shù)據(jù)嗎?很方便,我們來(lái)看著三個(gè)方法
最后通過toString方法轉(zhuǎn)變成String,方便寫入請(qǐng)求頭,
1 @Override 2 public String toString() { 3 StringBuilder result = new StringBuilder(); 4 for (int i = 0, size = size(); i < size; i++) { 5 result.append(name(i)).append(": ").append(value(i)).append("\n"); 6 } 7 return result.toString(); 8 } 9 10 /** Returns the field at {@code position} or null if that is out of range. */ 11 public String name(int index) { 12 int nameIndex = index * 2; 13 if (nameIndex < 0 || nameIndex >= namesAndValues.length) { 14 return null; 15 } 16 return namesAndValues[nameIndex]; 17 } 18 19 /** Returns the value at {@code index} or null if that is out of range. */ 20 public String value(int index) { 21 int valueIndex = index * 2 + 1; 22 if (valueIndex < 0 || valueIndex >= namesAndValues.length) { 23 return null; 24 } 25 return namesAndValues[valueIndex]; 26 }
3.RequestBody
requestBody也就是請(qǐng)求實(shí)體內(nèi)容,我們先來(lái)看一下如何來(lái)構(gòu)建一個(gè)RequestBody
(1)Request.create()方法創(chuàng)建

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"); 4 5 //構(gòu)建字符串請(qǐng)求體 6 RequestBody body1 = RequestBody.create(TEXT, string); 7 //構(gòu)建字節(jié)請(qǐng)求體 8 RequestBody body2 = RequestBody.create(STREAM, byte); 9 //構(gòu)建文件請(qǐng)求體 10 RequestBody body3 = RequestBody.create(STREAM, file); 11 //post上傳json 12 RequestBody body4 = RequestBody.create(JSON, json);//json為String類型的 13 14 //將請(qǐng)求體設(shè)置給請(qǐng)求方法內(nèi) 15 Request request = new Request.Builder() 16 .url(url) 17 .post(xx)// xx表示body1,body2,body3,body4中的某一個(gè) 18 .build(); 19
(2)構(gòu)建表單請(qǐng)求體,提交鍵值對(duì)(OkHttp3沒有FormEncodingBuilder這個(gè)類,替代它的是功能更加強(qiáng)大的FormBody:)
//構(gòu)建表單RequestBody RequestBody formBody=new FormBody.Builder() .add("name","maplejaw") .add("age","18") ... .build();
(3)構(gòu)建分塊表單請(qǐng)求體:(OkHttp3取消了MultipartBuilder,取而代之的是MultipartBody.Builder()
既可以添加表單,又可以也可以添加文件等二進(jìn)制數(shù)據(jù))
1 public static final MediaType STREAM = MediaType.parse("application/octet-stream"); 2 //構(gòu)建表單RequestBody 3 RequestBody multipartBody=new MultipartBody.Builder() 4 .setType(MultipartBody.FORM)//指明為 multipart/form-data 類型 5 .addFormDataPart("age","20") //添加表單數(shù)據(jù) 6 .addFormDataPart("avatar","111.jpg",RequestBody.create(STREAM,file)) //添加文件,其中avatar為表單名,111.jpg為文件名。 7 .addPart(..)//該方法用于添加RequestBody,Headers和添加自定義Part,一般來(lái)說(shuō)以上已經(jīng)夠用 8 .build(); 9 10
知道了RequestBody的創(chuàng)建,我們來(lái)看一下源碼
RequestBody也就是請(qǐng)求實(shí)體內(nèi)容,對(duì)于一個(gè)Get請(qǐng)求時(shí)沒有實(shí)體內(nèi)容的,Post提交才有,而且瀏覽器與服務(wù)器通信時(shí)基本上只有表單上傳才會(huì)用到POST提交,所以RequestBody其實(shí)也就是封裝了瀏覽器表單上傳時(shí)對(duì)應(yīng)的實(shí)體內(nèi)容,對(duì)于實(shí)體內(nèi)容是什么樣還不清楚的可以去看一下我的一篇博客Android的Http協(xié)議的通信詳解
OkHttp3中RequestBody有三種創(chuàng)建方式
①方式一:
public static RequestBody create(MediaType contentType, String content) { Charset charset = Util.UTF_8; if (contentType != null) { charset = contentType.charset();//MediaType的為請(qǐng)求頭中的ContentType創(chuàng)建方式: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,默認(rèn)使用UTF-8</span> contentType = MediaType.parse(contentType + "; charset=utf-8"); } } byte[] bytes = content.getBytes(charset); return create(contentType, bytes); }
②方式二:FormBody表單創(chuàng)建,我們來(lái)看一下
FormBody用于普通post表單上傳鍵值對(duì),我們先來(lái)看一下創(chuàng)建的方法,再看源碼
1 RequestBody formBody=new FormBody.Builder() 2 .add("name","maplejaw") 3 .add("age","18") 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"); 4 5 private final List<String> encodedNames; 6 private final List<String> encodedValues; 7 8 private FormBody(List<String> encodedNames, List<String> encodedValues) { 9 this.encodedNames = Util.immutableList(encodedNames); 10 this.encodedValues = Util.immutableList(encodedValues); 11 } 12 13 /** The number of key-value pairs in this form-encoded body. */ 14 public int size() { 15 return encodedNames.size(); 16 } 17 18 public String encodedName(int index) { 19 return encodedNames.get(index); 20 } 21 22 public String name(int index) { 23 return percentDecode(encodedName(index), true); 24 } 25 26 public String encodedValue(int index) { 27 return encodedValues.get(index); 28 } 29 30 public String value(int index) { 31 return percentDecode(encodedValue(index), true); 32 } 33 34 @Override public MediaType contentType() { 35 return CONTENT_TYPE; 36 } 37 38 @Override public long contentLength() { 39 return writeOrCountBytes(null, true); 40 } 41 42 @Override public void writeTo(BufferedSink sink) throws IOException { 43 writeOrCountBytes(sink, false); 44 } 45 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; 54 55 Buffer buffer; 56 if (countBytes) { 57 buffer = new Buffer(); 58 } else { 59 buffer = sink.buffer(); 60 } 61 62 for (int i = 0, size = encodedNames.size(); i < size; i++) { 63 if (i > 0) buffer.writeByte('&'); 64 buffer.writeUtf8(encodedNames.get(i)); 65 buffer.writeByte('='); 66 buffer.writeUtf8(encodedValues.get(i)); 67 } 68 69 if (countBytes) { 70 byteCount = buffer.size(); 71 buffer.clear(); 72 } 73 74 return byteCount; 75 } 76 77 public static final class Builder { 78 private final List<String> names = new ArrayList<>(); 79 private final List<String> values = new ArrayList<>(); 80 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 } 86 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 } 92 93 public FormBody build() { 94 return new FormBody(names, values); 95 } 96 } 97 }
,通過我們主要來(lái)看一下方法writeOrCountByteswriteOrCountBytes來(lái)計(jì)算請(qǐng)求體大小和將請(qǐng)求體寫入BufferedSink。
至于BufferSink和Buffer類,這兩個(gè)類是Okio中的類,Buffer相當(dāng)于一個(gè)緩存區(qū),BufferedSink相當(dāng)于OutputStream,它擴(kuò)展了
OutputStream的功能,Okio的完整源碼我后續(xù)也會(huì)寫博客
③方式三:MultipartBody分塊表單創(chuàng)建
MultipartBody, 既可以添加表單,又可以也可以添加文件等二進(jìn)制數(shù)據(jù),我們就看幾個(gè)重要的方法
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); }
我們來(lái)看這個(gè)方法,我們是addPart還是addFormDataPart最終都走到了這個(gè)方法,封裝成一個(gè)Part對(duì)象,也就是實(shí)體內(nèi)容中
的Content-Disposition跟文件二進(jìn)制流或者鍵值對(duì)的值
MultipartBody和FormBody大體上相同,主要區(qū)別在于writeOrCountBytes方法,分塊表單主要是將每個(gè)塊的大小進(jìn)行累加來(lái)求出請(qǐng)求體大小,如果其中有一個(gè)塊沒有指定大小,就會(huì)返回-1。所以分塊表單中如果包含文件,默認(rèn)是無(wú)法計(jì)算出大小的,除非你自己給文件的RequestBody指定contentLength。
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException { long byteCount = 0L; Buffer byteCountBuffer = null; if (countBytes) { //如果是計(jì)算大小的話,就new個(gè) sink = byteCountBuffer = new Buffer(); } //循環(huán)塊 for (int p = 0, partCount = parts.size(); p < partCount; p++) { Part part = parts.get(p); //獲取每個(gè)塊的頭 Headers headers = part.headers; //獲取每個(gè)塊的請(qǐng)求體 RequestBody body = part.body; //寫 --xxxxxxxxxx 邊界 sink.write(DASHDASH); sink.write(boundary); sink.write(CRLF); //寫塊的頭 if (headers != null) { for (int h = 0, 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 != -1) { 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. //如果有個(gè)塊沒有這名大小,就返回-1. byteCountBuffer.clear(); return -1L; } sink.write(CRLF); //如果是計(jì)算大小就累加,否則寫入BufferedSink if (countBytes) { byteCount += contentLength; } else { body.writeTo(sink); } sink.write(CRLF); } //寫 --xxxxxxxxxx-- 結(jié)束邊界 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指定請(qǐng)求和響應(yīng)遵循的緩存機(jī)制。在請(qǐng)求消息或響應(yīng)消息中設(shè)置Cache-Control并不會(huì)修改另一個(gè)消息處理過程中的緩存處理過程。請(qǐng)求時(shí)的緩存指令有下幾種:
- Public:所有內(nèi)容都將被緩存(客戶端和代理服務(wù)器都可緩存)。
- Private:內(nèi)容只緩存到私有緩存中(僅客戶端可以緩存,代理服務(wù)器不可緩存)
- no-cache:請(qǐng)求或者響應(yīng)消息不能緩存
- no-store:不使用緩存,也不存儲(chǔ)緩存
- max-age:緩存的內(nèi)容將在指定時(shí)間(秒)后失效, 這個(gè)選項(xiàng)只在HTTP 1.1可用, 并如果和Last-Modified一起使用時(shí), 優(yōu)先級(jí)較高
- 在 xxx 秒后,瀏覽器重新發(fā)送請(qǐng)求到服務(wù)器,指定時(shí)間(秒)內(nèi),客戶端會(huì)直接返回cache而不會(huì)發(fā)起網(wǎng)絡(luò)請(qǐng)求,若過期會(huì)自動(dòng)發(fā)起網(wǎng)絡(luò)請(qǐng)求
- min-fresh:指示客戶端可以接收響應(yīng)時(shí)間小于當(dāng)前時(shí)間加上指定時(shí)間的響應(yīng)。
- max-stale:指示客戶端可以接收超出超時(shí)期間的響應(yīng)消息。如果指定max-stale消息的值,那么客戶機(jī)可以接收超出超時(shí)期指定值之內(nèi)的響應(yīng)消息。
(2)CacheControl類
①常用的函數(shù)
final CacheControl.Builder builder = new CacheControl.Builder(); builder.noCache();//不使用緩存,全部走網(wǎng)絡(luò) builder.noStore();//不使用緩存,也不存儲(chǔ)緩存 builder.onlyIfCached();//只使用緩存 builder.noTransform();//禁止轉(zhuǎn)碼 builder.maxAge(10, TimeUnit.MILLISECONDS);//指示客戶機(jī)可以接收生存期不大于指定時(shí)間的響應(yīng)。 builder.maxStale(10, TimeUnit.SECONDS);//指示客戶機(jī)可以接收超出超時(shí)期間的響應(yīng)消息 builder.minFresh(10, TimeUnit.SECONDS);//指示客戶機(jī)可以接收響應(yīng)時(shí)間小于當(dāng)前時(shí)間加上指定時(shí)間的響應(yīng)。 CacheControl cache = builder.build();//cacheControl
②CacheControl的兩個(gè)常量:
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();//只使用緩存
③請(qǐng)求時(shí)如何使用:
final CacheControl.Builder builder = new CacheControl.Builder(); builder.maxAge(10, 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("服務(wù)器錯(cuò)誤", callBack); } } }); return call; } catch (Exception e) { Log.e(TAG, e.toString()); }
以上如果Cache沒有過期會(huì)直接返回cache而不會(huì)去發(fā)起網(wǎng)絡(luò)請(qǐng)求,若過期自動(dòng)發(fā)起網(wǎng)絡(luò)請(qǐng)求,注意:如果您使用FORCE_CACHE和網(wǎng)絡(luò)的響應(yīng)需求,OkHttp則會(huì)返回一個(gè)504提示,告訴你不可滿足請(qǐng)求響應(yīng),所以我們加一個(gè)判斷在沒有網(wǎng)絡(luò)的情況下使用
//判斷網(wǎng)絡(luò)是否連接 boolean connected = NetworkUtil.isConnected(context); if (!connected) { request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build(); }
posted on 2016-10-30 23:40 安卓筆記俠 閱讀(1240) 評(píng)論(0) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)