来吧,今天说说常用的网络框架OKHttp,也是现在Android所用的原生网络框架(Android 4.4开始,HttpURLConnection的底层实现被Google改成了OkHttp),GOGOGO!

  • OKHttp有哪些拦截器,分别起什么作用
  • OkHttp怎么实现连接池
  • OkHttp里面用到了什么设计模式

OKHttp有哪些拦截器,分别起什么作用

OKHTTP的拦截器是把所有的拦截器放到一个list里,然后每次依次执行拦截器,并且在每个拦截器分成三部分:

  • 预处理拦截器内容
  • 通过proceed方法把请求交给下一个拦截器
  • 下一个拦截器处理完成并返回,后续处理工作。

这样依次下去就形成了一个链式调用,看看源码,具体有哪些拦截器:

  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. }

根据源码可知,一共七个拦截器:

  • addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。
  • RetryAndFollowUpInterceptor,这里会对连接做一些初始化工作,以及请求失败的充实工作,重定向的后续请求工作。跟他的名字一样,就是做重试工作还有一些连接跟踪工作。
  • BridgeInterceptor,这里会为用户构建一个能够进行网络访问的请求,同时后续工作将网络请求回来的响应Response转化为用户可用的Response,比如添加文件类型,content-length计算添加,gzip解包。
  • CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
  • ConnectInterceptor,这里主要就是负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec
  • networkInterceptors,这里也是开发者自己设置的,所以本质上和第一个拦截器差不多,但是由于位置不同,所以用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。
  • CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。

OkHttp怎么实现连接池

  • 为什么需要连接池?

频繁的进行建立Sokcet连接和断开Socket是非常消耗网络资源和浪费时间的,所以HTTP中的keepalive连接对于降低延迟和提升速度有非常重要的作用。keepalive机制是什么呢?也就是可以在一次TCP连接中可以持续发送多份数据而不会断开连接。所以连接的多次使用,也就是复用就变得格外重要了,而复用连接就需要对连接进行管理,于是就有了连接池的概念。

OkHttp中使用ConectionPool实现连接池,默认支持5个并发KeepAlive,默认链路生命为5分钟。

  • 怎么实现的?

1)首先,ConectionPool中维护了一个双端队列Deque,也就是两端都可以进出的队列,用来存储连接。

2)然后在ConnectInterceptor,也就是负责建立连接的拦截器中,首先会找可用连接,也就是从连接池中去获取连接,具体的就是会调用到ConectionPool的get方法。

  1. RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
  2. assert (Thread.holdsLock(this));
  3. for (RealConnection connection : connections) {
  4. if (connection.isEligible(address, route)) {
  5. streamAllocation.acquire(connection, true);
  6. return connection;
  7. }
  8. }
  9. return null;
  10. }

也就是遍历了双端队列,如果连接有效,就会调用acquire方法计数并返回这个连接。

3)如果没找到可用连接,就会创建新连接,并会把这个建立的连接加入到双端队列中,同时开始运行线程池中的线程,其实就是调用了ConectionPool的put方法。

  1. public final class ConnectionPool {
  2. void put(RealConnection connection) {
  3. if (!cleanupRunning) {
  4. //没有连接的时候调用
  5. cleanupRunning = true;
  6. executor.execute(cleanupRunnable);
  7. }
  8. connections.add(connection);
  9. }
  10. }

3)其实这个线程池中只有一个线程,是用来清理连接的,也就是上述的cleanupRunnable

  1. private final Runnable cleanupRunnable = new Runnable() {
  2. @Override
  3. public void run() {
  4. while (true) {
  5. //执行清理,并返回下次需要清理的时间。
  6. long waitNanos = cleanup(System.nanoTime());
  7. if (waitNanos == -1) return;
  8. if (waitNanos > 0) {
  9. long waitMillis = waitNanos / 1000000L;
  10. waitNanos -= (waitMillis * 1000000L);
  11. synchronized (ConnectionPool.this) {
  12. //在timeout时间内释放锁
  13. try {
  14. ConnectionPool.this.wait(waitMillis, (int) waitNanos);
  15. } catch (InterruptedException ignored) {
  16. }
  17. }
  18. }
  19. }
  20. }
  21. };

这个runnable会不停的调用cleanup方法清理线程池,并返回下一次清理的时间间隔,然后进入wait等待。

怎么清理的呢?看看源码:

  1. long cleanup(long now) {
  2. synchronized (this) {
  3. //遍历连接
  4. for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
  5. RealConnection connection = i.next();
  6. //检查连接是否是空闲状态,
  7. //不是,则inUseConnectionCount + 1
  8. //是 ,则idleConnectionCount + 1
  9. if (pruneAndGetAllocationCount(connection, now) > 0) {
  10. inUseConnectionCount++;
  11. continue;
  12. }
  13. idleConnectionCount++;
  14. // If the connection is ready to be evicted, we're done.
  15. long idleDurationNs = now - connection.idleAtNanos;
  16. if (idleDurationNs > longestIdleDurationNs) {
  17. longestIdleDurationNs = idleDurationNs;
  18. longestIdleConnection = connection;
  19. }
  20. }
  21. //如果超过keepAliveDurationNs或maxIdleConnections,
  22. //从双端队列connections中移除
  23. if (longestIdleDurationNs >= this.keepAliveDurationNs
  24. || idleConnectionCount > this.maxIdleConnections) {
  25. connections.remove(longestIdleConnection);
  26. } else if (idleConnectionCount > 0) { //如果空闲连接次数>0,返回将要到期的时间
  27. // A connection will be ready to evict soon.
  28. return keepAliveDurationNs - longestIdleDurationNs;
  29. } else if (inUseConnectionCount > 0) {
  30. // 连接依然在使用中,返回保持连接的周期5分钟
  31. return keepAliveDurationNs;
  32. } else {
  33. // No connections, idle or in use.
  34. cleanupRunning = false;
  35. return -1;
  36. }
  37. }
  38. closeQuietly(longestIdleConnection.socket());
  39. // Cleanup again immediately.
  40. return 0;
  41. }

也就是当如果空闲连接maxIdleConnections超过5个或者keepalive时间大于5分钟,则将该连接清理掉。

4)这里有个问题,怎样属于空闲连接?

其实就是有关刚才说到的一个方法acquire计数方法:

  1. public void acquire(RealConnection connection, boolean reportedAcquired) {
  2. assert (Thread.holdsLock(connectionPool));
  3. if (this.connection != null) throw new IllegalStateException();
  4. this.connection = connection;
  5. this.reportedAcquired = reportedAcquired;
  6. connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  7. }

RealConnection中,有一个StreamAllocation虚引用列表allocations。每创建一个连接,就会把连接对应的StreamAllocationReference添加进该列表中,如果连接关闭以后就将该对象移除。

5)连接池的工作就这么多,并不负责,主要就是管理双端队列Deque<RealConnection>,可以用的连接就直接用,然后定期清理连接,同时通过对StreamAllocation的引用计数实现自动回收。

OkHttp里面用到了什么设计模式

  • 责任链模式

这个不要太明显,可以说是okhttp的精髓所在了,主要体现就是拦截器的使用,具体代码可以看看上述的拦截器介绍。

  • 建造者模式

在Okhttp中,建造者模式也是用的挺多的,主要用处是将对象的创建与表示相分离,用Builder组装各项配置。

比如Request:

  1. public class Request {
  2. public static class Builder {
  3. @Nullable HttpUrl url;
  4. String method;
  5. Headers.Builder headers;
  6. @Nullable RequestBody body;
  7. public Request build() {
  8. return new Request(this);
  9. }
  10. }
  11. }
  • 工厂模式

工厂模式和建造者模式类似,区别就在于工厂模式侧重点在于对象的生成过程,而建造者模式主要是侧重对象的各个参数配置。

例子有CacheInterceptor拦截器中又个CacheStrategy对象:

  1. CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  2. public Factory(long nowMillis, Request request, Response cacheResponse) {
  3. this.nowMillis = nowMillis;
  4. this.request = request;
  5. this.cacheResponse = cacheResponse;
  6. if (cacheResponse != null) {
  7. this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
  8. this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
  9. Headers headers = cacheResponse.headers();
  10. for (int i = 0, size = headers.size(); i < size; i++) {
  11. String fieldName = headers.name(i);
  12. String value = headers.value(i);
  13. if ("Date".equalsIgnoreCase(fieldName)) {
  14. servedDate = HttpDate.parse(value);
  15. servedDateString = value;
  16. } else if ("Expires".equalsIgnoreCase(fieldName)) {
  17. expires = HttpDate.parse(value);
  18. } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
  19. lastModified = HttpDate.parse(value);
  20. lastModifiedString = value;
  21. } else if ("ETag".equalsIgnoreCase(fieldName)) {
  22. etag = value;
  23. } else if ("Age".equalsIgnoreCase(fieldName)) {
  24. ageSeconds = HttpHeaders.parseSeconds(value, -1);
  25. }
  26. }
  27. }
  28. }
  • 观察者模式

之前我写过一篇文章,是关于Okhttp中websocket的使用,由于webSocket属于长连接,所以需要进行监听,这里是用到了观察者模式:

  1. final WebSocketListener listener;
  2. @Override public void onReadMessage(String text) throws IOException {
  3. listener.onMessage(this, text);
  4. }
  • 单例模式

这个就不举例了,每个项目都会有

  • 另外有的博客还说到了策略模式,门面模式等,这些大家可以网上搜搜,毕竟每个人的想法看法都会不同,细心找找可能就会发现。

拜拜

有一起学习的小伙伴可以关注下️我的公众号——码上积木,每天剖析一个知识点,我们一起积累知识。

谈谈OKHttp的几道面试题的更多相关文章

  1. ASP.NET 经典60道面试题

    转:http://bbs.chinaunix.net/thread-4065577-1-1.html ASP.NET 经典60道面试题 1. 简述 private. protected. public ...

  2. 350道面试题分享,拿下京东offer工资double

    350道面试题分享,拿下京东offer工资double 前言: 面试,其实是一个双向选择的过程,在这个过程里,我们不应该抱着畏惧的心态去对待,这样反而会影响自己的发挥.同时看中的应该不止薪资,还要看你 ...

  3. Python 最常见的 170 道面试题解析:2019 最新

    Python 最常见的 170 道面试题解析:2019 最新 2019年06月03日 23:30:10 GitChat的博客 阅读数 21329 文章标签: PythonPython入门Python面 ...

  4. JavaSSM框架精选50道面试题

    JavaSSM框架精选50道面试题 2019年02月13日 19:04:43 EerhtSedah 阅读数 7760更多 分类专栏: 面试题   版权声明:本文为博主原创文章,遵循CC 4.0 BY- ...

  5. Python 最常见的 170 道面试题全解析:2019 版

    Python 最常见的 170 道面试题全解析:2019 版 引言 最近在刷面试题,所以需要看大量的 Python 相关的面试题,从大量的题目中总结了很多的知识,同时也对一些题目进行拓展了,但是在看了 ...

  6. Java中ArrayList相关的5道面试题

    本文参考了 <关于ArrayList的5道面试题 > 1.ArrayList的大小是如何自动增加的? 这个问题我想曾经debug过并且查看过arraylist源码的人都有印象,它的过程是: ...

  7. 嵌入式C开发人员的最好的0x10道笔试题

    嵌入式C开发人员的最好的0x10道笔试题 2006-11-22 15:53 约定: 1) 下面的测试题中,认为所有必须的头文件都已经正确的包含了 2)数据类型 char 一个字节 1 byte int ...

  8. Java 208 道面试题:第一模块答案

    目前市面上的面试题存在两大问题:第一,题目太旧好久没有更新了,还都停留在 2010 年之前的状态:第二,近几年 JDK 更新和发布都很快,Java 的用法也变了不少,加上 Java 技术栈也加入了很多 ...

  9. Java并发编程75道面试题及答案

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

随机推荐

  1. P4915 帕秋莉的魔导书(动态开点线段树)

    题目背景 帕秋莉有一个巨大的图书馆,里面有数以万计的书,其中大部分为魔导书. 题目描述 魔导书是一种需要钥匙才能看得懂的书,然而只有和书写者同等或更高熟练度的人才能看得见钥匙.因此,每本魔导书都有它自 ...

  2. 【小白学PyTorch】21 Keras的API详解(上)卷积、激活、初始化、正则

    [新闻]:机器学习炼丹术的粉丝的人工智能交流群已经建立,目前有目标检测.医学图像.时间序列等多个目标为技术学习的分群和水群唠嗑答疑解惑的总群,欢迎大家加炼丹兄为好友,加入炼丹协会.微信:cyx6450 ...

  3. Dockerize ASP。净样板项目

    Get the source code from the Github repository. 介绍 在这篇文章中,我将一步步地向你展示如何在Docker上运行ABP模块零核心模板.然后,我们将讨论其 ...

  4. golang 进行grpc调用

    参考https://blog.csdn.net/qq_32744005/article/details/105606383 go get google.golang.org/grpc go get - ...

  5. MeteoInfoLab脚本示例:利用比湿、温度计算相对湿度

    利用比湿和温度计算相对湿度的函数是qair2rh(qair, temp, press=1013.25),三个参数分别是比湿.温度和气压,气压有一个缺省值1013.25,因此计算地面相对湿度的时候也可以 ...

  6. Java中字符串相关操作(判断,增删,转换)

    1:判断字符串中是否包含某个字符(字符串): startsWith(): 这个方法有两个变体并测试如果一个字符串开头的指定索引指定的前缀或在默认情况下从字符串开始位置 此方法定义的语法如下: publ ...

  7. docker19.03制作一个基于centos8的带有nfs4服务的镜像

    一,下载centos的image 1,下载centos最新image [root@localhost ~]# docker pull centos 2,查看是否成功下载到本地image [root@l ...

  8. 笔记本电脑插网线能上网,但是连不上WIFI,或者连上WiFi显示无internet,怎么解决?

    1.鼠标 右键 "此电脑",选择"属性"

  9. RocketMQ主从搭建

    RocketMQ可分为以下几种模式: 单点模式 主从模式 双从模式 双主双从模式,多主多从模式 搭建主从模式 tar -zxvf rocketmq-4.6.0.tar.gz -C /usr/local ...

  10. Linux入门到放弃之六《磁盘和文件系统管理一》

    要求:创建卷组名为 mail_store:逻辑卷名 mail,从卷组mail_store上划出50GB空间, 使用mkfs命令创建ext3文件系统,并实现开机自动挂载,挂载点/mailbox: (1) ...