Android高工面试被问OKHTTP内核解析,我慌了!(1),2024年最新大厂面试经验分享

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

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

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

  1. /** 准备执行的请求 */

  2. private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  3. /** 正在执行的异步请求,包含已经取消但未执行完的请求 */

  4. private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  5. /** 正在执行的同步请求,包含已经取消单未执行完的请求 */

  6. private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

在OkHttp,使用如下构造了单例线程池

  1. public synchronized ExecutorService executorService() {

  2. if (executorService == null) {

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

  4. new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

  5. }

  6. return executorService;

  7. }

构造一个线程池ExecutorService:

  1. executorService = new ThreadPoolExecutor(

  2. //corePoolSize 最小并发线程数,如果是0的话,空闲一段时间后所有线程将全部被销毁

  3. 0,

  4. //maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理

  5. Integer.MAX_VALUE,

  6. //keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间

  7. 60,

  8. //单位秒

  9. TimeUnit.SECONDS,

  10. //工作队列,先进先出

  11. new SynchronousQueue<Runnable>(),

  12. //单个线程的工厂

  13. Util.threadFactory("OkHttp Dispatcher", false));

可以看出,在Okhttp中,构建了一个核心为[0, Integer.MAX_VALUE]的线程池,它不保留任何最小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列,一个叫做”OkHttp Dispatcher”的线程工厂。

也就是说,在实际运行中,当收到10个并发请求时,线程池会创建十个线程,当工作完成后,线程池会在60s后相继关闭所有线程。

  1. synchronized void enqueue(AsyncCall call) {

  2. if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {

  3. runningAsyncCalls.add(call);

  4. executorService().execute(call);

  5. } else {

  6. readyAsyncCalls.add(call);

  7. }

  8. }

从上述源码分析,如果当前还能执行一个并发请求,则加入 runningAsyncCalls ,立即执行,否则加入 readyAsyncCalls 队列。

Dispatcher线程池总结

1)调度线程池Disptcher实现了高并发,低阻塞的实现 2)采用Deque作为缓存,先进先出的顺序执行 3)任务在try/finally中调用了finished函数,控制任务队列的执行顺序,而不是采用锁,减少了编码复杂性提高性能

这里是分析OkHttp源码,并不详细讲线程池原理,如对线程池不了解请参考如下链接

点我,线程池原理,在文章性能优化最后有视频对线程池原理讲解

  1. try {

  2. Response response = getResponseWithInterceptorChain();

  3. if (retryAndFollowUpInterceptor.isCanceled()) {

  4. signalledCallback = true;

  5. responseCallback.onFailure(RealCall.this, new IOException("Canceled"));

  6. } else {

  7. signalledCallback = true;

  8. responseCallback.onResponse(RealCall.this, response);

  9. }

  10. } finally {

  11. client.dispatcher().finished(this);

  12. }

当任务执行完成后,无论是否有异常,finally代码段总会被执行,也就是会调用Dispatcher的finished函数

  1. void finished(AsyncCall call) {

  2. finished(runningAsyncCalls, call, true);

  3. }

从上面的代码可以看出,第一个参数传入的是正在运行的异步队列,第三个参数为true,下面再看有是三个参数的finished方法:

  1. private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {

  2. int runningCallsCount;

  3. Runnable idleCallback;

  4. synchronized (this) {

  5. if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");

  6. if (promoteCalls) promoteCalls();

  7. runningCallsCount = runningCallsCount();

  8. idleCallback = this.idleCallback;

  9. }

  10. if (runningCallsCount == 0 && idleCallback != null) {

  11. idleCallback.run();

  12. }

  13. }

打开源码,发现它将正在运行的任务Call从队列runningAsyncCalls中移除后,获取运行数量判断是否进入了Idle状态,接着执行promoteCalls()函数,下面是promoteCalls()方法:

  1. private void promoteCalls() {

  2. if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.

  3. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

  4. for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {

  5. AsyncCall call = i.next();

  6. if (runningCallsForHost(call) < maxRequestsPerHost) {

  7. i.remove();

  8. runningAsyncCalls.add(call);

  9. executorService().execute(call);

  10. }

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

  12. }

  13. }

主要就是遍历等待队列,并且需要满足同一主机的请求小于maxRequestsPerHost时,就移到运行队列中并交给线程池运行。就主动的把缓存队列向前走了一步,而没有使用互斥锁等复杂编码

核心重点getResponseWithInterceptorChain方法

  1. Response getResponseWithInterceptorChain() throws IOException {

  2. // Build a full stack of interceptors.

  3. List<Interceptor> interceptors = new ArrayList<>();

  4. interceptors.addAll(client.interceptors());

  5. interceptors.add(retryAndFollowUpInterceptor);

  6. interceptors.add(new BridgeInterceptor(client.cookieJar()));

  7. interceptors.add(new CacheInterceptor(client.internalCache()));

  8. interceptors.add(new ConnectInterceptor(client));

  9. if (!forWebSocket) {

  10. interceptors.addAll(client.networkInterceptors());

  11. }

  12. interceptors.add(new CallServerInterceptor(forWebSocket));

  13. Interceptor.Chain chain = new RealInterceptorChain(

  14. interceptors, null, null, null, 0, originalRequest);

  15. return chain.proceed(originalRequest);

  16. }

1)在配置 OkHttpClient 时设置的 interceptors; 2)负责失败重试以及重定向的 RetryAndFollowUpInterceptor; 3)负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor; 4)负责读取缓存直接返回、更新缓存的 CacheInterceptor; 5)负责和服务器建立连接的 ConnectInterceptor; 6)配置 OkHttpClient 时设置的 networkInterceptors; 7)负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。

OkHttp的这种拦截器链采用的是责任链模式,这样的好处是将请求的发送和处理分开,并且可以动态添加中间的处理方实现对请求的处理、短路等操作。

从上述源码得知,不管okhttp有多少拦截器最后都会走,如下方法:

  1. Interceptor.Chain chain = new RealInterceptorChain(

  2. interceptors, null, null, null, 0, originalRequest);

  3. return chain.proceed(originalRequest);

从方法名字基本可以猜到是干嘛的,调用 chain.proceed(originalRequest); 将request传递进来,从拦截器链里拿到返回结果。那么拦截器Interceptor是干嘛的,Chain是干嘛的呢?继续往下看RealInterceptorChain

RealInterceptorChain类

下面是RealInterceptorChain的定义,该类实现了Chain接口,在getResponseWithInterceptorChain调用时好几个参数都传的null。

  1. public final class RealInterceptorChain implements Interceptor.Chain {

  2. public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,

  3. HttpCodec httpCodec, RealConnection connection, int index, Request request) {

  4. this.interceptors = interceptors;

  5. this.connection = connection;

  6. this.streamAllocation = streamAllocation;

  7. this.httpCodec = httpCodec;

  8. this.index = index;

  9. this.request = request;

  10. }

  11. ......

  12. @Override

  13. public Response proceed(Request request) throws IOException {

  14. return proceed(request, streamAllocation, httpCodec, connection);

  15. }

  16. public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,

  17. RealConnection connection) throws IOException {

  18. if (index >= interceptors.size()) throw new AssertionError();

  19. calls++;

  20. ......

  21. // Call the next interceptor in the chain.

  22. RealInterceptorChain next = new RealInterceptorChain(

  23. interceptors, streamAllocation, httpCodec, connection, index + 1, request);

  24. Interceptor interceptor = interceptors.get(index);

  25. Response response = interceptor.intercept(next);

  26. ......

  27. return response;

  28. }

  29. protected abstract void execute();

  30. }

主要看proceed方法,proceed方法中判断index(此时为0)是否大于或者等于client.interceptors(List )的大小。由于httpStream为null,所以首先创建next拦截器链,主需要把索引置为index+1即可;然后获取第一个拦截器,调用其intercept方法。

Interceptor 代码如下:

  1. public interface Interceptor {

  2. Response intercept(Chain chain) throws IOException;

  3. interface Chain {

  4. Request request();

  5. Response proceed(Request request) throws IOException;

  6. Connection connection();

  7. }

  8. }

BridgeInterceptor

BridgeInterceptor从用户的请求构建网络请求,然后提交给网络,最后从网络响应中提取出用户响应。从最上面的图可以看出,BridgeInterceptor实现了适配的功能。下面是其intercept方法:

  1. public final class BridgeInterceptor implements Interceptor {

  2. ......

  3. @Override

  4. public Response intercept(Chain chain) throws IOException {

  5. Request userRequest = chain.request();

  6. Request.Builder requestBuilder = userRequest.newBuilder();

  7. RequestBody body = userRequest.body();

  8. //如果存在请求主体部分,那么需要添加Content-Type、Content-Length首部

  9. if (body != null) {

  10. MediaType contentType = body.contentType();

  11. if (contentType != null) {

  12. requestBuilder.header("Content-Type", contentType.toString());

  13. }

  14. long contentLength = body.contentLength();

  15. if (contentLength != -1) {

  16. requestBuilder.header("Content-Length", Long.toString(contentLength));

  17. requestBuilder.removeHeader("Transfer-Encoding");

  18. } else {

  19. requestBuilder.header("Transfer-Encoding", "chunked");

  20. requestBuilder.removeHeader("Content-Length");

  21. }

  22. }

  23. if (userRequest.header("Host") == null) {

  24. requestBuilder.header("Host", hostHeader(userRequest.url(), false));

  25. }

  26. if (userRequest.header("Connection") == null) {

  27. requestBuilder.header("Connection", "Keep-Alive");

  28. }

  29. // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing

  30. // the transfer stream.

  31. boolean transparentGzip = false;

  32. if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {

  33. transparentGzip = true;

  34. requestBuilder.header("Accept-Encoding", "gzip");

  35. }

  36. List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());

  37. if (!cookies.isEmpty()) {

  38. requestBuilder.header("Cookie", cookieHeader(cookies));

  39. }

  40. if (userRequest.header("User-Agent") == null) {

  41. requestBuilder.header("User-Agent", Version.userAgent());

  42. }

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

  44. HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

  45. Response.Builder responseBuilder = networkResponse.newBuilder()

  46. .request(userRequest);

  47. if (transparentGzip

  48. && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))

  49. && HttpHeaders.hasBody(networkResponse)) {

  50. GzipSource responseBody = new GzipSource(networkResponse.body().source());

  51. Headers strippedHeaders = networkResponse.headers().newBuilder()

  52. .removeAll("Content-Encoding")

  53. .removeAll("Content-Length")

  54. .build();

  55. responseBuilder.headers(strippedHeaders);

  56. responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));

  57. }

  58. return responseBuilder.build();

  59. }

  60. /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */

  61. private String cookieHeader(List<Cookie> cookies) {

  62. StringBuilder cookieHeader = new StringBuilder();

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

  64. if (i > 0) {

  65. cookieHeader.append("; ");

  66. }

  67. Cookie cookie = cookies.get(i);

  68. cookieHeader.append(cookie.name()).append('=').append(cookie.value());

  69. }

  70. return cookieHeader.toString();

  71. }

  72. }

从上面的代码可以看出,首先获取原请求,然后在请求中添加头,比如Host、Connection、Accept-Encoding参数等,然后根据看是否需要填充Cookie,在对原始请求做出处理后,使用chain的procced方法得到响应,接下来对响应做处理得到用户响应,最后返回响应。接下来再看下一个拦截器ConnectInterceptor的处理。

  1. public final class ConnectInterceptor implements Interceptor {

  2. ......

  3. @Override

  4. public Response intercept(Chain chain) throws IOException {

  5. RealInterceptorChain realChain = (RealInterceptorChain) chain;

  6. Request request = realChain.request();

  7. StreamAllocation streamAllocation = realChain.streamAllocation();

  8. // We need the network to satisfy this request. Possibly for validating a conditional GET.

  9. boolean doExtensiveHealthChecks = !request.method().equals("GET");

  10. HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);

  11. RealConnection connection = streamAllocation.connection();

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

  13. }

  14. }

实际上建立连接就是创建了一个 HttpCodec 对象,它利用 Okio 对 Socket 的读写操作进行封装,Okio 以后有机会再进行分析,现在让我们对它们保持一个简单地认识:它对 java.io 和 java.nio 进行了封装,让我们更便捷高效的进行 IO 操作。

CallServerInterceptor

CallServerInterceptor是拦截器链中最后一个拦截器,负责将网络请求提交给服务器。它的intercept方法实现如下:

  1. @Override

  2. public Response intercept(Chain chain) throws IOException {

  3. RealInterceptorChain realChain = (RealInterceptorChain) chain;

  4. HttpCodec httpCodec = realChain.httpStream();

  5. StreamAllocation streamAllocation = realChain.streamAllocation();

  6. RealConnection connection = (RealConnection) realChain.connection();

  7. Request request = realChain.request();

  8. long sentRequestMillis = System.currentTimeMillis();

  9. httpCodec.writeRequestHeaders(request);

  10. Response.Builder responseBuilder = null;

  11. if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {

  12. // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100

  13. // Continue" response before transmitting the request body. If we don't get that, return what

  14. // we did get (such as a 4xx response) without ever transmitting the request body.

  15. if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {

  16. httpCodec.flushRequest();

  17. responseBuilder = httpCodec.readResponseHeaders(true);

  18. }

  19. if (responseBuilder == null) {

  20. // Write the request body if the "Expect: 100-continue" expectation was met.

  21. Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());

  22. BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

  23. request.body().writeTo(bufferedRequestBody);

  24. bufferedRequestBody.close();

  25. } else if (!connection.isMultiplexed()) {

  26. // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from

  27. // being reused. Otherwise we're still obligated to transmit the request body to leave the

  28. // connection in a consistent state.

  29. streamAllocation.noNewStreams();

  30. }

  31. }

  32. httpCodec.finishRequest();

  33. if (responseBuilder == null) {

  34. responseBuilder = httpCodec.readResponseHeaders(false);

  35. }

  36. Response response = responseBuilder

  37. .request(request)

  38. .handshake(streamAllocation.connection().handshake())

  39. .sentRequestAtMillis(sentRequestMillis)

  40. .receivedResponseAtMillis(System.currentTimeMillis())

  41. .build();

  42. int code = response.code();

  43. if (forWebSocket && code == 101) {

  44. // Connection is upgrading, but we need to ensure interceptors see a non-null response body.

  45. response = response.newBuilder()

  46. .body(Util.EMPTY_RESPONSE)

  47. .build();

  48. } else {

  49. response = response.newBuilder()

  50. .body(httpCodec.openResponseBody(response))

  51. .build();

  52. }

  53. if ("close".equalsIgnoreCase(response.request().header("Connection"))

  54. || "close".equalsIgnoreCase(response.header("Connection"))) {

  55. streamAllocation.noNewStreams();

  56. }

  57. if ((code == 204 || code == 205) && response.body().contentLength() > 0) {

  58. throw new ProtocolException(

  59. "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());

文末

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家

这里笔者分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

【视频教程】

天道酬勤,只要你想,大厂offer并不是遥不可及!希望本篇文章能为你带来帮助,如果有问题,请在评论区留言。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

eBody(response))`

  1. .build();

  2. }

  3. if ("close".equalsIgnoreCase(response.request().header("Connection"))

  4. || "close".equalsIgnoreCase(response.header("Connection"))) {

  5. streamAllocation.noNewStreams();

  6. }

  7. if ((code == 204 || code == 205) && response.body().contentLength() > 0) {

  8. throw new ProtocolException(

  9. "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());

文末

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家

这里笔者分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

[外链图片转存中…(img-ge78jb5R-1713050118881)]

[外链图片转存中…(img-c39tO8Wa-1713050118881)]

【视频教程】

[外链图片转存中…(img-T7mxjbVJ-1713050118882)]

天道酬勤,只要你想,大厂offer并不是遥不可及!希望本篇文章能为你带来帮助,如果有问题,请在评论区留言。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-gs9KiM2f-1713050118882)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值