1.OkHttp4.3源码解析之 - 发起请求
什么是OkHttp?
OkHttp在Android开发领域里面应该是无人不知了吧。它是一个由Square公司开源的第三方库。主要用于处理网络请求。
OkHttp Github地址:https://github.com/square/okhttp/
关于Square公司,大家可以去看看他们的其他开源库,质量都挺高的,不仅包括Android相关的,还有Go,Ruby,JS,Kotlin等等
Square官方网站:https://square.github.io/
到目前为止,OkHttp的最新版本是4.3.1,里面一部分代码也已经用Kotlin来重写了,而大部分分析OkHttp源码的文章还停留在以前旧的版本,因此在写这系列源码分析的文章同时,也是学习Kotlin的一个好机会。
从一个简单的请求说起
如果有做过Android开发的话,相信以下代码大家都很熟悉了:
1 | val client = OkHttpClient() |
这段代码做的事情很简单:
创建一个OkHttpClient对象
使用建造者模式创建一个Request对象
使用OkHttpClient.newCall(request).enqueue() 发起请求,并处理回调
但是这短短的几行代码里面,就已经有很多我们可以学习的东西了。
首先是建造者模式,也叫Builder模式,它的作用是让用户自由组合需要的参数,来实现不同的需求。具体实现方法可以通过接口,也可以像OkHttp一样,使用内部类。这里我们不详细展开阐述,有兴趣的读者可以去看看《大话设计模式》或者《Android源码设计模式分析与实战》。
然后,通过val
这种写法来定义变量也不太优雅,因为这些变量其实声明一次就够了,我们可以改进一下代码:
1 | // 定义Request |
把声明的三个变量都干掉,这样代码看起来是不是简洁多了?
上面就是一个最简单的OkHttp发起GET请求的方式,下面我们一起来看看OkHttp是怎么做到的。
1.创建OkHttpClient
这个类的作用就是发起HTTP请求和接收响应
首先OkHttpClient有两个构造方法,一个是无参构造方法,另外一个是传入参数为Builder的构造方法
OkHttpClient的构造方法
无参构造方法
1
2 > constructor() : this(Builder())
>
有参构造方法
1
2
3
4 > open class OkHttpClient internal constructor(
> builder: Builder
> )
>
OkHttpClient.Builder类
可以看到,无参构造方法其实也是调用了Builder为参数的构造方法,这里传入的Builder() 就是默认的实现,继续看看Builder里面是怎么实现的:
1 | class Builder constructor() { |
Builder是OkHttpClient的内部类,然后Builder声明了很多的参数,这些参数的作用我会在后面的文章中详细分析。
到这里为止,我们就完成了第一步,创建一个OkHttpClient对象,在构造方法里面创建了Builder()对象。然后看第二步:Request.Builder() 创建Request对象
2.创建Request对象
Request的构造方法
Request的构造方法需要4个参数
1 | class Request internal constructor( |
Request.Builder类
Request同样使用了Builder模式,我们直接看Request.Builder类:
1 | open class Builder { |
可以看到,Builder默认的请求方法是GET,并且初始化了一个大小为20的ArrayList作为Headers的容器
除了method和haders之外,还有两个关键的参数,一个是url,另外一个就是RequestBody。这几个参数都提供了set方法,支持链式调用,例如url:
1 | open fun url(url: String): Builder { |
这里做了一点处理,如果是基于web socket协议的url,会被替换成http,而wws则是加密的web socket协议。
设置完了url / requestBody / mothod / headers 之后,我们都会调用build()方法:
1 | open fun build(): Request { |
其实就是把刚刚设置的作为参数,调用Request的有参构造方法,创建一个Request对象。到这里为止,第二步也就完成了。
3.发起请求
做完前面两步之后,终于到了激动人心的时刻了,我们通过OkHttpClient.newCall(request).enqueue()来发起请求
到源码里面看看newCall和enqueue这两个方法分别做了什么
newCall()方法
1 | override fun newCall(request: Request): Call { |
调用RealCall.newRealCall方法,并且传入了OkHttpClient和Request作为参数
再看看newRealCall:
1 | companion object { |
其实就是创建了一个RealCall对象,注意这里同时创建了一个Transmitter对象。它后面会再出现的,暂时先忽略,我们先回到发起请求这个过程中来。
enqueue()方法
上一步创建了RealCall对象是吧,传入了OkHttpClient和Request对象是吧,那么enqueue()方法又做了什么呢?
这个enqueue()方法其实是Call接口的其中一个方法,除了enqueue之外,还有request(), execute()等其他方法。我们先看这个enqueue()方法:
1 | override fun enqueue(responseCallback: Callback) { |
可以看到,首先这个方法有一个Callback作为参数,而这个Callback接口里面又有两个方法:
1 | interface Callback { |
分别用于处理失败和成功两种情况的回调。
在enqueue的方法体内,首先通过一个synchronized关键字,确保这个方法只会执行一次,
然后通过dispatcher.enqueue来执行异步请求
1 | internal fun enqueue(call: AsyncCall) { |
继续看看promoteAndExecute()
1 | private fun promoteAndExecute(): Boolean { |
首先判断是不是超过了最大请求数或者是相同Host的最大请求数,如果是的话就直接return
否则就执行asyncCall.excuteOn方法
1 | fun executeOn(executorService: ExecutorService) { |
AsyncCall是RealCall里面的一个内部类,因此在这里已经持有了OkHttpClient对象,也就持有了Dispatcher,而这个executorService又是什么呢?
其实这是一个线程池执行器,在Dispatcher中定义为一个变量
1 |
|
而 executorService.execute(this) 中的 this
指的就是AsyncCall对象,一个实现了Runnable接口的对象
因此,上面的excuteOn方法,其实就是执行AsyncCall的run()方法啊。
1 | override fun run() { |
这里又用到了Kotlin的内联函数,相当于在方法体外面多加了一层try…finally,关于内联函数,大家可以看看官方文档的说明:Kotlin中文网 - 内联函数
1 | inline fun threadName(name: String, block: () -> Unit) { |
首先我们来看注释1
1 |
|
这里用到了另外一个设计模式:责任链模式,用来处理整个网络请求中不同的部分,例如失败重试,缓存,连接等等。这些不同的部分都通过拦截器的方式来实现。
在chain.proceed(originalRequest)
方法中,其实就是调用了RealInterceptorChain.proceed()方法
这个方法的源码:
1 |
|
可以看到,通过index + 1的方法,我们取出下一个拦截器,然后执行里面的intercept方法,这就是proceed方法主要进行的工作。最终把Response返回。得到的这个Response又通过Callback.onResponse回调方法,使得我们可以获取到这个Response对象。而如果中途抛出异常,那么则会回调onFailure方法。
至于我们在上面加入的那些拦截器,详细的说明会放到下一篇文章中讲解,我们也会加入自定义拦截器的例子。毕竟这种情况在实际开发中还是经常会遇到的,例如自定义ConverterFactory来解析后台返回的数据。
总结
最后,让我们再来回顾一下OkHttp是如何发起请求的:
- 构造一个OkHttpClient对象,这里有许多的变量用于控制请求时候的参数
- 构造一个Request对象,这里主要是4个要素:url, method, header, body
- 调用OkHttpClient.newCall(request).enqueue()方法发起请求,在RealCall类中,实现了Call接口,并且持有OkHttpClient和Request对象,还有一个AsyncCall的内部类,这个内部类就是用来发起异步请求的,这个类同时也实现了Runnable接口,它的getResponseWithInterceptorChain()方法通过责任链的模式,把请求相关的拦截器一个个加入到List中,然后再通过RealInterceptorChain的proceed()方法来执行这些不同的拦截器所定义的方法。
- 最后成功则回调Callback.onResponse, 失败回调Callback.onFailure
还有,到目前为止我们已经发现了OkHttp使用了两个设计模式,分别是:
- Builder模式
- 责任链模式
有兴趣的童鞋可以自行上网查找相关资料~
好了,一个简单的请求大致上就是这么个流程,下一篇文章我们继续深入了解OkHttp里面的拦截器~