一,概述
在上篇blog中以get請求爲例分析了OKHttp框架的表層源碼,具體參見:OKHttp源碼分析(一)
在post請求中用到的API很大部分與get請求中用到的API相同,最大的不同就是Request.Builder類的post方法,這個方法的作用是設置post請求的請求體,接收的參數是RequestBody類及子類對象。
Request.Builder類的post方法的源碼是:
public Builder post(RequestBody body) {
return method("POST", body);
}
method方法的源碼是:
public Builder method(String method, RequestBody body) {
this.method = method;
this.body = body;
return this;
}
這個方法的作用就是將body對象給Request對象的body字段賦值。
在OKHttp源碼分析(一)中我們知道:OKHttpClient對象和Request對象都傳遞給了RealCall類,即可以在RealCall對象中拿到body對象,然後就可以調用body的writeTo方法得到流對象,向服務器寫數據。body的writeTo方法中的代碼原理類似於httpURLconnection的post方式上傳參數,我們可以對比學習。
具體在哪兒調用的body的writeTo方法不是本篇的重點,本篇的重點是分析RequestBody類及其子類。
具體爲以下幾點:
1,RequestBody類中核心方法
2,RequestBody類中Create方法
3,FromBody類
4,MultipartBody類
二,RequestBody類及其核心方法
RequestBody類是請求體類,這是上傳數據的核心類,它的writeTo方法可以得到流對象,然後將請求體數據寫到服務器。這是上傳數據的核心方法。
RequestBody類中核心方法有以下三個:
1,public abstract MediaType contentType()//數據類型
2,public long contentLength()//數據長度
3,public abstract void writeTo(BufferedSink sink)//寫操作
對於熟悉http協議的小夥伴都知道,在http上傳數據時都有數據類型和數據長度,所以前兩個方法就不做介紹。下面重點看第三個方法。
RequestBody類中的writeTo方法是抽象方法,具體實現在子類中,所以具體寫數據的邏輯也在子類中。這兒需要說明的是BufferedSink 類。
BufferedSink 類是square公司開源的IO框架Okio中的一個類,這個類封裝了OutputStream,即本質是一個輸出流,具有write方法。Okio框架和BufferedSink 類也不是本篇介紹的重點,這兒不做講解。把BufferedSink 當成OutputStream使用即可。
三,RequestBody類的create方法
我們知道RequestBody是一個抽象類,它不能進行實例化。因此RequestBody類提供了create方法來創建RequestBody的實例對象。
RequestBody類的create方法有多個重載,但重要的方法只有兩個:
//1,創建上傳byte數據的RequestBody對象。
create(final MediaType contentType, final byte[] content,final int offset, final int byteCount)
//2,創建上傳File數據的RequestBody對象。
create(final MediaType contentType, final File file)
1,創建上傳byte數據的RequestBody對象
這個方法的表面意思是上傳byte數據,但根據程序員的第六感覺可以清晰的發現它可以上傳String數據和file數據。因爲String數據和file數據都可以輕鬆轉換成byte數組。由於File文件較大,轉換成Byte數組太佔內存,所以提供了File數據的專用方法。對於上傳String數據常常使用該方法。在OKHttp的使用詳解中上傳Json數據底層調用的就是該方法。
方法的原碼是:
public static RequestBody create(final MediaType contentType, final byte[] content,
final int offset, final int byteCount) {
return new RequestBody() {
@Override public MediaType contentType() {
return contentType;
}
@Override public long contentLength() {
return byteCount;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.write(content, offset, byteCount);
}
};
}
其實方法的實現很簡單,就是創建RequestBody 的子類對象,並重寫三個方法。下面注重看writeTo方法。writeTo方法的實現只有一行代碼:
sink.write(content, offset, byteCount);
這行代碼的意思是:寫byte數組,從offset開始,寫byteCount長度。
sink.write方法就類似於OutputStream的write方法,此時我們應該明白這行代碼的含義了。
此時我們發現:OKHttp雖然是偏底層的網絡請求框架,但底層實現並不麻煩,這與httpURLconnection的用法很類似。
2,創建上傳File數據的RequestBody對象
方法的原碼如下:
public static RequestBody create(final MediaType contentType, final File file) {
if (file == null) throw new NullPointerException("content == null");
return new RequestBody() {
@Override public MediaType contentType() {
return contentType;
}
@Override public long contentLength() {
return file.length();
}
@Override public void writeTo(BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(file);
sink.writeAll(source);
} finally {
Util.closeQuietly(source);
}
}
};
}
這個方法也是創建RequestBody類的實例對象,下面也重點看writeTo方法。writeTo方法的核心代碼是:
source = Okio.source(file);//根據文件得到輸入流對象。
sink.writeAll(source);//將輸入流對象寫出去。
writeAll是RealBufferSink類的方法,這個也是屬於Okio框架中的。方法的原碼是:
@Override public long writeAll(Source source) throws IOException {
long totalBytesRead = 0;
for (long readCount; (readCount = source.read(buffer, Segment.SIZE)) != -1; ) {
totalBytesRead += readCount;
emitCompleteSegments();
}
return totalBytesRead;
}
emitCompleteSegments方法的源碼是:
@Override public BufferedSink emitCompleteSegments() throws IOException {
if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount();
if (byteCount > 0) sink.write(buffer, byteCount);
return this;
}
有沒有一種很熟悉的感覺。對,這就是常用的IO流操作。
四,FormBody類
雖然RequestBody提供了create方法可以上傳String類型數據。但對於上傳鍵值對數據來說需要拼接數據,比較麻煩,所以框架中提供了專業上傳鍵值對數據的FormBody類。
FormBody類的基本用法如下:
FormBody.Builder formBody = new FormBody.Builder();//創建表單請求體
formBody.add("username","zhangsan");//傳遞鍵值對參數
formBody.add("password","000000");//傳遞鍵值對參數
RequestBody body= formBody.build();
這兒明顯是Builder設計模式。
1,分析FormBody的內部類Builder類
Builder是FormBody的內部類,原碼是:
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 FormBody build() {
return new FormBody(names, values);
}
}
這個類的邏輯非常簡單,重點如下:
1,首先創建兩個list集合,分別用來盛放key和value;
2,add方法的作用就是將鍵值對分別放入兩個集合中。
3,build方法的作用是將key集合與value集合傳遞給FormBody對象。
2,FormBody類的writeTo方法
下面看重點,FormBody類的writeTo方法的原碼:
@Override public void writeTo(BufferedSink sink) throws IOException {
writeOrCountBytes(sink, false);
}
writeOrCountBytes方法的核心原碼是:
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) {
long byteCount = 0L;
Buffer buffer;
if (countBytes) {
buffer = new Buffer();
} else {
buffer = sink.buffer();
}
for (int i = 0, size = encodedNames.size(); i < size; i++) {
if (i > 0) buffer.writeByte('&');
buffer.writeUtf8(encodedNames.get(i));
buffer.writeByte('=');
buffer.writeUtf8(encodedValues.get(i));
}
if (countBytes) {
byteCount = buffer.size();
buffer.clear();
}
return byteCount;
}
看到這兒是不是仍是一種熟悉的感覺,在使用HttpURLconnection的post請求傳遞鍵值對參數時就是這麼拼接的。
五,MultipartBody類
根據類名就可得知這個類是多重的body,即可以上傳鍵值對數據,又可以同時上傳File數據。比如在微信中發朋友圈時,既需要上傳文字又需要上傳圖片,此時就需要使用這種多重的body。
MultipartBody類的基本使用如下:
MultipartBody multipartBody =new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("groupId",""+groupId)//添加鍵值對參數
.addFormDataPart("title","title")
.addFormDataPart("file",file.getName(),RequestBody.create(MediaType.parse("file/*"), file))//添加文件
.build();
這兒明顯也是builder設計模式。
1,分析MultipartBody 內部類builder
MultipartBody 實現同時上傳鍵值對數據和File數據的原理與httpURLconnection相似,都是仿照Web中提交Form表單數據時的數據格式。
下面看addFormDataPart方法,這個方法有兩個重要重載:
//1,添加鍵值對數據
addFormDataPart(String name, String value)
//2,添加File數據
addFormDataPart(String name, String filename, RequestBody body)
首先看添加鍵值對數據addFormDataPart方法的源碼:
public Builder addFormDataPart(String name, String value) {
return addPart(Part.createFormData(name, value));
}
再看addPart方法:
public Builder addPart(Part part) {
if (part == null) throw new NullPointerException("part == null");
parts.add(part);
return this;
}
這個方法的本質將Part對象賦值給Builder的parts字段。下面看part對象的創建。
在addFormDataPart方法中可知,part對象創建的代碼是:
Part.createFormData(name, value)。
方法的源碼是:
public static Part createFormData(String name, String value) {
return createFormData(name, null, RequestBody.create(null, value));
}
createFormData方法的源碼是:
public static Part createFormData(String name, String filename, RequestBody body) {
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);
}
此時開始初步仿照web中提交form表單數據的格式進行封裝。將name和fileName封裝到Header對象中。
Part類的create方法的源碼是:
public static Part create(Headers headers, RequestBody body) {
return new Part(headers, body);
}
將header對象和body對象傳遞給part對象,然後將part對象放入Builder的字段parts集合中。
首先看添加File數據addFormDataPart方法的源碼:
public Builder addFormDataPart(String name, String filename, RequestBody body) {
return addPart(Part.createFormData(name, filename, body));
}
首先將File對象得到body對象。下面的方法addPart和Part類的createFormData方法都已經講解過。
添加File數據的流程如下:
1,首先將File對象得到body對象。
2,將name和fileName封裝到Header對象中。
3,將header對象和body對象傳遞給part對象,然後將part對象放入Builder的字段parts集合中。
最後看Builder類的build方法:
public MultipartBody build() {
return new MultipartBody(boundary, type, parts);
}
創建MultipartBody對象,將三個參數傳遞過去。
boundary和type作用是封裝數據格式,parts中封裝了header和body數據。
2,分析MultipartBody類的WriteTo方法
方法的源碼是:
@Override public void writeTo(BufferedSink sink) throws IOException {
writeOrCountBytes(sink, false);
}
writeOrCountBytes方法的源碼如下:
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
long byteCount = 0L;
Buffer byteCountBuffer = null;
for (int p = 0, partCount = parts.size(); p < partCount; p++) {//遍歷part集合
Part part = parts.get(p);
Headers headers = part.headers;
RequestBody body = part.body;
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);//寫Header數據
}
}
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) {
byteCountBuffer.clear();
return -1L;
}
sink.write(CRLF);
if (countBytes) {
byteCount += contentLength;
} else {
body.writeTo(sink);//寫body數據
}
sink.write(CRLF);
}
sink.write(DASHDASH);//寫數據格式字符
sink.write(boundary);
sink.write(DASHDASH);
sink.write(CRLF);
if (countBytes) {
byteCount += byteCountBuffer.size();
byteCountBuffer.clear();
}
return byteCount;
}
MultipartBody類的WriteTo方法稍微有些複雜,但這部分代碼是上傳數據的關鍵,值得我們研究學習。