Android源码-一文带你搞懂OkHttp,2021Android高级面试题及答案

Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException(“Canceled”));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}

队列机制

Dispathcer中维护了3个队列,分别为异步等待队列、异步执行队列、同步执行队列。

/** Ready async calls in the order they’ll be run. */
private final Deque readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven’t finished yet. */
private final Deque runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven’t finished yet. */
private final Deque runningSyncCalls = new ArrayDeque<>();

不管是同步还是异步,最终在finally块都会调用dispatcher的finished方法,会移除掉该队列任务,最后实现如下

int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError(“Call wasn’t in-flight!”);
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}

if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}

在finish中会再调用promoteCalls方法,会重新检索准备中的队列,将队列加入到线程中

private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

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

if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}

if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}

OkHttp的拦截器及调用链是怎么执行?

调用链执行流程

通过上述的分析,我们知道不管同步还是异步,最终调用到的都是RealCall的getResponseWithInterceptorChain()方法,如下:

Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);
}

其中定义了拦截器集合及RealInterceptorChain拦截链,具体执行了拦截链的proceed方法,如下:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();

calls++;

// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)

  • " must retain the same host and port");
    }

// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)

  • " must call proceed() exactly once");
    }

// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);

// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor

  • " must call proceed() exactly once");
    }

// Confirm that the intercepted response isn’t null.
if (response == null) {
throw new NullPointerException(“interceptor " + interceptor + " returned null”);
}

if (response.body() == null) {
throw new IllegalStateException(
“interceptor " + interceptor + " returned a response with no body”);
}

return response;
}

  1. 先判断是否超过list的size,如果超过则遍历结束,如果没有超过则继续执行
  2. calls+1
  3. new了一个RealInterceptorChain,其中然后下标index+1
  4. 从list取出下一个interceptor对象
  5. 执行interceptor的intercept方法

总结一下就是每一个RealInterceptorChain对应一个interceptor,然后每一个interceptor再产生下一个RealInterceptorChain,直到List迭代完成。

拦截器

从上面的调用关系可以看出除了红色圈出的拦截器之外都是系统提供的拦截器,这整个过程是递归的执行过程,在 CallServerInterceptor 中得到最终的 Response 之后,将 response 按递归逐级进行返回,期间会经过 NetworkInterceptor 最后到达 Application Interceptor 。

OkHttp是如何进行数据缓存?

缓存策略

OkHttp使用了CacheInterceptor拦截器进行数据缓存的控制使用了CacheStrategy实现了上面的流程图,它根据之前缓存的结果与当前将要发送Request的header进行策略,并得出是否进行请求的结果。根据输出的networkRequest和cacheResponse的值是否为null给出不同的策略,如下:

ne

tworkRequest cacheResponse result 结果 null null only-if-cached (表明不进行网络请求,且缓存不存在或者过期,一定会返回503错误) null non-null 不进行网络请求,直接返回缓存,不请求网络 non-null null 需要进行网络请求,而且缓存不存在或者过去,直接访问网络 non-null non-null Header中包含ETag/Last-Modified标签,需要在满足条件下请求,还是需要访问网络

缓存算法

通过分析CacheInterceptor拦截器的intercept方法,我们可以发现具体的缓存都是使用了Cache类进行,最后具体的实现在DiskLruCache类中。缓存实际上是一个比较复杂的逻辑,单独的功能块,实际上不属于OKhttp上的功能,实际上是通过是http协议和DiskLruCache做了处理。LinkedHashMap可以实现LRU算法,并且在这个case里,它被用作对DiskCache的内存索引

OkHttp的连接池复用机制是怎么样?

链路

RealConnection是Connection的实现类,代表着链接socket的链路,如果拥有了一个RealConnection就代表了我们已经跟服务器有了一条通信链路,而且通过 RealConnection代表是连接socket链路,RealConnection对象意味着我们已经跟服务端有了一条通信链路。 另外StreamAllocation类为流的桥梁,在RetryAndFollowUpInterceptor中进行初始化,在ConnectInterceptor中进行newStream操作,具体的连接拦截器代码如下:

public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();

// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals(“GET”);
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();

return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

newStream创建留最后会调用到findConnection方法,这里面是连接复用的关键,如果再连接池中找到能复用的连接,则直接返回。 否则将RealConnection加入到链接池ConnectionPool中,具体代码如下:

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (released) throw new IllegalStateException(“released”);
if (codec != null) throw new IllegalStateException(“codec != null”);
if (canceled) throw new IOException(“Canceled”);

// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new streams.
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// We had an already-allocated connection and it’s good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don’t report it as released!
releasedConnection = null;
}

if (result == null) {
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);

if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we’re done.
return result;
}

// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}

synchronized (connectionPool) {
if (canceled) throw new IOException(“Canceled”);

if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
List routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}

if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}

// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we’re about to do.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);

最后

以前一直是自己在网上东平西凑的找,找到的东西也是零零散散,很多时候都是看着看着就没了,时间浪费了,问题却还没得到解决,很让人抓狂。

后面我就自己整理了一套资料,还别说,真香!

资料有条理,有系统,还很全面,我不方便直接放出来,大家可以先看看有没有用得到的地方吧。

系列教程图片

2020Android复习资料汇总.png

flutter

NDK

设计思想开源框架

微信小程序
很全面,我不方便直接放出来,大家可以先看看有没有用得到的地方吧。

[外链图片转存中…(img-4K8eKOJ8-1647757792100)]

[外链图片转存中…(img-dOleLqoL-1647757792101)]

[外链图片转存中…(img-5ffB14CX-1647757792101)]

[外链图片转存中…(img-Ql0iN9Ze-1647757792102)]

[外链图片转存中…(img-psxLwc2v-1647757792102)]

[外链图片转存中…(img-Z92ev9wk-1647757792103)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值