OkHttp之RequestBody请求体
继OkHttp之Request请求一文,看完Request类,内部存储了和请求相关的数据,并没有什么复杂的逻辑。接着看看RequestBody类内部实现是怎样的。
1、RequestBody
RequestBody是抽象类。
1.1、两个抽象方法
有两个抽象方法contentType()和writeTo(sink: BufferedSink)。需要具体的RequestBody实例提供实现:表明数据的媒体类型;提供写入数据内容。
/** Returns the Content-Type header for this body. */
abstract fun contentType(): MediaType?
/** Writes the content of this request to [sink]. */
abstract fun writeTo(sink: BufferedSink)
1.2、基本方法
RequestBody内还定义了三个基本方法,描述网络请求的一些属性信息。方法是open的,实现类可重写这些方法。
/**
* 返回将写入对[writeTo]的调用中的接收器的字节数;如果该计数未知,则返回-1。
*/
open fun contentLength(): Long = -1L
/**
* 是否双工:指二台通讯设备之间,允许有双向的资料传输。
* [grpc]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
*/
open fun isDuplex(): Boolean = false
/**
* 如果此主体希望最多调用一次[writeTo],并且最多可以传输一次,则返回true。
*/
open fun isOneShot(): Boolean = false
1.3、扩展函数
在RequestBody类的伴生对象中定义了四个扩展函数:分别从String、ByteString、ByteArray、File扩展了构造RequestBody实例的方法。
fun String.toRequestBody(contentType: MediaType? = null): RequestBody
fun ByteString.toRequestBody(contentType: MediaType? = null): RequestBody
fun ByteArray.toRequestBody(contentType: MediaType?,offset,byteCount): RequestBody
fun File.asRequestBody(contentType: MediaType? = null): RequestBody
RequestBody类源码并不多,继续看OkHttp提供的两个实现:FormBody和MultipartBody。
2、FormBody
FormBody实现了RequestBody类的两个抽象方法,携带表单请求数据。
2.1、成员及方法
内部使用两个一样大小的数组保存name及对应的value。
private val encodedNames: List<String> = encodedNames.toImmutableList()
private val encodedValues: List<String> = encodedValues.toImmutableList()
....
//提供方法获取表单数据K-V
fun encodedName(index: Int) = encodedNames[index]
fun name(index: Int) = encodedName(index).percentDecode(plusIsSpace = true)
fun encodedValue(index: Int) = encodedValues[index]
fun value(index: Int) = encodedValue(index).percentDecode(plusIsSpace = true)
2.2、内部类Builder
无法直接使用FormBody的构造方法创建表单请求体实例,需要借助内部的Builder类构造,调用add()方法添加表单数据
fun add(name: String, value: String)
fun addEncoded(name: String, value: String)
最后调用Builder的build()方法直接创建FormBody表单实例。
fun build(): FormBody = FormBody(names, values)
3、MultipartBody
MultipartBody继承自RequestBody抽象类,用来携带分块请求数据。MultipartBody的实现稍微复杂一些。
3.1、内部类:Part分块数据
通过它的内部构造方法不难看出,是通过Part类型集合存储分块的数据。
class MultipartBody internal constructor(
val type: MediaType,
val parts: List<Part>
)
Part是MultipartBody中的内部类,携带每个分块的数据:Headers和RequestBody。
class Part private constructor(
val headers: Headers?,
val body: RequestBody
)
3.2、内部类Builder
同样的,我们无法直接通过MultipartBody构造函数创建实例,而应该使用MultipartBody类内部的Builder,Builder内部三个成员和MultipartBody一一对应:
class Builder {
private val boundary: ByteString = boundary.encodeUtf8()
private var type = MIXED
private val parts = mutableListOf<Part>()
...
}
可以看到MultipartBody默认MIME类型是"multipart/mixed"。当然可以调用setType(type: MediaType)方法设置当前构造的分块请求体数据类型。
fun setType(type: MediaType) = apply {
require(type.type == "multipart") { "multipart != $type" }
this.type = type
}
通过Builder的addPart(...)方法添加各种分块RequestBody,以及对应的Headers(如果有的话):
/** Add a part to the body. */
fun addPart(body: RequestBody)
/** Add a part to the body. */
fun addPart(headers: Headers?, body: RequestBody)
/** Add a form data part to the body. */
fun addFormDataPart(name: String, value: String)
/** Add a form data part to the body. */
fun addFormDataPart(name: String, filename: String?, body: RequestBody)
/** Add a part to the body. */
fun addPart(part: Part)
最后,调用build()创建并返回MultipartBody实例。
fun build(): MultipartBody {
return MultipartBody(boundary, type, parts.toImmutableList())
}