Android 网络框架之OkHttp源码解析


Response,顾名思义,为请求的返回体;

那么这个类里面究竟实现了什么逻辑呢?

先来看一下成员变量:

从图片可以看出,Response和Request有点类似,封装了一些参数返回,比如code,message,headers,body等等;

在来看一下这张图片:

也是定义了参数的赋值与获取,这里就不再赘述;

Response类里面主要定义了服务器返回的相关信息,这里面也没有其他的复杂逻辑,把它当成一个bean类来理解即可;

4,ReallCall


ReallCall,顾名思义,我们可以先理解为真正的请求,那么我们接下来来看源码求证一下,看看是不是真的是这样;

首先,先来看一下参数:

从图片可以看出,只有几个参数,都是在构造方法初始化的;

这里面初始化了Transmitter,这个我们后面再讲;

那么我们再来看一下它这里面有哪些方法:

这个几个方法有没有熟悉的感觉,这里其实是通过桥接设计模式,实际调用的是OkHttpClient和Transmitter的函数;

这几个方法是网络请求的最核心的方法,execute()方法实际上是同步请求,在当前线程就触发了网络请求,通过getResponseWithInterceptorChain()来执行网络请求从而获取到Response返回结果;

而enqueue(Callback responseCallback)方法,是在子线程中执行的,通过创建一个AsyncCall,传递给client.dispatcher(),这里面主要是线程的执行逻辑;

这个AsyncCall是实现了Runnable接口,具体执行是在AsyncCall的execute()方法里面;

我们来看一下AsyncCall的execute()方法具体实现逻辑;

这里面主要有两个方法,一个是executeOn(),通过入参传递一个线程池,来执行当前线程,另一个方法是execute(),为异步的具体实现,让我们来看看这个方法做了啥?

和上面同步调用的逻辑一样,也是通过getResponseWithInterceptorChain()来执行网络请求从而获取到Response返回结果;

还有这个方法getResponseWithInterceptorChain(),通过拦截器+责任链设计模式来实现网络请求,这个我们下面再介绍,这个只要了解是通过这个来进行网络请求的即可;

小结: ReallCall的职责是进行真正的网络请求,里面封装了两个方法,一个是同步请求execute(),一个是异步请求enqueue(),还有最后也是最重要的一个方法getResponseWithInterceptorChain(),通过这个方法来执行网络请求;

5、Dispatcher


我们在看ReallCall源码的时候,频频见到这个方法,client.dispatcher().调用,这个方法的真面目就是Dispatcher,这个Dispatcher是在创建OkHttpClient的时候进行初始化的;

接下来我们来看看这个类的职责是做什么的;

老规矩,先来看看成员变量:

参数不多,我们来一个个介绍:

  • maxRequests:最多存在64个请求;

  • maxRequestsPerHost:每个主机最多同时请求数为5;

  • idleCallback:程序空闲时的回调;

  • executorService:线程池;

  • readyAsyncCalls:即将要进行的异步请求;

  • runningAsyncCalls:正在进行的异步请求;

  • runningSyncCalls:正在进行的同步请求;

下面来讲一讲这个类几个比较重要的方法;

(1)线程池的创建:

public synchronized ExecutorService executorService() {

if (executorService == null) {

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,

new SynchronousQueue<>(), Util.threadFactory(“OkHttp Dispatcher”, false));

}

return executorService;

}

核心线程为0,每隔60秒会清空空闲的线程,而最大线程无限制,但是已经通过成员变量来进行控制了,没啥影响;

(2)同步请求的执行:

同步请求的执行是这个方法,具体调用是在ReallCall的excute()方法里面,在Dispatcher先通过调用executed(),后面再调用finish()方法来移除同步请求;

来看一下这个finish()方法;

private void finished(Deque calls, T call) {

Runnable idleCallback;

synchronized (this) {

// 移除队列的请求

if (!calls.remove(call)) throw new AssertionError(“Call wasn’t in-flight!”);

idleCallback = this.idleCallback;

}

// 执行请求

boolean isRunning = promoteAndExecute();

if (!isRunning && idleCallback != null) {

// 触发空闲线程执行

idleCallback.run();

}

}

这个方法的逻辑很简单,先移除队列里的call,然后再调用promoteAndExecute()执行已经准备好执行的请求,这个逻辑我们下面再讲;

(3)异步请求的执行:

void enqueue(AsyncCall call) {

synchronized (this) {

// 添加请求到异步队列;

readyAsyncCalls.add(call);

if (!call.get().forWebSocket) {

// 判断当前请求是否已经存在

AsyncCall existingCall = findExistingCallWithHost(call.host());

// 如果当前请求已经存在,则复用之前的线程计数,不进行递增;

if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);

}

}

// 执行请求

promoteAndExecute();

}

这个方法里面,先将请求添加到异步队列readyAsyncCalls里面,然后再调用promoteAndExecute()方法来触发请求,下面我们来看看promoteAndExecute()的逻辑;

private boolean promoteAndExecute() {

assert (!Thread.holdsLock(this));

List executableCalls = new ArrayList<>();

boolean isRunning;

synchronized (this) {

// 1、遍历准备要执行的请求队列

for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {

AsyncCall asyncCall = i.next();

// 2、判断当前正在执行的请求个数大于最大请求个数时,则取消请求

if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.

// 3、判断当前主机的连接数超过5个时,则跳过当前请求;

if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

i.remove();

asyncCall.callsPerHost().incrementAndGet();

executableCalls.add(asyncCall);

// 添加请求到正在执行的队列中

runningAsyncCalls.add(asyncCall);

}

isRunning = runningCallsCount() > 0;

}

for (int i = 0, size = executableCalls.size(); i < size; i++) {

AsyncCall asyncCall = executableCalls.get(i);

// 执行请求;

asyncCall.executeOn(executorService());

}

return isRunning;

}

这个promoteAndExecute()方法的主要逻辑是执行readyAsyncCalls队列里的请求,而maxRequests和maxRequestsPerHost也是在这里处理的;

除此之后,这个类里面还有一些查询的方法,比如queuedCalls(),runningCalls()等,不过这些不是重点,了解即可;

6、Interceptor


拦截器,可以说是OkHttp最重要的部分了,这一部分通过一个很巧妙的设计,将复杂的网络请求逻辑分散到每个拦截器中,这种设计模式就叫做责任链

责任链模式的优点:降低耦合度,简化对象,增强对象的灵活性;

缺点:性能会受到一定的影响;

那么对于网络请求,最简单的实现就是从0到1,也就是我直接请求,不考虑异常因素,保证每次请求都能成功,那么这个实现就很简单,只需要调用请求网络的api进行请求数据即可;

但是现实往往是残酷的,网络的环境极其复杂,而每一次的请求也不一定能返回,所以我们需要使用各种策略来保证网络请求可以正常完成,比如重试,缓存等操作来保证网络请求的正常使用;

而OkHttp的网络请求通过责任链设计了几个拦截器,巧妙的通过责任链模式来处理复杂的网络请求,避免了类的臃肿并且提供了很好的扩展性,缺点就是当责任链上的对象过多时,可能会出现性能的问题;

那么接下来我们来看看OkHttp的拦截器的实现吧;

OkHttp的拦截器有:

  • RetryAndFollowUpInterceptor:失败和重定向拦截器;

  • BridgeInterceptor:封装Response的拦截器;

  • CacheInterceptor:缓存处理相关的拦截器;

  • ConnectInterceptor:连接服务的拦截器,真正的网络请求在这里实现;

  • CallServerInterceptor:负责写请求和读响应的拦截器;

下面我们来一个个具体分析;

6.1、RetryAndFollowUpInterceptor

public Response intercept(Chain chain) throws IOException {

Request request = chain.request();

RealInterceptorChain realChain = (RealInterceptorChain) chain;

Transmitter transmitter = realChain.transmitter();

int followUpCount = 0;

Response priorResponse = null;

while (true) {

// 准备连接请求

transmitter.prepareToConnect(request);

Response response;

boolean success = false;

// 执行其他拦截器的功能,获取Response;

response = realChain.proceed(request, transmitter, null);

// 根据Response的返回码来判断要执行重试还是重定向;

Request followUp = followUpRequest(response, route);

if (followUp == null) {

// 如果followUpRequest返回的Request为空,那边就表示不需要执行重试或者重定向,直接返回数据;

return response;

}

RequestBody followUpBody = followUp.body();

if (followUpBody != null && followUpBody.isOneShot()) {

// 如果followUp为null,请求体不为空,并且只需要请求一次时,那么就返回response;

return response;

}

// 判断重试或者重定向的次数是否超过最大的次数,是的话则抛出异常;

if (++followUpCount > MAX_FOLLOW_UPS) {

throw new ProtocolException("Too many follow-up requests: " + followUpCount);

}

// 将需要重试或者重定向的请求赋值给新的请求;

request = followUp;

}

}

followUpRequest方法的逻辑我们大概瞄一眼,就是根据返回码来做一下操作;

6.2、BridgeInterceptor

桥接拦截器,这里主要做网络请求的封装,用于简化应用层的逻辑,比如网络请求需要传"Transfer-Encoding",“Accept-Encoding”,“User-Agent”,"Cookie"这些参数,但是应用层不需要关心这些,那么就由这个拦截器来做这些封装;

当请求完成之后,也会对Response的header做一下封装处理,返回给应用层,这样应用层就不需要关心这个header的细节,简化操作;

public Response intercept(Chain chain) throws IOException {

if (body != null) {

MediaType contentType = body.contentType();

if (contentType != null) {

requestBuilder.header(“Content-Type”, contentType.toString());

}

// 处理封装"Content-Length",“Transfer-Encoding”,“Host”,“Connection”,“Accept-Encoding”,“Cookie”,"User-Agent"等请求头;

// 执行后续的拦截器的逻辑

Response networkResponse = chain.proceed(requestBuilder.build());

// 获取返回体的Builder

Response.Builder responseBuilder = networkResponse.newBuilder()

.request(userRequest);

//处理返回的Response的"Content-Encoding"、“Content-Length”、"Content-Type"等返回头;

return responseBuilder.build();

}

6.3、CacheInterceptor

CacheInterceptor是缓存处理的拦截器,我们先来看一下这个拦截器的逻辑;

public Response intercept(Chain chain) throws IOException {

// 先获取候选缓存,前提是有配置缓存,也就是cache不为空;

Response cacheCandidate = cache != null

? cache.get(chain.request())
null;

long now = System.currentTimeMillis();

// 获取缓存策略;

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

// 缓存策略的请求类

Request networkRequest = strategy.networkRequest;

// 获取的本地的缓存;

Response cacheResponse = strategy.cacheResponse;

// 如果被禁止使用网络数据且缓存数据为空,那么返回一个504的Response,并且body为空;

if (networkRequest == null && cacheResponse == null) {

return new Response.Builder()

.build();

}

// 如果不需要使用网络数据,那么就直接返回缓存的数据;

if (networkRequest == null) {

return cacheResponse.newBuilder()

.cacheResponse(stripBody(cacheResponse))

.build();

}

// 执行后续的拦截器逻辑;

Response networkResponse = null;

try {

networkResponse = chain.proceed(networkRequest);

} finally {

}

if (cacheResponse != null) {

// 如果缓存数据不为空并且code为304,表示数据没有变化,继续使用缓存数据;

if (networkResponse.code() == HTTP_NOT_MODIFIED) {

Response response = cacheResponse.newBuilder().xx.build();

// 更新缓存数据

cache.update(cacheResponse, response);

return response;

}

}

// 获取网络返回的response

Response response = networkResponse.newBuilder()

.cacheResponse(stripBody(cacheResponse))

.networkResponse(stripBody(networkResponse))

.build();

// 将网络数据保存到缓存中;

CacheRequest cacheRequest = cache.put(response);

return cacheWritingResponse(cacheRequest, response);

if (HttpMethod.invalidatesCache(networkRequest.method())) {

try {

// 缓存失效,那么就移除缓存

cache.remove(networkRequest);

} catch (IOException ignored) {

// The cache cannot be written.

}

}

}

return response;

}

在这个获取缓存策略这一步,会生成一个CacheStrategy对象,用于管理缓存策略,那么在将缓存策略之前,我们先来了解几个概念;

强缓存:在请求数据的时候,查看请求头expires和cache-control是否命中缓存,如果是的话,那么久就会从缓存中获取数据,不会走网络请求;

协商缓存:而协商缓存是在没有命中强缓存的情况下才会走的逻辑,必会走一次网络请求,通过last-modified和etag返回头判断是否命中缓存,如果没有命中,那么就走网络重新获取到数据,协商缓存需要服务器支持才能实现;

那么接下来我们来看一下OkHttp是怎么实现缓存策略逻辑的;

如果对于缓存逻辑不是很清楚的话,可以看一下这篇文章:Android 你不得不学的HTTP相关知识

从上面那个方法,我们来看看new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get()的逻辑;

private CacheStrategy getCandidate() {

// 如果不使用缓存,那么就返回一个空的Response的CacheStrategy;

if (!isCacheable(cacheResponse, request)) {

return new CacheStrategy(request, null);

}

// 强缓存

long ageMillis = cacheResponseAge();

long freshMillis = computeFreshnessLifetime();

// 判断强缓存是否有效,是的话就返回缓存数据;

if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {

Response.Builder builder = cacheResponse.newBuilder();

if (ageMillis + minFreshMillis >= freshMillis) {

builder.addHeader(“Warning”, “110 HttpURLConnection “Response is stale””);

}

long oneDayMillis = 24 * 60 * 60 * 1000L;

if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {

builder.addHeader(“Warning”, “113 HttpURLConnection “Heuristic expiration””);

}

return new CacheStrategy(null, builder.build());

}

// 协商缓存

String conditionName;

String conditionValue;

if (etag != null) {

// etag协商缓存

conditionName = “If-None-Match”;

conditionValue = etag;

} else if (lastModified != null) {

// Last-Modified协商缓存

conditionName = “If-Modified-Since”;

// 最后修改时间

conditionValue = lastModifiedString;

} else if (servedDate != null) {

// Last-Modified协商缓存

conditionName = “If-Modified-Since”;

// 服务器最后修改时间

conditionValue = servedDateString;

} else {

// 没有协商缓存,返回一个空的Response的CacheStrategy;

return new CacheStrategy(request, null); // No condition! Make a regular request.

}

// 设置header

Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();

Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

Request conditionalRequest = request.newBuilder()

.headers(conditionalRequestHeaders.build())

.build();

return new CacheStrategy(conditionalRequest, cacheResponse);

}

6.4、ConnectInterceptor

这个拦截器比较重要,网络的最底层实现都是通过这个类,这里面封装了socket连接和TLS握手等逻辑,接下来我们来看看具体是怎么实现的;

public Response intercept(Chain chain) throws IOException {

// 这简单的一行代码,却实现了无比复杂的网络请求,Exchange用于下一个拦截器CallServerInterceptor进行网络请求使用;

Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

// 执行后续的拦截器逻辑

return realChain.proceed(request, transmitter, exchange);

}

这个类主要实现了以下几个步骤的逻辑:

  • 1:调用transmitter.newExchange方法;

  • 2:通过Transmitter的ExchangeFinder调用了find方法;

  • 3:ExchangeFinder里调用了findHealthyConnection方法;

  • 4:ExchangeFinder里调用了findConnection方法创建了RealConnection;

  • 5:调用了RealConnection的connect方法,实现了TCP + TLS 握手,底层通过socket来实现的;

  • 6: 通过RealConnection的newCodec方法创建了两个协议类,一个是Http1ExchangeCodec,对应着HTTP1.1,一个是Http2ExchangeCodec,对应着HTTP2.0;

这个拦截器主要是实现网络连接的逻辑,而网络请求的逻辑是放在CallServerInterceptor这个拦截器中实现的;

那么上面我们讲完这个拦截器的基本逻辑,下面我们来看看更深层次的知识;

对于连接来说,最简单的实现就是每次需要的时候都进行创建并连接,不需要考虑网络环境,以及资源等等因素,但是现实情况我们不可能这样做,因为如果要追求极致的体验,我们就必须得做优化;

而这里的优化就是连接复用;

下面我们来看看源码是怎么实现复用逻辑的,上面我们了解到创建RealConnection是通过ExchangeFinder的findConnection方法,那么我们来看看这个方法里的具体逻辑;

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,

int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {

synchronized (connectionPool) {

if (result == null) {

// 尝试从缓存池中获取可用的连接

if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {

foundPooledConnection = true;

result = transmitter.connection;

} else if (nextRouteToTry != null) {

// 如果获取不到,那么久从Route里面获取

selectedRoute = nextRouteToTry;

nextRouteToTry = null;

} else if (retryCurrentRoute()) {

// 如果当前的路由是重试的路由,那么就从路由里面获取

selectedRoute = transmitter.connection.route();

}

}

}

if (result != null) {

// 如果获取到连接,那么就直接返回结果

return result;

}

// 如果前面没有获取到连接,那么这里就通过RouteSelector先获取到Route,然后再获取Connection;

boolean newRouteSelection = false;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

结尾

最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

…(img-fvxmrF3M-1712194594762)]

[外链图片转存中…(img-lDId37Zt-1712194594763)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

结尾

最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

[外链图片转存中…(img-FQbrvAqV-1712194594763)]

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

[外链图片转存中…(img-3nFrGriy-1712194594763)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值