网络请求(二)— OkHttp
1 OkHttp
简介
目前主流的Android
网络请求框架有OkHttp
和Retrofit
,不过,Retrofit
底层使用的是OkHttp
,其自身是不具备网络请求能力的。
OkHttp
是由Square公司开发并共享开源的高效网络访问框架,使用简单,它替代了HttpUrlConnection
和Apache
的HttpClient
。谷歌官方在Android 6.0 (API 23)
里已移除HttpClient
,加入了OkHttp
。
默认情况下OkHttp
具备以下特性:
- 支持
HTTP/2.0
,HTTP/2.0
是持久化连接,支持多路复用(客户端和服务端只有一个连接,通过这一个连接可以发起多重请求); - 连接池减少请求延时(如果
HTTP/2.0
不可用); - 透明的
GZIP
压缩下载大小; - 缓存响应内容,避免一些完全重复的网络请求;
- 网络出现问题后,
OkHttp
能自动中恢复。如果服务器有多个IP
地址,一个失败后,OkHttp
会自动尝试连接其他的地址;
OkHttp/2.0
是基于SPDY
(SPeeDY
)设计的。SPDY
是谷歌开发的基于TCP
的会话层协议,用于最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY
并不是一种用于替代HTTP
的协议,而是对HTTP
协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP
报头压缩。谷歌表示,引入SPDY
协议后,在实验室测试中页面加载速度比原先快64%
。
speedy [ˈspiːdi] 迅速发生的;高速的,快速移动的
下面是Http
常见的一些状态码:
100~199
:指示信息,表示请求已接收,继续处理;200~299
:请求成功,表示请求已被成功接收、理解;300~399
:重定向,要完成请求必须进行更进一步的操作;400~499
:客户端错误,请求有语法错误或请求无法实现;500~599
:服务器端错误,服务器未能实现合法的请求;
2 OkHttp
的使用流程
添加依赖:
dependencies {
...
implementation("com.squareup.okhttp3:okhttp:4.9.3")
}
在使用OkHttp
进行请求时,首先要创建一个OkHttpClient
的实例:
val client = OkHttpClient()
如果想要发起一条HTTP
请求,就需要创建一个Request
对象(HttpClient
和Request
都用了建造者模式):
val request = Request.Builder().url(url).build()
之后调用OkHttpClient
的newCall
方法来创建一个Call
对象,并调用它的execute/enqueue
方法来发送请求并获取服务器返回的数据,其中,execute()
方法是同步方法,enqueue()
方法是异步方法:
val response = client.newCall(request).execute()
response
对象就是服务器返回的数据,可以使用如下写法来的到返回的具体内容:
val responseData = response.body?.string()
下面是OkHttp
进行GET
请求/POST
请求的代码:
private val client = OkHttpClient() // 新建OkHttpClient客户端
// GET请求(同步)
fun getMethod(url: String): String {
val request = Request.Builder().url(url).build() // 新建Request对象
val response = client.newCall(request).execute() // Response为OkHttp中的响应
return response.body?.string() ?: ""
}
// POST请求(同步)
val JSON = "application/json; charset=utf-8".toMediaType()
fun postMethod(url: String, json: String): String {
val body = RequestBody.create(JSON, json)
val request = Request.Builder().url(url).post(body).build()
val response = client.newCall(request).execute()
return response.body?.string() ?: ""
}
// GET请求(异步)
fun run() {
val request = Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
})
}
// POST请求(异步)
fun run() {
val formBody = FormBody.Builder()
.add("search", "Jurassic Park")
.build()
val request = Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
})
}
更多使用方式:https://square.github.io/okhttp/recipes/
基本的步骤就是创建 OkHttpClient
、Request
和 Call/RealCall
,之后调用 Call/RealCall.execute()/enqueue(...)
方法。RealCall
实现了接口 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
}
}
class RealCall(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
...
}
3 OkHttp
的请求流程
以下是OkHttp
发起请求的大致流程:
3.1 OkHttpClient
在使用OkHttpClient
之前,需要先创建一个OkHttpClient
客户端,OkHttpClient
的构造方法如下:
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
constructor() : this(Builder())
}
除了直接创建 OkHttpClient
实例之外,还可以使用建造者模式。OkHttpClient.Builder
里面的可配置参数如下:
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
constructor() : this(Builder())
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher() // 分发器
internal var connectionPool: ConnectionPool = ConnectionPool() // 连接池
internal val interceptors: MutableList<Interceptor> = mutableListOf() // 拦截器
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
internal var retryOnConnectionFailure = true // 重试连接失败
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
internal var cache: Cache? = null
internal var callTimeout = 0
internal var connectTimeout = 10_000
internal var readTimeout = 10_000
internal var writeTimeout = 10_000
internal constructor(okHttpClient: OkHttpClient) : this() {
this.dispatcher = okHttpClient.dispatcher
this.connectionPool = okHttpClient.connectionPool
this.interceptors += okHttpClient.interceptors
this.networkInterceptors += okHttpClient.networkInterceptors
this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure
this.cookieJar = okHttpClient.cookieJar
this.cache = okHttpClient.cache
this.callTimeout = okHttpClient.callTimeoutMillis
this.connectTimeout = okHttpClient.connectTimeoutMillis
this.readTimeout = okHttpClient.readTimeoutMillis
this.writeTimeout = okHttpClient.writeTimeoutMillis
}
fun callTimeout(timeout: Long, unit: TimeUnit) = apply {
callTimeout = checkDuration("timeout", timeout, unit)
}
fun addInterceptor(interceptor: Interceptor) = apply {
interceptors += interceptor
}
fun build(): OkHttpClient = OkHttpClient(this)
}
}
获取 OkHttpClient
和 Request
实例之后,会调用 OkHttpClient.newCall(Reuqest)
方法,得到 Call/RealCall
对象。对于HttpClient.newCall
方法,RealCall
才是真正的执行者:
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
}
3.2 同步请求
同步请求是指发出网络请求之后当前线程被阻塞,直到请求的结果(成功或者失败)到来,才继续向下执行。同步请求使用的是execute
方法,如下所示:
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()
}
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
}
class RealCall( // RealCall为真正的请求执行者
val client: OkHttpClient,
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
internal val eventListener: EventListener = client.eventListenerFactory.create(this)
val call: RealCall
get() = this@RealCall
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" } // 每个call只能执行一次
timeout.enter()
callStart()
try {
client.dispatcher.executed(this) // 通过dispatcher已经进入执行状态
return getResponseWithInterceptorChain() // 通过一系列的拦截器请求处理和响应处理得到最终的返回结果
} finally {
client.dispatcher.finished(this) // 通知dispatcher,已经执行完毕
}
}
}
class Dispatcher constructor() {
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>() // 运行中的同步请求队列
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
}
async [əˈsɪŋk] 异步(asynchronous) sync [sɪŋk] 同步
同步请求是调度器dispatcher
执行—client.dispatcher.executed(call: RealCall)
来完成。
3.3 Dispatcher
调度器
dispatcher [dɪˈspætʃər] [计] 调度程序;[计] 分配器
分发器主要是用来维护请求队列与线程池,完成请求调配。在创建HttpClient
的时候,我们也可以传递自定义的线程池来创建分发器, 以下是Dispatcher
的相关源码:
class Dispatcher constructor() {
// 并发执行的最大请求数,超过了这个数量之后,请求在内存中排队,等待正在运行的调用完成
@get:Synchronized var maxRequests = 64
set(maxRequests) {
require(maxRequests >= 1) { "max < 1: $maxRequests" }
synchronized(this) {
field = maxRequests
}
promoteAndExecute()
}
// 同一域名同时执行的最大请求数
@get:Synchronized var maxRequestsPerHost = 5
set(maxRequestsPerHost) {
require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
synchronized(this) {
field = maxRequestsPerHost
}
promoteAndExecute()
}
// 闲置任务
@set:Synchronized
@get:Synchronized
var idleCallback: Runnable? = null
// 异步请求等待执行队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
// 异步请求正在执行队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
// 同步请求正在执行队列
private val runningSyncCalls = ArrayDeque<RealCall>()
// 异步请求使用的线程池
private var executorServiceOrNull: ExecutorService? = null
// 创建线程池(懒加载)
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
// 构造器,自定义线程池
constructor(executorService: ExecutorService) : this() {
this.executorServiceOrNull = executorService
}
}
Dispatcher
有两个构造方法,可以使用设定的线程池。如果没有设定线程池,则会使用默认的线程池,这个线程池比较适合执行大量的消耗比较少的任务。Dispatcher
中的默认线程池配置保证了新加入的任务可以被立即执行,避免阻塞。
同步请求不需要线程池,也不需要做任何限制,所以分发器只是做一下记录,后续按照加入队列的顺序同步请求即可。
3.4 异步请求
异步请求是指网络请求发出之后,不必等待请求结果的到来,就可以去做其他的事情,当请求结果到来时,在做处理结果的动作。异步请求使用的是enqueue
方法,如下所示:
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
}
class RealCall( // RealCall为真正的请求执行者
val client: OkHttpClient,
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
}
class Dispatcher constructor() {
private val readyAsyncCalls = ArrayDeque<AsyncCall>() // 正在准备中的异步请求队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>() // 运行中的异步请求
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService) // 线程池执行任务
}
return isRunning
}
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
}
}
异步请求是调度器dispatcher
执行—client.dispatcher.enqueue(call: AsyncCall)
,并通过回调(Callback
)获取服务器返回的结果。
Dispatcher
将call
加入到队列中,然后通过线程池来执行call
。Dispatcher
是一个任务调度器,它内部维护了三个双端队列(新来请求放在队尾,执行请求从对头部取):
readyAsyncCalls
:准备运行的异步请求;runningAsyncCalls
:正在运行的异步请求;runningSyncCalls
:正在运行的同步请求;
当 runningAsyncCalls
的数量小于 64
并且正在运行的请求主机小于 5
时,把请求添加到 runningAsyncCalls
中并在线程池中执行,否则就加入到 readyAsyncCalls
中进行等待执行。
4 OkHttp
的拦截器
OkHttp
拦截器就是基于责任链模式来实现的, 在请求到达时,拦截器会做一些处理(比如添加参数),然后传递给下一个拦截器处理:
OkHttp
提供了一系列的拦截器来处理相应的业务。也可以通过自定义拦截器,来实现自己的拦截业务。下面是一些常用的拦截器:
retryAndFollowUpInterceptor
:失败和重定向拦截器。 当请求内部抛出异常时,判定是否需要重试,当响应结果是3xx
重定向时,构建新的请求并发送请求;BridgeInterceptor
:应用层和网络层的桥接拦截器。 主要工作是请求添加cookie
,添加固定的header
,比如Host
、Content-Length
、Content-Type
、User-Agent
等等,然后保存响应结果的cookie
,如果响应使用gzip
压缩过,则还需要解压;CacheInterceptor
:缓存拦截器。如果命中缓存,则不发起网络请求;ConnectInterceptor
:连接拦截器,内部会维持一个连接池,主要负责TCP
连接,包括连接复用、创建连接(三次握手)、释放连接以及创建连接上的socket
流;networkInterceptor
:网络拦截器,通常用于监控网络层的数据传输;CallServerInterceptor
:请求拦截器,在前置准备工作完成后,真正发起网络请求;
OkHttp
空闲连接如何清除?
- 在将连接加入连接池的时候就会启动定时任务;
- 有空闲连接的话,如果最长的空闲时间大于
5
分钟或空闲数大于5
,就关闭移除这个最长空闲连接;如果空闲数不大于5
且最长的空闲时间不大于5
分钟,就在时间到5
分钟的时候清理; - 没有空闲连接,就等
5
分钟后再尝试清理; - 没有连接不清理;
5 总结
OkHttp
是一个网络请求框架,它是支持HTTP/2.0
的,因此HTTP/2.0
的一些优势也就体现在了这个框架上。比如说,HTTP/2.0
是基于SPDY
协议的,这个协议是用来减少网络延迟,提高网络速度的,再比如持久化连接、多路复用,客户端和服务端只有一个连接,可以通过这一个连接向服务端发起多重请求。如果HTTP/2.0
不可以用,还可以使用连接池来减少延迟,连接池是HTTP/1.1
提出的概念,连接池可以理解成是一个容器,我们创建的一些连接,在请求结束后,并不会断开,而是直接放到连接池中,下次直接拿来用就可以了。除了这些还有,比如缓存,如果命中缓存,可以避免重复的请求,如果网络出错,还可以自动恢复等等。
以下是OkHttp
的请求流程:
- 通过建造者模式来创建
OkHttpClient
和Request
对象(建造者模式就是用来创建灵活性和可扩展性强的复杂对象); - 通过调用
OkHttpClient
的newCall
方法来获取一个RealCall
对象,通过RealCall
来实现同步请求或者异步请求。在RealCall
的execute
方法和enqueue
方法中,是使用调度器Dispatcher
来完成的; - 在
Dispatcher
中维护了3
个双端队列——准备运行的异步请求队列(readyAsyncCalls
)、正在运行的异步请求队列(runningAsyncCalls
)和正在运行的同步队列(runningSyncCalls
)。如果是同步请求,则直接添加到同步请求队列中,等待顺序调用即可。如果是异步请求,如果正在运行的异步队列中的任务数未超过64
,且同一域名的请求没有超过5
个,则加入到正在运行的异步请求队列中,同时添加到线程池,否则加入到准备运行的请求队列中; OkHttp
的拦截器使用的是责任链模式(为同一请求的接收者创建一个链,请求沿着这条链传递,如果某个接收者需要处理这个请求,就交给它处理,处理完成后再继续向下传递),方法名为getResponseWithInterceptorChain()
。在OkHttp
中有六个拦截器,第一个是失败和重定向拦截器,如果内部请求出现异常,判断是否需要重试;第二个拦截器是应用层和网络层桥接拦截器,主要负责Cookie
的处理和添加固定的请求头;第三个拦截器是缓存拦截器,如果命中缓存,则不再进行网络请求;第四个拦截器是连接拦截器,其内部维护着一个的连接池,负责TCP
的连接,包括连接复用、创建连接、释放连接以及创建连接上的Socket
流;第五个拦截器是网络拦截器,主要用来监听网络上的数据传输;第六个拦截器是请求拦截器,当前置工作全部完成后,发起网络请求。
参考
https://blog.csdn.net/OneDeveloper/article/details/88381817
https://blog.csdn.net/u012949047/article/details/52296137
https://square.github.io/okhttp/
https://blog.csdn.net/qq_29882585/article/details/111870887
https://www.jianshu.com/p/01a25bd98b1f
https://baijiahao.baidu.com/s?id=1716997541121502638&wfr=spider&for=pc
征服面试官:OkHttp原理篇掌握这篇面试题汇总,吊打面试官!
面试突击:OkHttp 原理八连问
更多推荐
所有评论(0)