服务请求拦截器会把消息请求头、请求体发送给服务端。并把服务端返回信息转换为Response。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.exchange!!
val request = realChain.request
val requestBody = request.body
val sentRequestMillis = System.currentTimeMillis()

// 向socket中写入请求头
exchange.writeRequestHeaders(request)

var invokeStartEvent = true
var responseBuilder: Response.Builder? = null
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
invokeStartEvent = false
}
if (responseBuilder == null) {
if (requestBody.isDuplex()) { // 双工传输,HTTP/2才支持
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest()
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
requestBody.writeTo(bufferedRequestBody)
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
// 消息体写入到socket
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
}
} else {
exchange.noRequestBody()
if (!exchange.connection.isMultiplexed) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
exchange.noNewExchangesOnConnection()
}
}
} else {
exchange.noRequestBody()
}

if (requestBody == null || !requestBody.isDuplex()) {
// 刷新输出流,会调用socket的flush
exchange.finishRequest()
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
invokeStartEvent = false
}
}
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code
if (code == 100) {
// Server sent a 100-continue even though we did not request one. Try again to read the actual
// response status.
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
}
response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
code = response.code
}

exchange.responseHeadersEnd(response)

response = if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response.newBuilder()
.body(EMPTY_RESPONSE)
.build()
} else {
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
"close".equals(response.header("Connection"), ignoreCase = true)) {
exchange.noNewExchangesOnConnection()
}
if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
throw ProtocolException(
"HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
}
return response
}
}

这个是最后一个拦截器,从代码可以看到没有调用chain.process。但是服务请求拦截器返回的response并不是最终的response,前置的拦截器还没有完全处理完成。比如服务返回了redirect,在重试和重定向拦截器中会发起重定向,拦截器会被再次执行。

阅读全文 »

连接拦截器(ConnectInterceptor)负责与服务器建立网络连接,连接缓存池、dns解析、https证书校验等都在连接拦截器阶段来执行。连接拦截器类中的代码非常少,主要逻辑都在Exchange的初始化中:

1
2
3
4
5
6
7
8
9
10
11
12
13
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
// 1. 初始化Exchange
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
// 2. 交给下一个拦截器进行处理,如果用户注册了网络拦截器,
// 下一个拦截器就是用户注册的网络拦截器
val result = connectedChain.proceed(realChain.request)
return result
}
}

连接拦截器的主要时序:


zenuml
ConnectInterceptor.intercept {
    RealCall.initExchange {
        ExchangeCodec = ExchangeFinder.find {
            RealConnection = findHealthyConnection {
                findConnection
            }
            ExchangeCodec = RealConnection.newCodec {
                if (http2) {
                    "new Http2ExchangeCodec()"
                } else {
                    "new Http1ExchangeCodec()"
                }
            }
        }
        new Exchange
    }
    RealInterceptorChain."copy(传入Exchange)"
    RealInterceptorChain."proceed(把网络请求交给下一个拦截器)"
}

连接池查找可用连接

下面流程图是从okhttp3.internal.connection.ExchangeFinder#findConnection开始进行分析:

新的请求与connection是否是相同的host和port不同相同删除该connection与call之间的关联将connection标记被idle状态关闭socket为空connection关联的calls列表为空复用当前connection不为空当前connection是否为空?(RealCall持有的RealConnection)查找连接池连接池中存在可用连接存在不存在关联call与connection返回连接池中找的的连接创建路由解析器并解析ip地址基于IP地址再次查找连接池返回可用连接yes存在可用连接不使用使用合并连接的路由,或者使用已存在路由选择器的路由创建新的Connection连接服务器查找连接池是否存在多路复用的connection(HTTP/2)存在不存在关闭新建connection的socket返回多路复用的connection新连接放到连接池中关联call与connection返回新建连接

连接拦截器类图结构

ConnectInterceptorRealInterceptorChainExchangeExchangeFinderExchangeCodecRealCallOkHttpClientRealConnectionHttp1ExchangeCodecHttp2ExchangeCodecSocketRealConnectionPoolAddressDnsDnsSystem10..nrawSocketsocket0..n1
阅读全文 »

重试和重定向拦截器主要负责三件事:1) 出错后判断是否重试;2) 处理重定向;3) 处理用户认证(401和407响应码);

来看下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
// 1、重试和重定向的场景依然是在while循环中,不会退出该拦截器
while (true) {
// 2、根据需要创建 ExchangeFinder
call.enterNetworkInterceptorExchange(request, newExchangeFinder)

var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}

try {
// 3、realChain指向下一个拦截器,发生重试或者重定向后,
// 会从下一个拦截器开始执行,前面的应用拦截器和此拦截器都不会被执行了
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
// 4、错误可以恢复,继续while循环
continue
} catch (e: IOException) {
// An attempt to communicate with a server failed. The request may have been sent.
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
// 5、错误可以恢复,继续while循环
continue
}

......

val exchange = call.interceptorScopedExchange
// 6、followUpRequest是判断是否重试的主要逻辑,包括重定向、超时重试、用户认证等场景
val followUp = followUpRequest(response, exchange)

if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}

val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}

response.body?.closeQuietly()

if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}

request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}

应用拦截器只会被执行一次的原因

我们都知道okhttp支持两种类型的用户层拦截器:应用拦截器(Application Interceptors)和网络拦截器(Network Interceptors)。应用拦截器每次发起请求都会被保证执行一次,即使发生了重试和重定向也是被执行一次。而网络拦截器在重试和重定向的场景都会被执行多次,而且缓存的场景可能不会被执行。这种差异是什么原因导致的呢?

RetryAndFollowUpInterceptor拦截器使用了一个while循环,当需要重试或者重定向时,不会退出循环,继续执行chain.proceed(),这个chain就是RealInterceptorChain。在分析RealInterceptorChain的proceed()方法时,我们知道每次执行下一个拦截器时,都会把RealInterceptorChain拷贝一份,chain的index就指向下一个拦截器。所以重试和重定向的情况都会从下一个拦截器开始,而用户注册的程序拦截器在RetryAndFollowUpInterceptor前面,这就是即使发生了重试和重定向程序拦截器也会只被执行一次的根本原因。而网络拦截器排在RetryAndFollowUpInterceptor后面,所以会被执行多次。

参考文章

  1. HTTP 身份验证
阅读全文 »

BridgeInterceptor拦截器简要描述一下就是做了如下三件事:

  1. 首先会把用户的Request进行进一步的处理(处理request header),转换成真正请求网络的Request;
  2. 然后继续调用其他拦截器进行网络请求;
  3. 最后收到响应结果后保存cookie,并解压缩gzip,最终转换为用户的Response。

BridgeInterceptor处理时序

BridgeInterceptorBridgeInterceptorRequestRequestRequest.BuilderCookieJarCookieJarRealInterceptorChainRealInterceptorChaininterceptnewBuilder()基于用户传入的Request生成新的Request.BuilderRequest.Builder如果body不为null,则根据需要添加请求头:Content-Type、Content-Length、Transfer-Encoding如果Host不存在,则添加如果Connection,则添加Connection=Keep-Alive如果用户没有指定Accept-Encoding和Range头,则设置为gziploadForRequest(): 加载cookie添加Cookie添加User-Agentbuild()new() 生成新的Requestproceed(Request) 继续其他拦截器的处理流程其他拦截器处理完成receiveHeaders(): 保存cookie,header中的Set-Cookie字段如果响应内容是gzip格式,则进行解压处理返回 Response

从上述流程可以看出BridgeInterceptor拦截器的处理逻辑比较简单。

阅读全文 »

缓存使用方法

1
2
3
4
5
6
7
private val client: OkHttpClient = OkHttpClient.Builder()
.cache(Cache(
directory = File("/dir/http_cache"),
maxSize = 5L * 1024L * 1024L // 5MiB
))
.build()

缓存机制只实现了get请求的缓存,不支持其他的请求类型,比如POST。下面的官方的说明:

Don’t cache non-GET responses. We’re technically allowed to cache HEAD requests and some POST requests, but the complexity of doing so is high and the benefit is low.

Cacheget(request: Request): Response?put(response: Response): CacheRequest?CacheInterceptorDiskLruCacheCacheStrategyCacheControl

Cache-Control请求头是控制缓存策略的关键,server、client端都可以进行设置。Cache-Control决定了哪些response可以被缓存,以及缓存的response是否满足当前的request。协议定义可以参考RFC 7234, 5.2

缓存拦截器流程

缓存拦截器的处理流程如下:

  1. 首先通过url的md5值去读取本地可用缓存(后面会校验缓存是否可用);
  2. 计算得到CacheStrategy,实际上就是计算得到networkRequestcacheResponse,如果两者都为null,就直接报504错误;如果networkRequest是null,表示直接使用缓存。
阅读全文 »

okhttp 整体架构

RealCallOkHttpClientRealInterceptorChainindex: Intinterceptors: List<Interceptor>Response proceed(Request r)InterceptorExchangeFinderRealConnectionPoolExchangeRetryAndFollowUpInterceptorBridgeInterceptorCacheInterceptorConnectInterceptorCallServerInterceptor1n RealCallRealCallRealInterceptorChainexecutegetResponseWithInterceptorChain创建Interceptor ListnewRealInterceptorChainproceednext = copy(index+1)复制当前责任链,指针指向下一个拦截器intercept(next)执行拦截器

okhttp的拦截器分为应用拦截器网络拦截器,拦截器按照顺序执行,看下RealCall.getResponseWithInterceptorChain中拦截器的顺序定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
//1.添加用户注册的应用拦截器
interceptors += client.interceptors
//2.添加重试重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
//3.添加桥接拦截器,用于用户数据和网络数据之间的相互转换
interceptors += BridgeInterceptor(client.cookieJar)
//4.添加缓存拦截器
interceptors += CacheInterceptor(client.cache)
//5.连接拦截器,负责跟服务器建立连接
interceptors += ConnectInterceptor
if (!forWebSocket) {
//6.添加用户自定义网络拦截器
interceptors += client.networkInterceptors
}
//7.服务请求连接器
interceptors += CallServerInterceptor(forWebSocket)

val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
//后面代码省略

拦截器的处理时序

这个是简化版的连接器处理时序,连接器调用到下一个都是要经过RealInterceptorChain.proceed,这里面逻辑比较简单,就是找到下一个连接器并执行,这个细节时序图中没有体现。

应用拦截器应用拦截器重试重定向拦截器重试重定向拦截器桥接拦截器桥接拦截器缓存拦截器缓存拦截器连接拦截器连接拦截器网络拦截器网络拦截器服务请求拦截器服务请求拦截器intercept初始化ExchangeFinder重试和重定向场景运行多次处理请求头,包括加载cookie加载本地缓存缓存池获取可用连接或新建连接,创建Exchange和HTTP解码器发送请求解析响应保存缓存保存cookiegzip解压处理异常和返回码决定是否重试、重定向

同步和异步请求源码解析

阅读全文 »

一次点击一般会有先后两个事件:PointerDownEvent和PointerUpEvent,分别表示按下down和抬起up(如果手指发生了滑动,还会有PointerMoveEvent事件)。本文分别分析按下和抬起两个事件的处理流程。

备注:本文以GestureDetector为示例进行点击事件处理流程的分析,基于Flutter 2.5.3。

PointerDownEvent(按下)事件的处理流程

GestureBindingGestureBindingHitTestResultRenderPointerListenerRenderPointerListenerTapGestureRecognizerTapGestureRecognizerhandlePointerEvent(PointerDownEvent)newHitTestResult命中测试 hitTest(RendererBinding).hitTestrenderView.hitTestchild!.hitTest深度优先遍历子树add(命中测试通过的RenderBox)add(HitTestEntry(RenderView.this))add(HitTestEntry(GestureBinding.this))点击down事件分发dispatchEvent获取命中测试的target列表handleEventonPointerDown?.call(event)对应RawGestureDetectorState._handlePointerDownaddPointer(event)addAllowedPointer(event)startTrackingPointer(pointer)pointerRouter.addRoute 加入到路由中gestureArena.add(pointer, this) 添加到事件竞技场handleEventpointerRouter.route(event)handleEvent正常按键按下的情况无操作。在触摸点移出按键区域等特殊情况会做清理操作。gestureArena.close阻止新事件竞争者加入只有一个竞争者才会执行scheduleMicrotaskGestureArenaManager删除pointer id对应的竞技场     acceptGesture(pointer)唯一的竞争者此时并不会立刻触发点击事件回调,而是等待up事件的到来。

命中测试 hitTest

一次点击事件发生后,会首先进行命中测试。

命中测试hitTest在PointerDownEvent事件的处理流程中进行,从RendererBinding.hitTest开始,然后调用GestureBinding.hitTest,再调用RenderView.hitTest,RenderView会直接调用子节点的hitTest。(其实RendererBinding和GestureBinding都是对应一个WidgetsFlutterBinding类实例,属于继承关系。)RenderView的子节点是RenderBox的子类,从此之后,参与命中测试的组件都是RenderBox或者其子类。hitTest的默认处理逻辑在RenderBox中实现:

1
2
3
4
5
6
7
8
9
10
11
abstract class RenderBox extends RenderObject {
bool hitTest(BoxHitTestResult result, { required Offset position }) {
if (_size!.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
}

处理逻辑解读:

  • 先判断点击的坐标是否在控件范围内,如果在,则表示命中测试,否则就返回,不会再对子节点进行命中测试;
  • 执行hitTestChildren判断子节点是否命中测试,如果命中就加入到HitTestResult中;
  • 最后把自己加入到HitTestResult中。
阅读全文 »

红黑树的定义

红黑树是一种自平衡二进制搜索树。每个节点存储一个代表颜色的标记位,用于在数的插入和删除期间保持数的近似平衡。红黑树是一个特殊的二叉树,用于组织可以进行比较的数据元素。

红黑树的特点

  1. 每个节点都必须是红色或者黑色;
  2. 跟节点是黑色。这条规则有时会被忽略,因为跟节点始终可以从红色变为黑色,但反过来就不一定成立。该规则对数据分析影响很小;
  3. 所有叶子节点(包括空节点)都是黑色;
  4. 红色节点的子节点都是黑色;
  5. 从给定节点到其任何后代NIL节点的每条路径都经过相同数量的黑色节点;

参考链接:

阅读全文 »

startActivity流程

ActivityActivityInstrumentationInstrumentationIActivityManagerIActivityManagerAMSAMSActivityStarterActivityStarterActivityRecordActivityStackSupervisorActivityStackSupervisorActivityStackActivityStackApplicationThreadApplicationThreadActivityThreadActivityThreadstartActivitystartActivityForResultexecStartActivitystartActivitystartActivitystartActivityAsUserstartActivityMayWaitstartActivityLockedstartActivityActivityRecordstartActivitystartActivityUncheckedresumeFocusedStackTopActivityLockedresumeTopActivityUncheckedLockedresumeTopActivityInnerLockedstartSpecificActivityLocked会判断是否启动新的进程realStartActivityLockedscheduleLaunchActivitysendMessage(H.LAUNCH_ACTIVITY, r)handleMessagestartProcessLocked需要启动进程

Activity启动流程

ActivityThreadActivityThreadInstrumentationInstrumentationActivityPhoneWindowDecorViewWindowManagerImplWindowManagerImplWindowManagerGlobalWindowManagerGlobalViewRootImplhandleMessage.LAUNCH_ACTIVITYhandleLaunchActivityinitializeServiceManager.getService("window"))performLaunchActivitynewActivityClassLoader.loadClassnewInstanceActivityattachmWindowPhoneWindowsetWindowManagercallActivityOnCreateperformCreateonCreatesetContentViewDecorViewmContentParent.addViewperformStartcallActivityOnStartonStarthandleResumeActivityperformResumeActivityperformResumecallActivityOnResumeonResumeaddView(decorView)addViewViewRootImplmViews.add(view)mRoots.add(root)mParams.add(wparams)setView WindowManagerGlobalArrayList<View> mViews;ArrayList<ViewRootImpl> mRootsViewDecorViewViewRootImpl1n1n
阅读全文 »
0%