Android开发老生新谈:从OkHttp原理看网络请求

Android开发老生新谈:从OkHttp原理看网络请求asyncCall.executeOn(executorService)
}
return isRunning
}
异步请求首先会将AsyncCall添加到双向队列readyA

asyncCall.executeOn(executorService)

}

返回值正在运行

}

异步请求通过首先将AsyncCall添加到双向队列readyAsyncCalls(即准备执行但尚未执行的队列)中来准备请求。然后它遍历就绪运行队列readyAsyncCalls,找到满足条件的请求,并将它们添加到有效请求、executableCalls和运行队列runningAsyncCalls列表中。过滤条件主要有两个:

if (runningAsyncCalls.size=this.maxRequests) Break:并发运行请求数小于最大请求数64。

if (asyncCall.callsPerHost.get()=this.maxRequestsPerHost) continue: 特定主机的并发请求数不能超过最大请求数5。

这意味着,如果您的并发请求数超过64 个或特定主机上的请求数超过5 个,则超出的请求暂时无法执行,必须等待它们被添加到执行队列中。

过滤并保存有效请求后,我们立即开始遍历请求,并使用调度器Dispatcher的ExecutorService来一一执行Runnable任务。即遍历完后,将这些有效网络添加到线程池中执行。要求。

类:RealCall.AsyncCall

重写fun run() {

threadName(\”OkHttp ${redactedUrl()}\”) {

……

尝试{

val 响应=getResponseWithInterceptorChain()

信号回调=true

responseCallback.onResponse(this@RealCall, 响应)

} catch(e: IOException) {

……

responseCallback.onFailure(this@RealCall, e)

}

}

}

上面的代码是一个运行在线程池中的请求任务。在try-catch 块内,您可以看到以下语句,该语句检索网络请求的结果响应并通知用户返回的响应。或由于回调而发生错误。该回调是第一次使用OkHttp时注册用于监控的回调。

另外,这个方法是不是很熟悉?因为,正如我在讨论上面三个主要核心类时提到的,任何RealCall 同步或异步请求最终都会到达getResponseWithInterceptorChain() 步骤。

通过getResponseWithInterceptorChain()方法返回网络请求结果响应。网络请求结果是如何获取的? 我们如何与服务器交互呢?我们来分析一下这个方法的内部结构。

拦截器的内部实现

==========================================================================

从上面OkHttp的结构分析我们可以看到,所有的网络请求细节都封装在核心方法getResponseWithInterceptorChain()中。接下来我们来考虑它的具体实现。

类别: 纯酒精

内心乐趣getResponseWithInterceptorChain(): 响应{

//构建完整的拦截器堆栈。

val 拦截器=mutableListOf()

拦截器+=client.interceptors

拦截器+=RetryAndFollowUpInterceptor(客户端)

拦截器+=BridgeInterceptor(client.cookieJar)

拦截器+=CacheInterceptor(client.cache)

拦截器+=ConnectInterceptor

如果(!forWebSocket){

拦截器+=client.networkInterceptors

}

Interceptor +=CallServerInterceptor(对于WebSocket)

val链=RealInterceptorChain(

电话=这个,

拦截器=拦截器,

索引=0,

替换=空,

请求=原始请求,

connectTimeoutMillis=client.connectTimeoutMillis,

readTimeoutMillis=client.readTimeoutMillis,

writeTimeoutMillis=客户端.writeTimeoutMillis

……

尝试{

val 响应=chain.proceed(originalRequest)

……

返回响应

}

……

}

getResponseWithInterceptorChain()的内部实现是通过责任链模型完成的,将网络请求的每个阶段封装到每个链(即每个拦截器)中,配置好每个拦截器后,将其放在一个列表中使用。创建一个RealInterceptorChain 对象作为参数,并调用chain.proceed(request) 发起请求并获取响应。

每个拦截器首先执行一些准备操作,例如确定请求是否可用,将请求转换为服务器可以解析的格式,然后对请求进行chain.proceed(request) 。前面提到,getResponseWithInterceptorChain()的内部实现是一个责任链模型,而chain.proceed(request)的作用是责任链模型的核心,将请求传递给下一个拦截器。

OkHttp总共包含7个拦截器。每个拦截器都封装了整个网络请求的细节,只要清楚就可以了。一切都会变得清晰起来。我们来一一分析一下这些窃听者的作用。

7种拦截器的职责

1、用户自定义拦截器interceptors

用户定义的拦截器优先于所有其他拦截器。开发者可以根据业务需求定制网络拦截器。例如自定义token处理拦截器、日志拦截器等。

2、RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor 是一个拦截器,如果请求失败并被重定向,它会重试。这会在内部启动一个请求循环,每个循环首先执行一个准备操作(call.enterNetworkInterceptorExchange(request, newExchangeFinder))。此准备操作的主要目的是创建ExchangeFinder 来查找请求的可用Tcl 或TSL 连接。然后设置一些与连接相关的参数,比如连接编解码器。 ExchangeFinder 稍后将在连接网络时详细说明。

准备就绪后,将发起网络请求(response=realChain.proceed(request))。这段代码的目的是将请求传递给下面的拦截器。同时判断当前请求是否出错,是否需要重定向。如果发生错误或需要重定向,则重新开始新的循环,直到没有错误发生且需要重定向。

我们还将在这里简要解释确定错误和重定向的标准。

错误标准:使用try-catch 块捕获请求中的异常。这里捕获RouteException和IOException,发生错误后首先判断当前请求是否可以重试。

重定向标准:判断是否需要重定向,检查响应的状态码,如果状态码为3xx,则表示需要重定向,发起新的请求,重试操作。

3、BridgeInterceptor

BridgeInterceptor是一个用于连接应用程序代码和网络代码的拦截器。换句话说,拦截器帮助用户为服务器请求准备必要的配置。这个定义可能过于抽象。首先,让我们看一下请求URL 的服务器请求标头是什么样的。

网址:wanandroid.com/wxarticle/c.

方法:获取

对应的请求头是:

获取/wxarticle/chapters/json HTTP/1.1

主机:wanandroid.com

Accept: application/json, text/plain,/

Accept-Encoding: gzip、deflate、br

接受语言: zh-CN,zh;q=0.9

连接:保持活动

用户代理: Mozilla/5.0 xxx

……

您可能想知道BridgeInterceptor 拦截器与此有何关系。事实上,BridgeInterceptor的作用就是让用户处理网络请求。这有助于用户输入服务器请求所需的配置信息,例如上面提到的User-Agent、Connection、Host和Accept-Encoding。同时,对请求的结果也进行相应的处理。

BridgeInterceptor的内部实现主要分为三个步骤:

设置用户网络请求的Content-Type、Content-Length、Host、Connection、Cookie等参数。即它将常见的请求转换为适合服务器解析的格式,以便适应服务器端。

通过chain.proceed(requestBuilder.build()) 方法将转换后的request 传递给下一个拦截器CacheInterceptor,并接收返回的结果Response。

生成的响应也会转换为gzip 和Content-Type,以便在应用程序端进行适配。

因此,BridgeInterceptor是应用程序和服务器之间的桥梁。

4、CacheInterceptor

CacheInterceptor 是一个处理网络请求缓存的拦截器。它的内部处理类似于一些图像缓存的逻辑。首先判断是否有可用的缓存,如果没有则调用chain.proceed(networkRequest) 方法。请求下一个拦截器。得到结果后,缓存结果。

5、ConnectInterceptor

ConnectInterceptor是一个建立连接和请求的拦截器。

内部乐趣initExchange(chain: RealInterceptorChain): Exchange {

……

valexchangeFinder=this.exchangeFinder!

val 编解码器=ExchangeFinder.find(客户端, 链)

val 结果=Exchange(this, eventListener, ExchangeFinder, 编解码器)

this.interceptorScopedExchange=结果

this.exchange=结果

……

如果(取消)抛出IOException(“取消”)

返回结果

}

从源码中可以看到我们首先通过ExchangeFinder查询编解码器。你知道这个ExchangeFinder吗?在上面RetryAndFollowUpInterceptor的分析中,每个循环首先准备创建一个ExchangeFinder。

这个编解码器是什么?这是确定请求是通过Http1 还是Http2 发出的编解码器。

找到合适的编解码器后,使用Exchange 作为参数来创建它。 Exchange 包括许多网络连接实现,我们将在稍后更详细地讨论。首先,让我们看看如何找到正确的编解码器。

如何找到可用连接?

要找到合适的编解码器,您必须首先找到可用的网络连接,然后使用此可用连接创建新的编解码器。 内部使用大约五种过滤方法来查找可用连接。

第一:从连接池中查找

if (connectionPool.callAcquirePooledConnection(地址, 调用, null, false)) {

val 结果=call.connection!

返回结果

}

搜索连接池中的连接以确定每个连接是否可用。条件是:

请求数必须小于连接可以承受的最大请求数。在HTTP2 以下,最大请求数为1,并且可以在此连接上创建新的交换。

连接的主机与请求的主机匹配。

如果从连接池中获取到符合条件的连接,则直接返回。

如果没有,请继续执行第二种方法来获取可用连接。

第二种:传递路由并从连接池中检索

if (connectionPool.callAcquirePooledConnection(地址, 呼叫, 路线, false)) {

val 结果=call.connection!

返回结果

}

第二种方法仍然是从连接池中获取,但是这次的不同之处在于,这个路由是作为参数传递的。该路由是一个包含路由路由的列表集合,其中路由实际上指的是连接的IP地址、TCP端口和端口。代理模式。

这次我们主要从Http2的连接池中获取。路由必须共享IP 地址,此连接的服务器证书必须包含新主机,并且证书必须与主机匹配。

第三种方法:自己创建连接

如果前两次没有从连接池中获得可用连接,请自行创建一个。

val newConnection=RealConnection(连接池, 路由)

call.connectionToCancel=newConnection

尝试{

newConnection.connect(

连接超时,

读取超时,

写入超时,

Ping 间隔毫秒,

启用连接重试,

电话,

事件监听器

}

连接创建实际上是内部套接字和TLS 连接。 “TCP/TLS 连接是如何实现的?”这个问题将在稍后回答。

自己创建连接后,又从连接池中查找。

第四种方法:将多路复用设置为true,继续从连接池中查找。

if (connectionPool.callAcquirePooledConnection(地址、呼叫、路由、true)) {

val 结果=call.connection!

newConnection.socket().closeQuietly()

返回结果

}

这次,我们将搜索连接池并将requireMultiplexed 设置为true 以仅查找支持多路复用的连接。连接建立后,新连接将存储在连接池中。

如何找到Http1和Http2的编/解码器?

我们已经分析了上面几种寻找可用且成功的连接的方法。要创建编解码器,我们需要根据这些连接来区分Http1 和Http2。如果http2Connection 不为null,则创建Http2ExchangeCodec,否则创建Http1ExchangeCodec。

找到编解码器后,返回到ConnectInterceptor 的开头并使用该编解码器创建Exchange。在内部,此Exchange 实际上使用Http1 或Http2 解码器来写入请求标头或构造并发送请求正文。到服务器。

Exchange 初始化成功后,请求将传递到下一个拦截器CallServerInterceptor。

6、CallServerInterceptor

CallServerInterceptor是链中的最后一个拦截器,主要用于向服务器发送内容,主要发送http header和body信息。

在内部,它使用我们上面创建的Exchange 来写入请求标头、构造请求正文、发送请求、检索结果、解析结果并将其发回。

7、NetworkInterceptor

networkInterceptor也是一个用户自定义的拦截器,它的位置在ConnectInterceptor之后、CallServerInterceptor之前。我知道第一个拦截器是用户定义的,但是这个和这个有什么区别?

在networkInterceptor之前已经有多个拦截器在使用,并且请求信息已经非常复杂,包括RetryAndFollowUpInterceptor中的重试拦截器。同样是调用一次,所以networkInterceptor会被调用多次,但是第一个自定义拦截器只被调用一次。如果您需要自定义拦截器(例如令牌或日志)以进行资源消耗,通常使用第一个拦截器。

到目前为止,所有七个拦截器均已分析完毕。在分析ConnectInterceptor时,出现了一个问题:TCP/TLS连接是如何实现的?

如何建立TCP/TLS 连接?

================================================================================

TCP连接

有趣的连接(

connectTimeout: 整数,

readTimeout: 整数,

writeTimeout: 整数,

pingIntervalMillis: Int,

connectionRetryEnabled: 布尔值,

拨打:电话,

事件监听器: 事件监听器

){

……

而(真){

尝试{

if (route.requiresTunnel()) {

connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)

如果(rawSocket==null){

//无法连接到隧道,但正确关闭了资源。

休息

}

} 除此之外{

connectSocket(connectTimeout, readTimeout, 调用, eventListener)

}

建立协议(connectionSpecSelector、pingIntervalMillis、call、eventListener)

eventListener.connectEnd(调用,route.socketAddress,route.proxy,协议)

休息

} catch(e: IOException) {

……

}

可以看到connect内部开启了一个while循环。第一步是确定route.requiresTunnel()。此requireTunnel() 方法指示请求是否使用Proxy.Type.HTTP 代理并且目标是HTTPS 连接。

在这种情况下,请创建代理隧道连接隧道(connectTunnel)。创建此隧道的目的是使用HTTP 来代理HTTPS 请求。

否则,直接建立TCP连接(connectSocket)。

建立请求协议。

代理隧道是如何创建的?在内部,您首先通过HTTP 代理发出TLS 请求。即,将Host、Proxy-Connection 和User-Agent 标头添加到地址URL 中。然后,它使用connectSocket 打开TCP 连接,并尝试使用TLS 请求创建代理隧道最多21 次。

正如您所看到的,无论是否需要代理隧道,都会建立TCP 连接(connectSocket)。那么TCP连接是如何建立的呢?

私人乐趣connectSocket(

connectTimeout: 整数,

readTimeout: 整数,

拨打:电话,

事件监听器: 事件监听器

){

val proxy=root.proxy

val 地址=root.地址

val rawSocket=when (proxy.type()) {

Proxy.Type.DIRECT、Proxy.Type.HTTP – 地址.socketFactory.createSocket()!

else-socket(代理)

}

this.rawSocket=原始套接字

eventListener.connectStart(调用,route.socketAddress,代理)

rawSocket.soTimeout=readTimeo

尝试{

Platform.get().connectSocket(rawSocket,route.socketAddress,connectTimeout)

} catch(e: ConnectException) {

throw ConnectException(\”无法连接到${route.socketAddress}\”).apply {

初始化原因(e)

}

}

……

}

从源码来看,如果代理类型是直连或者HTTP/FTP代理,则直接创建socket。否则,将指定并创建代理类型。创建后,您将看到返回一个代表TCP 连接的rawSocket。最后调用Platform.get().connectSocket。这实际上是

调用socket的connect方法来打开一个TCP连接。

TLS连接

在建立TCP连接或者创建Http代理隧道后,就会开始建立连接协议(establishProtocol)。

private fun establishProtocol(

connectionSpecSelector: ConnectionSpecSelector,

pingIntervalMillis: Int,

call: Call,

eventListener: EventListener

) {

if (route.address.sslSocketFactory == null) {

if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {

socket = rawSocket

protocol = Protocol.H2_PRIOR_KNOWLEDGE

startHttp2(pingIntervalMillis)

return

}

socket = rawSocket

protocol = Protocol.HTTP_1_1

return

}

eventListener.secureConnectStart(call)

connectTls(connectionSpecSelector)

eventListener.secureConnectEnd(call, handshake)

if (protocol === Protocol.HTTP_2) {

startHttp2(pingIntervalMillis)

}

}

判断当前地址是否是HTTPS;
如果不是HTTPS,则判断当前协议是否是明文HTTP2,如果是的则调用startHttp2,开始Http2的握手动作,如果是Http/1.1则直接return返回;
如果是HTTPS,就开始建立TLS安全协议连接了(connectTls);
如果是HTTPS且为HTTP2,除了建立TLS连接外,还会调用startHttp2,开始Http2的握手动作。

在上述第3步时就提到了TLS的连接(connectTls),那我们就来看一下它的内部实现:

private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {

val address = route.address

val sslSocketFactory = address.sslSocketFactory

var success = false

var sslSocket: SSLSocket? = null

try {

// Create the wrapper over the connected socket.

sslSocket = sslSocketFactory!!.createSocket(

rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket

// Configure the socket’s ciphers, TLS versions, and extensions.

val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)

if (connectionSpec.supportsTlsExtensions) {

Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)

}

// Force handshake. This can throw!

sslSocket.startHandshake()

// block for session establishment

val sslSocketSession = sslSocket.session

val unverifiedHandshake = sslSocketSession.handshake()

// Verify that the socket’s certificates are acceptable for the target host.

if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {

val peerCertificates = unverifiedHandshake.peerCertificates

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
ureTlsExtensions(sslSocket, address.url.host, address.protocols)

}

// Force handshake. This can throw!

sslSocket.startHandshake()

// block for session establishment

val sslSocketSession = sslSocket.session

val unverifiedHandshake = sslSocketSession.handshake()

// Verify that the socket’s certificates are acceptable for the target host.

if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {

val peerCertificates = unverifiedHandshake.peerCertificates

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-FYWGgGwL-1718990421526)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

#以上关于Android开发老生新谈:从OkHttp原理看网络请求的相关内容来源网络仅供参考,相关信息请以官方公告为准!

原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/91614.html

Like (0)
CSDN的头像CSDN
Previous 2024年6月22日
Next 2024年6月22日

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注