96SEO 2026-02-20 04:19 2
OKHTTP的拦截器是把所有的拦截器放到一个list里然后每次依次执行拦截器并且在每个拦截器分成三部分

预处理拦截器内容通过proceed方法把请求交给下一个拦截器下一个拦截器处理完成并返回后续处理工作。
getResponseWithInterceptorChain()
Throws(IOException::class)internal
getResponseWithInterceptorChain():
mutableListOfInterceptor()interceptors
client.interceptorsinterceptors
RetryAndFollowUpInterceptor(client)interceptors
BridgeInterceptor(client.cookieJar)interceptors
CacheInterceptor(client.cache)interceptors
client.networkInterceptors}interceptors
CallServerInterceptor(forWebSocket)val
originalRequest,connectTimeoutMillis
client.connectTimeoutMillis,readTimeoutMillis
client.readTimeoutMillis,writeTimeoutMillis
chain.proceed(originalRequest)if
{noMoreExchanges(null)}}}根据源码可知一共七个拦截器
addInterceptor(Interceptor)这是由开发者设置的会按照开发者的要求在所有的拦截器处理之前进行最早的拦截处理比如一些公共参数Header都可以在这里添加。
RetryAndFollowUpInterceptor这里会对连接做一些初始化工作以及请求失败的充实工作重定向的后续请求工作。
跟他的名字一样就是做重试工作还有一些连接跟踪工作。
BridgeInterceptor这里会为用户构建一个能够进行网络访问的请求同时后续工作将网络请求回来的响应Response转化为用户可用的Response比如添加文件类型content-length计算添加gzip解包。
CacheInterceptor这里主要是处理cache相关处理会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存而且如果本地有了可⽤的Cache就可以在没有网络交互的情况下就返回缓存结果。
ConnectInterceptor这里主要就是负责建立连接了会建立TCP连接或者TLS连接以及负责编码解码的HttpCodecnetworkInterceptors这里也是开发者自己设置的所以本质上和第一个拦截器差不多但是由于位置不同所以用处也不同。
这个位置添加的拦截器可以看到请求和响应的数据了所以可以做一些网络调试。
CallServerInterceptor这里就是进行网络数据的请求和响应了也就是实际的网络I/O操作通过socket读写数据。
我们知道Okhttp中通过okhttpClient对象是通过Builder对象初始化出来的此处Builder的用法是建造者模式建造者模式主要是分离出外部类的属性初始化而初始化属**给了内部类Buidler类这么做的好处是外部类不用关心属性的初始化。
而在初始化的时候有interceptors、networkInterceptors两种拦截器的初始化还有dispatcher(分发器)的初始化以及后面需要讲到的cache(缓存)初始化等。
初始化完了后通过builder的build方法构造出okhttpClient对象该类被称作客户端类通过它的newCall方法返回RealCall对象在newCall过程的过程中需要request的信息request信息包装了url、method、headers、body等信息。
最后通过RealCall的同步或异步方法交给了okhttpClient的dispatcher来处理在处理同步或异步之前都会判断有没有正在executed所以我们不能对同一个RealCall调用异步或同步方法。
在异步的时候会把RealCall给包装成一个AsyncCall它是一个runnable对象。
接着就来到了分发器异步处理部分首先会把AsyncCall加入到readyAsyncCalls的集合中该集合表示准备阶段的请求集合紧接着从runningAsyncCalls(该集合装的都是要即将请求的集合)和readyAsyncCalls集合中找相同host的AsyncCall如果找到了会把当中记录的相同host的个数给该AsyncCall。
注意这里保存host个数用的原子性的AtomicInteger来记录的
接着会去判断最大的请求是否大于64以及相同host是否大于5个这里也是okhttp面试高频知识点如果都通过的话会把当前的AsyncCall的相同host记录数加一接着会加入到runningAsyncCalls集合中接着循环遍历刚符合条件的AsyncCall通过线程池去执行AsyncCall注意此处的线程池的配置是没有核心线程总的线程个数是没有限制的也就是说都是非核心线程并且个数没有限制非核心线程等待的时间是60秒并且使用的任务队列是SynchronousQueue它是一个没有容量的阻塞队列只会当里面没有任务的时候才能往里面放任务当放完之后只能等它的任务被取走才能放这不就是jdk里面提供的Executors.newCachedThreadPool线程池吗可能是okhttp想自己定义线程工厂的参数吧定义线程的名字。
所以到这里才会进入到子线程由于AsyncCall是一个runnable因此最终执行来到了它的run方法吧run方法最终会走到execute方法该方法来到了okhttp最有意思的单链表结构的拦截器部分它会把所有的拦截器组装成一个集合然后传给RealInterceptorChain的process方法在该方法中会先把下一个RealInterceptorChain初始化出来然后把下一个RealInterceptorChain传给当前Interceptor的intercept方法最终一个个的response返回到AsyncCall的execute方法。
处理完当前的AsyncCall后会交给dispatcher它会将该AsyncCall的host数减一并且把它从runningAsyncCalls集合中移除接着再从readyAsyncCalls集合中拿剩下的AsyncCall继续执行直到执行完readyAsyncCalls里面的AsyncCall。
连接池部分主要是在RealConnectionPool类中该类用connections(双端队列)存储所有的连接cleanupRunnable是专门用来清除超时的RealConnection既然有清除的任务那肯定有清除的线程池没错该线程池(executor)跟okhttp处理异步时候的线程池是一样的keepAliveDurationNs表示每一个连接keep-alive的时间默认是5分钟maxIdleConnections连接池的最大容量默认是5个。
RealConnection中有transmitters字段用来保存该连接的transmitter个数通过里面的transmitter个数来标记该RealConnection有没有在使用中。
频繁的进行建立Sokcet连接TCP三次握手和断开SocketTCP四次分手是非常消耗网络资源和浪费时间的HTTP中的keepalive连接对于
降低延迟和提升速度有非常重要的作用。
复用连接就需要对连接进行管理这里就引入了连接池的概念。
Okhttp支持5个并发KeepAlive默认链路生命为5分钟(链路空闲后保持存活的时间)连接池有ConectionPool实现对连接进行回收和管理。
频繁的进行建立Sokcet连接和断开Socket是非常消耗网络资源和浪费时间的所以HTTP中的keepalive连接对于降低延迟和提升速度有非常重要的作用。
也就是可以在一次TCP连接中可以持续发送多份数据而不会断开连接。
所以连接的多次使用也就是复用就变得格外重要了而复用连接就需要对连接进行管理于是就有了连接池的概念。
OkHttp中使用ConectionPool实现连接池默认支持5个并发KeepAlive默认链路生命为5分钟。
1,首先ConectionPool中维护了一个双端队列Deque也就是两端都可以进出的队列用来存储连接。
2.然后在ConnectInterceptor也就是负责建立连接的拦截器中首先会找可用连接也就是从连接池中去获取连接具体的就是会调用到ConectionPool的get方法。
(connection.isEligible(address,
{streamAllocation.acquire(connection,
也就是遍历了双端队列如果连接有效就会调用acquire方法计数并返回这个连接。
3.如果没找到可用连接就会创建新连接并会把这个建立的连接加入到双端队列中同时开始运行线程池中的线程其实就是调用了ConectionPool的put方法。
true;executor.execute(cleanupRunnable);}connections.add(connection);}
}4.其实这个线程池中只有一个线程是用来清理连接的也就是上述的cleanupRunnable
{ConnectionPool.this.wait(waitMillis,
这个runnable会不停的调用cleanup方法清理线程池并返回下一次清理的时间间隔然后进入wait等待。
i.next();//检查连接是否是空闲状态//不是则inUseConnectionCount
(pruneAndGetAllocationCount(connection,
{inUseConnectionCount;continue;}idleConnectionCount;//
idleDurationNs;longestIdleConnection
connection;}}//如果超过keepAliveDurationNs或maxIdleConnections//从双端队列connections中移除if
connections.remove(longestIdleConnection);}
-1;}}closeQuietly(longestIdleConnection.socket());//
也就是当如果空闲连接maxIdleConnections超过5个或者keepalive时间大于5分钟则将该连接清理掉。
(Thread.holdsLock(connectionPool));if
IllegalStateException();this.connection
connection;this.reportedAcquired
reportedAcquired;connection.allocations.add(new
StreamAllocationReference(this,
在RealConnection中有一个StreamAllocation虚引用列表allocations。
每创建一个连接就会把连接对应的StreamAllocationReference添加进该列表中如果连接关闭以后就将该对象移除。
5.连接池的工作就这么多并不复杂主要就是管理双端队列DequeRealConnection可以用的连接就直接用然后定期清理连接同时通过对StreamAllocation的引用计数实现自动回收。
连接池是为了解决频繁的进行建立Sokcet连接TCP三次握手和断开SocketTCP四次分手。
Okhttp的连接池支持最大5个链路的keep-alive连接并且默认keep-alive的时间是5分钟。
连接池实现的类是RealConnectionPool它负责存储与清除的工作存储是通过ArrayDeque的双端队列存储删除交给了线程池处理cleanupRunnable的任务。
在每次创建RealConnection或从连接池中拿一次RealConnection会给RealConnection的
transmitters集合添加一个若引用的transmitter对象添加它主要是为了后面判断该连接是否在使用中在连接池中找连接的时候会对比连接池中相同host的连接。
如果在连接池中找不到连接的话会创建连接创建完后会存储到连接池中。
在把连接放入连接池中时会把清除操作的任务放入到线程池中执行删除任务中会判断当前连接有没有在使用中有没有正在使用通过RealConnection的transmitters集合的size是否为0来判断如果不在使用中找出空闲时间最长的连接如果空闲时间最长的连接超过了keep-alive默认的5分钟或者空闲的连接数超过了最大的keep-alive连接数5个的话会把存活时间最长的连接从连接池中删除。
保证keep-alive的最大空闲时间和最大的连接数。
可以说是okhttp的精髓所在了主要体现就是拦截器的使用具体代码可以看看上述的拦截器介绍。
在Okhttp中建造者模式也是用的挺多的主要用处是将对象的创建与表示相分离用Builder组装各项配置。
工厂模式和建造者模式类似区别就在于工厂模式侧重点在于对象的生成过程而建造者模式主要是侧重对象的各个参数配置。
例子有CacheInterceptor拦截器中又个CacheStrategy对象
cacheResponse.sentRequestAtMillis();this.receivedResponseMillis
cacheResponse.receivedResponseAtMillis();Headers
(Date.equalsIgnoreCase(fieldName))
HttpDate.parse(value);servedDateString
(Expires.equalsIgnoreCase(fieldName))
(Last-Modified.equalsIgnoreCase(fieldName))
HttpDate.parse(value);lastModifiedString
(ETag.equalsIgnoreCase(fieldName))
(Age.equalsIgnoreCase(fieldName))
HttpHeaders.parseSeconds(value,
关于Okhttp中websocket的使用由于webSocket属于长连接所以需要进行监听这里是用到了观察者模式
版本提供了EventListener接口可以让调用者接收一系列网络请求过程中的事件例如DNS解析、TSL/SSL连接、Response接收等。
通过继承此接口调用者可以监视整个应用中网络请求次数、流量大小、耗时(比如dns解析时间请求时间响应时间等等)情况。
释放当前Transmitter的RealConnectionpublic
{eventListener.callStart(this);client.dispatcher().executed(this);Response
getResponseWithInterceptorChain();if
{eventListener.callStart(this);client.dispatcher().enqueue(new
在OkHttp库中有一个EventListener类。
该类是网络事件的侦听器。
扩展这个类以监视应用程序的HTTP调用的数量、大小和持续时间。
所有启动/连接/获取事件最终将接收到匹配的结束/释放事件要么成功(非空参数)要么失败(非空可抛出)。
比如可以在开始链接记录时间dns开始结束等方法解析记录时间可以计算dns的解析时间。
比如可以在开始请求记录时间记录connectStartconnectEnd等方法时间则可以计算出connect连接时间。
Eventlistener只适用于没有并发的情况如果有多个请求并发执行我们需要使用Eventlistener.
Factory来给每个请求创建一个Eventlistener。
这个mRequestId是唯一值可以选择使用AtomicInteger自增1的方式设置id这个使用了cas保证多线程条件下的原子性特性。
{super.callStart(call);//mRequestId
mNextRequestId.getAndIncrement()
;//getAndAdd在多线程下使用cas保证原子性mRequestId
String.valueOf(mNextRequestId.getAndIncrement());ToolLogUtils.i(TAG-------callStart---requestId-----mRequestId);saveEvent(NetworkTraceBean.CALL_START);saveUrl(call.request().url().toString());}Overridepublic
domainName);ToolLogUtils.d(TAG,
dnsStart);saveEvent(NetworkTraceBean.DNS_START);}Overridepublic
inetAddressList);ToolLogUtils.d(TAG,
dnsEnd);saveEvent(NetworkTraceBean.DNS_END);}Overridepublic
connectStart);saveEvent(NetworkTraceBean.CONNECT_START);}Overridepublic
{super.secureConnectStart(call);ToolLogUtils.d(TAG,
secureConnectStart);saveEvent(NetworkTraceBean.SECURE_CONNECT_START);}Overridepublic
secureConnectEnd);saveEvent(NetworkTraceBean.SECURE_CONNECT_END);}Overridepublic
connectEnd);saveEvent(NetworkTraceBean.CONNECT_END);}Overridepublic
{super.requestHeadersStart(call);ToolLogUtils.d(TAG,
requestHeadersStart);saveEvent(NetworkTraceBean.REQUEST_HEADERS_START);}Overridepublic
requestHeadersEnd);saveEvent(NetworkTraceBean.REQUEST_HEADERS_END);}Overridepublic
{super.requestBodyStart(call);ToolLogUtils.d(TAG,
requestBodyStart);saveEvent(NetworkTraceBean.REQUEST_BODY_START);}Overridepublic
requestBodyEnd);saveEvent(NetworkTraceBean.REQUEST_BODY_END);}Overridepublic
{super.responseHeadersStart(call);ToolLogUtils.d(TAG,
responseHeadersStart);saveEvent(NetworkTraceBean.RESPONSE_HEADERS_START);}Overridepublic
{super.responseHeadersEnd(call,
responseHeadersEnd);saveEvent(NetworkTraceBean.RESPONSE_HEADERS_END);}Overridepublic
{super.responseBodyStart(call);ToolLogUtils.d(TAG,
responseBodyStart);saveEvent(NetworkTraceBean.RESPONSE_BODY_START);}Overridepublic
responseBodyEnd);saveEvent(NetworkTraceBean.RESPONSE_BODY_END);}Overridepublic
{super.callEnd(call);ToolLogUtils.d(TAG,
callEnd);saveEvent(NetworkTraceBean.CALL_END);generateTraceData();NetWorkUtils.timeoutChecker(mRequestId);}Overridepublic
generateTraceData(){NetworkTraceBean
IDataPoolHandleImpl.getInstance().getNetworkTraceModel(mRequestId);MapString,
traceModel.getNetworkEventsMap();MapString,
traceModel.getTraceItemList();traceList.put(NetworkTraceBean.TRACE_NAME_TOTAL,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.CALL_START,
NetworkTraceBean.CALL_END));traceList.put(NetworkTraceBean.TRACE_NAME_DNS,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.DNS_START,
NetworkTraceBean.DNS_END));traceList.put(NetworkTraceBean.TRACE_NAME_SECURE_CONNECT,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.SECURE_CONNECT_START,
NetworkTraceBean.SECURE_CONNECT_END));traceList.put(NetworkTraceBean.TRACE_NAME_CONNECT,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.CONNECT_START,
NetworkTraceBean.CONNECT_END));traceList.put(NetworkTraceBean.TRACE_NAME_REQUEST_HEADERS,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.REQUEST_HEADERS_START,
NetworkTraceBean.REQUEST_HEADERS_END));traceList.put(NetworkTraceBean.TRACE_NAME_REQUEST_BODY,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.REQUEST_BODY_START,
NetworkTraceBean.REQUEST_BODY_END));traceList.put(NetworkTraceBean.TRACE_NAME_RESPONSE_HEADERS,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.RESPONSE_HEADERS_START,
NetworkTraceBean.RESPONSE_HEADERS_END));traceList.put(NetworkTraceBean.TRACE_NAME_RESPONSE_BODY,NetWorkUtils.getEventCostTime(eventsTimeMap,NetworkTraceBean.RESPONSE_BODY_START,
NetworkTraceBean.RESPONSE_BODY_END));}private
IDataPoolHandleImpl.getInstance().getNetworkTraceModel(mRequestId);MapString,
networkTraceModel.getNetworkEventsMap();networkEventsMap.put(eventName,
SystemClock.elapsedRealtime());}private
IDataPoolHandleImpl.getInstance().getNetworkTraceModel(mRequestId);networkTraceModel.setUrl(url);}}参考
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback