前言
现在Android界的网络请求已经是OkHttp和Retrofit的天下了,Retrofit本质上也是将请求委托给了OkHttp,所以我们如果想要能够更加全面的使用和掌握OkHttp,了解其源码是必不可少的。 如果对Http还有不了解的小伙伴,可以一下这篇文章,带你全面掌握Http协议 面试官的这份HTTP灵魂追问你Hold住吗?
作者:Mlx
链接:https://juejin.im/post/6881436122950402056
OK,出发~
简单使用
同步方式和异步方式殊途同归,而且异步方式更多了异步和线程的概念,所以本文将直接分析异步,同步的话流程也是一样的。
val client=OkHttpClient().newBuilder().build()
val request=Request.Builder().url("https://www.baidu.com").build()
val call = client.newCall(request)
call.enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
println("onFailure:$e")
}
override fun onResponse(call: Call, response: Response) {
println("onResponse:${response.body?.string()}")
}
})
这可以说是最简单的使用方式了。
使用 OkHttp 基本是以下四步:
- 创建
OkHttpClient
对象 - 创建
Request
请求对象 - 创建
Call
对象 - 同步请求调用
call.execute();
异步请求调用call.enqueue(callback)
这都是啥?一个简单的请求为啥需要这么多对象呢?
简单架构
那好,我们来分析一下他们的职责,其实啊一次网络请求和寄快递没啥区别。
首先,你在淘宝买了一个东西,发现不满意,要换货,你得去快递点(OkHttpClient
),然后填写快递单(Request
)表明要寄给谁,寄的啥东西,填完以后你交个快递小哥,小哥把你这个快递给你寄出去(Call
)。如果是同步请求(call.execute
)的话,你就在这等着快递寄出去,然后卖家再发给你换好的货(Response
)。显然,这么等的话怕是等个三四天,所以我们把快递给了快递小哥以后,告诉小哥我的电话(CallBack
回调),说货到了通知我(触发回调 onResponse/onFailure
)。
这样一来是不是很简单了
什么?还不够清晰明了?靠,只能把大宝贝儿掏出来了,看图
好,到此结束,OKHTTP分析完毕!
什么?这就完事了?我不是听说还有啥拦截器,什么责任链,什么缓存balabla的,你这叫我和面试官如何对线?
好吧,那我们接着下面看OkHttp做了什么。
构建OkHttpClient对象
我们一步一步来,先分析我们如何鬼使神差的走进快递点的,啊不,构建OkHttpClient对象的
val client=OkHttpClient().newBuilder().build()
这行代码是构建OkHttpClient对象的,很明显有个Builder对象,这明眼人一看就知道是构建者模式去构建OkHTTPClient对象。
什么?构建者模式是什么
构建者模式一般适用于创建比较复杂的对象使用的,可以屏蔽用户对细节的感知balabala
我最讨厌说定义了,我这么说吧,你如果创建一个对象构造函数中参数特别多的时候,大部分情况下你用构建者就行了。
为什么?就比如说你想整个车,你可以直接说,来奔驰,给我整俩车。你对如何造车完全不知道,但这并不影响你提车泡妹。
可是你不是说参数比较多的时候用构建者么?
没错,参数比较多的时候你是不是得一个个传参,还得小心参数的位置啥的?当然了,在Kotlin中可以指定哪个参数,它本质上和构建者也差不多。你也不想提车的时候还得小心翼翼的和厂家一一说明要求吧,我得有个四个轮的车,白色的,能坐四个人的等等要求。
所以构建者模式就是为了解决此问题,你可以没有要求,我就使用默认值,你可以有很多要求,甚至可以重复,反正我只在你提车的时候最终确认即可给你符合你要求的车。
所以如果使用构建者模式的话,我们不必为参数的位置和忘了传什么参数而烦忧,我们可以写出如下代码:
val client=OkHttpClient()
.newBuilder()
.dispatcher()
.callTimeout()
.authenticator()
.addInterceptor()
.cache()
.build()
你看你可以使用构建者模式去创建出符合你所有要求的对象,不用担心参数位置,或者忘记传哪个参数。
当然,本篇不是讲设计模式的,随后我将会开辟一个新的系列,专门讲设计模式。这里点到为止。
我们继续回到我们的OkHttp来,使用Builder对象创建了OkHttpclient对象过程中,其实给OkHttpclient创建了一系列的配置,我们这里简要的看一下,在newBuilder方法中创建了一个Builder对象:
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()//调度器,一会讲
internal val interceptors: MutableList<Interceptor> = mutableListOf()//拦截器,一会也会讲
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()//网络拦截器,也会讲
...//定义很多其他对象
}
在这里省略了很多对象,只列出了三个比较重要的对象,一会都会讲的。
build方法真正构建了OkHttpclient对象:
fun build(): OkHttpClient = OkHttpClient(this)
这个this是什么?
这个build方法是Builder对象的build方法,this当然是Builder对象啦。这里真正创建了一个OkHTTPClient对象,并把Builder对象自身传了过去。在OkHttpclient对象的构造方法中有如下代码:
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher //调度器
@get:JvmName("interceptors") val interceptors: List<Interceptor> =
builder.interceptors.toImmutableList()//拦截器集合
@get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> =
builder.networkInterceptors.toImmutableList()//网络拦截器集合
}
可以看到,最终builder对象中的拦截器全部给了OkHttpClient对象,这也正是构建者模式的体现之处。
简而言之,就是OkHttpClient里面定义了很多我们不知道的对象。正如你寄快递的时候,你并不知道快递是如何寄出去的,OkHttp帮你完成了很多的细节工作。
接下来就是构建Request对象了
构建Request对象
你想寄快递,必须填写快递单,写你要寄给谁,寄到哪里,怎么寄平邮还是快件,这一切就是Request对象的职责。它负责记录你的请求信息。
val request=Request.Builder().url("https://www.baidu.com").build()
经过上面的分析,我们已经知道,构建者Builder中所有的参数最终全部会给到Request对象中。所以我们直接看看Request对象的构造方法中有什么东西~
class Request internal constructor(
@get:JvmName("url") val url: HttpUrl,//ur,也就是你要发送的地方
@get:JvmName("method") val method: String, //请求方法,GET/POST
@get:JvmName("headers") val headers: Headers,//请求头,一些备注信息或者请求信息
@get:JvmName("body") val body: RequestBody?,//请求的内容,就是你具体要发送的快递不能
internal val tags: Map<Class<*>, Any>
) {
....
}
注释已经讲的很清楚了,关于HTTP网络这块,大家不太懂的可以看我之前的文章,从HTTP表层到原理全部都有讲解。
接下来也没啥内容了,该轮到Call对象了
创建Call对象
val call = client.newCall(request)
正如你想寄快递,你必须填写好快递单交给快递站的小哥才行。
这里你已经填好了快递单,你需要交给快递站,然后快递站派出快递小哥帮你发快递。
快递小哥就是Call,我们看看是Call是什么东东:
interface Call : Cloneable {
fun request(): Request//返回请求信息,就是返回你填写的快递单
@Throws(IOException::class)
fun execute(): Response//同步请求,就是你在快递站傻等~
fun enqueue(responseCallback: Callback)//异步请求,接受回调参数。就是你告诉快递小哥你电话,让小哥寄快递
fun cancel()//取消请求,就是你取消寄快递
fun isExecuted(): Boolean//是否正在请求,就是你的快递寄出去了没,寄到哪了
fun isCanceled(): Boolean//是否是取消状态
fun timeout(): Timeout //超时时间,就是你这个快递寄出去半年了都没动静,你就认为中途丢了
public override fun clone(): Call
fun interface Factory {
fun newCall(request: Request): Call
}
}
Call本质上是个接口,也符合我们的面向接口编程。它里面定义了很多方法,在上面注释中我也都详细阐述了。可是接口并不能具体帮我们干活啊,就像你知道你要找一个快递小哥,快递小哥是一个抽象的概念并不是一个具体存在的人,你只知道快递小哥能帮你寄快递,这个寄快递就是一个抽象的方法。所以我们需要知道到底是哪个快递小哥帮我们寄了快递。
我们看一下这个newCall
方法:
//OkHttpClient.kt
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
啊知道了,RealCall
才是真正为我们服务的那个快递小哥。创建RealCall的过程中,我们传进去一共三个参数,第一个this自然就是OkHttpclient对象本身了,第二个参数是请求信息也就是快递单,第三个参数是用于指明不是webSocket请求。暂时不用管第三个参数
好,我们现在万事俱备只欠东风,所有的对象都已经创建完成了,可以寄快递了吧?
发送请求
发送请求就是将你的电话号告诉给快递小哥,快递小哥寄出去以后,收到快递再给你打电话告诉你快递到了或者丢了。对应的就是以下代码。
call.enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
println("onFailure:$e")
}
override fun onResponse(call: Call, response: Response) {
println("onResponse:${response.body?.string()}")
}
})
前面也说了call.enqueue是一个异步方法,我们既然是从源码开始学习的,自然不能只停留在表面,当然要深入源码去探索,看看这个发送的过程到底是怎么样的过程。在上面我们知道,这个快递小哥是一个宽泛的概念(接口),真正执行任务的时候要具体到某个人的(实现类),这个具体的人就是RealCall对象,所以我们看