如何用ps做创意视频网站,做特产的网站,可以做淘宝客的网站有哪些,做电商有那个网站前言OkHttp可以说是Android开发中最常见的网络请求框架,OkHttp使用方便#xff0c;扩展性强#xff0c;功能强大#xff0c;OKHttp源码与原理也是面试中的常客但是OKHttp的源码内容比较多#xff0c;想要学习它的源码往往千头万绪#xff0c;一时抓不住重点.本文从几个问题…前言OkHttp可以说是Android开发中最常见的网络请求框架,OkHttp使用方便扩展性强功能强大OKHttp源码与原理也是面试中的常客但是OKHttp的源码内容比较多想要学习它的源码往往千头万绪一时抓不住重点.本文从几个问题出发梳理OKHttp相关知识点以便快速构建OKHttp知识体系如果对你有用欢迎点赞~本文主要包括以下内容OKHttp请求的整体流程是怎样的?OKHttp分发器是怎样工作的?OKHttp拦截器是如何工作的?应用拦截器和网络拦截器有什么区别?OKHttp如何复用TCP连接?OKHttp空闲连接如何清除?OKHttp有哪些优点?OKHttp框架中用到了哪些设计模式?1. OKHttp请求整体流程介绍首先来看一个最简单的Http请求是如何发送的。val okHttpClient OkHttpClient()val request: Request Request.Builder().url(https://www.google.com/).build()okHttpClient.newCall(request).enqueue(object :Callback{overridefunonFailure(call: Call, e: IOException) {}overridefunonResponse(call: Call, response: Response) {}})
复制代码这段代码看起来比较简单,OkHttp请求过程中最少只需要接触OkHttpClient、Request、Call、 Response但是框架内部会进行大量的逻辑处理。所有网络请求的逻辑大部分集中在拦截器中但是在进入拦截器之前还需要依靠分发器来调配请求任务。关于分发器与拦截器我们在这里先简单介绍下后续会有更加详细的讲解分发器:内部维护队列与线程池完成请求调配;拦截器:五大默认拦截器完成整个请求过程。整个网络请求过程大致如上所示通过建造者模式构建OKHttpClient与 RequestOKHttpClient通过newCall发起一个新的请求通过分发器维护请求队列与线程池完成请求调配通过五大默认拦截器完成请求重试缓存处理建立连接等一系列操作得到网络请求结果2. OKHttp分发器是怎样工作的?分发器的主要作用是维护请求队列与线程池,比如我们有100个异步请求肯定不能把它们同时请求而是应该把它们排队分个类分为正在请求中的列表和正在等待的列表等请求完成后即可从等待中的列表中取出等待的请求从而完成所有的请求而这里同步请求各异步请求又略有不同同步请求synchronizedvoidexecuted(RealCall call) {runningSyncCalls.add(call);
}
复制代码因为同步请求不需要线程池也不存在任何限制。所以分发器仅做一下记录。后续按照加入队列的顺序同步请求即可异步请求synchronizedvoidenqueue(AsyncCall call) {//请求数最大不超过64,同一Host请求不能超过5个if (runningAsyncCalls.size() maxRequests runningCallsForHost(call) maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);}
}
复制代码当正在执行的任务未超过最大限制64同时同一Host的请求不超过5个则会添加到正在执行队列同时提交给线程池。否则先加入等待队列。每个任务完成后都会调用分发器的finished方法,这里面会取出等待队列中的任务继续执行3. OKHttp拦截器是怎样工作的?经过上面分发器的任务分发下面就要利用拦截器开始一系列配置了# RealCalloverridefunexecute(): Response {try {client.dispatcher.executed(this)return getResponseWithInterceptorChain()} finally {client.dispatcher.finished(this)}}
复制代码我们再来看下RealCall的execute方法可以看出最后返回了getResponseWithInterceptorChain,责任链的构建与处理其实就是在这个方法里面internalfungetResponseWithInterceptorChain(): Response {// Build a full stack of interceptors.val interceptors mutableListOfInterceptor()interceptors client.interceptorsinterceptors RetryAndFollowUpInterceptor(client)interceptors BridgeInterceptor(client.cookieJar)interceptors CacheInterceptor(client.cache)interceptors ConnectInterceptorif (!forWebSocket) {interceptors client.networkInterceptors}interceptors CallServerInterceptor(forWebSocket)val chain RealInterceptorChain(call this,interceptors interceptors,index 0)val response chain.proceed(originalRequest)}
复制代码如上所示构建了一个OkHttp拦截器的责任链责任链顾名思义就是用来处理相关事务责任的一条执行链执行链上有多个节点每个节点都有机会条件匹配处理请求事务如果某个节点处理完了就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。如上所示责任链添加的顺序及作用如下表所示拦截器作用应用拦截器拿到的是原始请求可以添加一些自定义header、通用参数、参数加密、网关接入等等。RetryAndFollowUpInterceptor处理错误重试和重定向BridgeInterceptor应用层和网络层的桥接拦截器主要工作是为请求添加cookie、添加固定的header比如Host、Content-Length、Content-Type、User-Agent等等然后保存响应结果的cookie如果响应使用gzip压缩过则还需要进行解压。CacheInterceptor缓存拦截器如果命中缓存则不会发起网络请求。ConnectInterceptor连接拦截器内部会维护一个连接池负责连接复用、创建连接三次握手等等、释放连接以及创建连接上的socket流。networkInterceptors网络拦截器用户自定义拦截器通常用于监控网络层的数据传输。CallServerInterceptor请求拦截器在前置准备工作完成后真正发起了网络请求。我们的网络请求就是这样经过责任链一级一级的递推下去最终会执行到CallServerInterceptor的intercept方法此方法会将网络响应的结果封装成一个Response对象并return。之后沿着责任链一级一级的回溯最终就回到getResponseWithInterceptorChain方法的返回,如下图所示4. 应用拦截器和网络拦截器有什么区别?从整个责任链路来看应用拦截器是最先执行的拦截器也就是用户自己设置request属性后的原始请求而网络拦截器位于ConnectInterceptor和CallServerInterceptor之间此时网络链路已经准备好只等待发送请求数据。它们主要有以下区别首先应用拦截器在RetryAndFollowUpInterceptor和CacheInterceptor之前所以一旦发生错误重试或者网络重定向网络拦截器可能执行多次因为相当于进行了二次请求但是应用拦截器永远只会触发一次。另外如果在CacheInterceptor中命中了缓存就不需要走网络请求了因此会存在短路网络拦截器的情况。其次除了CallServerInterceptor之外每个拦截器都应该至少调用一次realChain.proceed方法。实际上在应用拦截器这层可以多次调用proceed方法本地异常重试或者不调用proceed方法中断但是网络拦截器这层连接已经准备好可且仅可调用一次proceed方法。最后从使用场景看应用拦截器因为只会调用一次通常用于统计客户端的网络请求发起情况而网络拦截器一次调用代表了一定会发起一次网络通信因此通常可用于统计网络链路上传输的数据。5. OKHttp如何复用TCP连接?ConnectInterceptor的主要工作就是负责建立TCP连接建立TCP连接需要经历三次握手四次挥手等操作如果每个HTTP请求都要新建一个TCP消耗资源比较多而Http1.1已经支持keep-alive,即多个Http请求复用一个TCP连接OKHttp也做了相应的优化下面我们来看下OKHttp是怎么复用TCP连接的ConnectInterceptor中查找连接的代码会最终会调用到ExchangeFinder.findConnection方法具体如下# ExchangeFinder
//为承载新的数据流 寻找 连接。寻找顺序是 已分配的连接、连接池、新建连接private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,int pingIntervalMillis, boolean connectionRetryEnabled)throws IOException {synchronized (connectionPool) {// 1.尝试使用 已给数据流分配的连接.例如重定向请求时可以复用上次请求的连接releasedConnection transmitter.connection;result transmitter.connection;if (result null) {// 2. 没有已分配的可用连接就尝试从连接池获取。连接池稍后详细讲解if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {result transmitter.connection;}}}synchronized (connectionPool) {if (newRouteSelection) {//3. 现在有了IP地址再次尝试从连接池获取。可能会因为连接合并而匹配。这里传入了routes上面的传的nullroutes routeSelection.getAll();if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, false)) {foundPooledConnection true;result transmitter.connection;}}// 4.第二次没成功就把新建的连接进行TCP TLS 握手与服务端建立连接. 是阻塞操作result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,connectionRetryEnabled, call, eventListener);synchronized (connectionPool) {// 5. 最后一次尝试从连接池获取注意最后一个参数为true即要求 多路复用http2.0//意思是如果本次是http2.0那么为了保证 多路复用性因为上面的握手操作不是线程安全会再次确认连接池中此时是否已有同样连接if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {// 如果获取到就关闭我们创建里的连接返回获取的连接result transmitter.connection;} else {//最后一次尝试也没有的话就把刚刚新建的连接存入连接池connectionPool.put(result);}}return result;
}
复制代码上面精简了部分代码可以看出连接拦截器使用了5种方法查找连接首先会尝试使用 已给请求分配的连接。已分配连接的情况例如重定向时的再次请求说明上次已经有了连接若没有 已分配的可用连接就尝试从连接池中 匹配获取。因为此时没有路由信息所以匹配条件address一致——host、port、代理等一致且匹配的连接可以接受新的请求。若从连接池没有获取到则传入routes再次尝试获取这主要是针对Http2.0的一个操作,Http2.0可以复用square.com与square.ca的连接若第二次也没有获取到就创建RealConnection实例进行TCP TLS握手与服务端建立连接。此时为了确保Http2.0连接的多路复用性会第三次从连接池匹配。因为新建立的连接的握手过程是非线程安全的所以此时可能连接池新存入了相同的连接。第三次若匹配到就使用已有连接释放刚刚新建的连接若未匹配到则把新连接存入连接池并返回。以上就是连接拦截器尝试复用连接的操作流程图如下6. OKHttp空闲连接如何清除?上面说到我们会建立一个TCP连接池但如果没有任务了空闲的连接也应该及时清除OKHttp是如何做到的呢? # RealConnectionPoolprivateval cleanupQueue: TaskQueue taskRunner.newQueue()privateval cleanupTask object : Task($okHttpName ConnectionPool) {overridefunrunOnce(): Long cleanup(System.nanoTime())}long cleanup(long now) {int inUseConnectionCount 0;//正在使用的连接数int idleConnectionCount 0;//空闲连接数RealConnection longestIdleConnection null;//空闲时间最长的连接long longestIdleDurationNs Long.MIN_VALUE;//最长的空闲时间//遍历连接找到待清理的连接, 找到下一次要清理的时间还未到最大空闲时间synchronized (this) {for (IteratorRealConnection i connections.iterator(); i.hasNext(); ) {RealConnection connection i.next();//若连接正在使用continue正在使用连接数1if (pruneAndGetAllocationCount(connection, now) 0) {inUseConnectionCount;continue;}//空闲连接数1idleConnectionCount;// 赋值最长的空闲时间和对应连接long idleDurationNs now - connection.idleAtNanos;if (idleDurationNs longestIdleDurationNs) {longestIdleDurationNs idleDurationNs;longestIdleConnection connection;}}//若最长的空闲时间大于5分钟 或 空闲数 大于5就移除并关闭这个连接if (longestIdleDurationNs this.keepAliveDurationNs|| idleConnectionCount this.maxIdleConnections) {connections.remove(longestIdleConnection);} elseif (idleConnectionCount 0) {// else就返回 还剩多久到达5分钟然后wait这个时间再来清理return keepAliveDurationNs - longestIdleDurationNs;} elseif (inUseConnectionCount 0) {//连接没有空闲的就5分钟后再尝试清理.return keepAliveDurationNs;} else {// 没有连接不清理cleanupRunning false;return -1;}}//关闭移除的连接closeQuietly(longestIdleConnection.socket());//关闭移除后 立刻 进行下一次的 尝试清理return0;}
复制代码思路还是很清晰的在将连接加入连接池时就会启动定时任务有空闲连接的话如果最长的空闲时间大于5分钟 或 空闲数 大于5就移除关闭这个最长空闲连接如果 空闲数 不大于5 且 最长的空闲时间不大于5分钟就返回到5分钟的剩余时间然后等待这个时间再来清理。没有空闲连接就等5分钟后再尝试清理。没有连接不清理。流程如下图所示7. OKHttp有哪些优点?使用简单在设计时使用了外观模式将整个系统的复杂性给隐藏起来将子系统接口通过一个客户端OkHttpClient统一暴露出来。扩展性强可以通过自定义应用拦截器与网络拦截器完成用户各种自定义的需求功能强大支持Spdy、Http1.X、Http2、以及WebSocket等多种协议通过连接池复用底层TCP(Socket)减少请求延时无缝的支持GZIP减少数据流量支持数据缓存,减少重复的网络请求支持请求失败自动重试主机的其他ip自动重定向8. OKHttp框架中用到了哪些设计模式?构建者模式OkHttpClient与Request的构建都用到了构建者模式外观模式 OkHttp使用了外观模式,将整个系统的复杂性给隐藏起来将子系统接口通过一个客户端OkHttpClient统一暴露出来。责任链模式: OKHttp的核心就是责任链模式通过5个默认拦截器构成的责任链完成请求的配置享元模式: 享元模式的核心即池中复用,OKHttp复用TCP连接时用到了连接池同时在异步请求中也用到了线程池总结本文主要梳理了OKHttp原理相关知识点并回答了以下问题:OKHttp请求的整体流程是怎样的?OKHttp分发器是怎样工作的?OKHttp拦截器是如何工作的?应用拦截器和网络拦截器有什么区别?OKHttp如何复用TCP连接?OKHttp空闲连接如何清除?OKHttp有哪些优点?OKHttp框架中用到了哪些设计模式?