OkHttp三问—百度真题

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

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


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


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


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


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


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


Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> 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);
    return chain.proceed(originalRequest);
  }


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


  • 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方法。


RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection, true);
        return connection;
      }
    }
    return null;
  }


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


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


public final class ConnectionPool {
    void put(RealConnection connection) {
        if (!cleanupRunning) {
         //没有连接的时候调用
            cleanupRunning = true;
            executor.execute(cleanupRunnable);
        }
        connections.add(connection);
    }
}


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


private final Runnable cleanupRunnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                //执行清理,并返回下次需要清理的时间。
                long waitNanos = cleanup(System.nanoTime());
                if (waitNanos == -1) return;
                if (waitNanos > 0) {
                    long waitMillis = waitNanos / 1000000L;
                    waitNanos -= (waitMillis * 1000000L);
                    synchronized (ConnectionPool.this) {
                        //在timeout时间内释放锁
                        try {
                            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };


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


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


long cleanup(long now) {
    synchronized (this) {
      //遍历连接
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        //检查连接是否是空闲状态,
        //不是,则inUseConnectionCount + 1
        //是 ,则idleConnectionCount + 1
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }
        idleConnectionCount++;
        // If the connection is ready to be evicted, we're done.
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }
      //如果超过keepAliveDurationNs或maxIdleConnections,
      //从双端队列connections中移除
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {      
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {      //如果空闲连接次数>0,返回将要到期的时间
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // 连接依然在使用中,返回保持连接的周期5分钟
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        cleanupRunning = false;
        return -1;
      }
    }
    closeQuietly(longestIdleConnection.socket());
    // Cleanup again immediately.
    return 0;
  }


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


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


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


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


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


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


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


  • 责任链模式


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


  • 建造者模式


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


public class Request {
  public static class Builder {
    @Nullable HttpUrl url;
    String method;
    Headers.Builder headers;
    @Nullable RequestBody body;
    public Request build() {
      return new Request(this);
    }
  }
}


  • 工厂模式


工厂模式和建造者模式类似,区别就在于工厂模式侧重点在于对象的生成过程,而建造者模式主要是侧重对象的各个参数配置。例子有CacheInterceptor拦截器中又个CacheStrategy对象:


CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;
      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }


  • 观察者模式


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


final WebSocketListener listener;
  @Override public void onReadMessage(String text) throws IOException {
    listener.onMessage(this, text);
  }


  • 单例模式


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


  • 另外有的博客还说到了策略模式,门面模式等,这些大家可以网上搜搜,毕竟每个人的想法看法都会不同,细心找找可能就会发现。
目录
相关文章
|
7月前
|
设计模式 前端开发 网络协议
Android 开发网易面试凉凉经,面试官:基础不牢,彻底帮你搞懂
Android 开发网易面试凉凉经,面试官:基础不牢,彻底帮你搞懂
|
消息中间件 算法 Java
知乎最具争议的Java面试成神笔记,GitHub已下载量已过百万
前言 现在,在某论坛上面对于“中国程序员数量是否已经饱和或者过剩”这个话题很火! 今年大家都有一个共同的感受:工作不好找,薪资不理想,面试造火箭。 其实,由于不仅是因为今年受疫情影响,很多公司经营不下去,公司规模缩小,造成岗位缺少。更重要的是因为初级过剩,中级缺少,高级紧缺。所以说,作为一名程序员,如果不学习,不增进自己的技术,那你一定会被内卷被优化! 其他的不能帮助大家,分享一篇我在知乎意外发现《Java程序员面试神技》分享给大家 目录 内容展示 JVM篇 JVM 内存区域 程序计数器(线程私有) 虚拟机栈(线程私有) 本地方法区(线程私有)
82 0
|
7月前
|
前端开发 数据可视化 安全
2024金三银四必看前端面试题!简答版精品!
2024金三银四必看前端面试题!2w字精品!简答版 金三银四黄金期来了 想要跳槽的小伙伴快来看啊
172 3
|
7月前
|
安全 NoSQL Java
从安卓转到Java开发,我吃透了这份pdf,终于4面拿下美团offer
先说说个人情况吧,坐标广州,16年从一所普通二本大学毕业,毕业后在一家小公司干android开发,年薪在15w左右。转Java的契机是认识到了一个朋友,做Java后台的,经常跟他聊相关的内容,经过慎重考虑及个人的发展规划,所以就决定转型了。
|
消息中间件 分布式计算 Java
31岁没offer!GitHub彩版JAVA核心知识点+面试题集竟成救命稻草。
年初是求职和跳槽高峰期,最近很多朋友出去面试,整体的感受和反馈是:市场虽然在回暖,但是形势依旧很严峻,面试难度和拿offer的难度比往年难多了。
牛皮!仅凭“阿里爸爸”大厂面试参考笔记,去携程Java三面,已OC
今天来分享一位读者携程校招 Java 岗位的面经。 下面是正文。 个人背景:双非本,机械专业转码。 携程在正式面试之前,会有一个性格测试(40分钟)。性格测试之后,大概过一周进行笔试。笔试之后,会邮件通知是否通过并预约第一轮面试时间。 普通 offer 一般只有两面,如果是 sp 或者 ssp 的话,技术面貌似是三面。 携程的面试难度一般,效率比较高,面试体验还是不错的。
|
消息中间件 算法 架构师
不愧是Github疯传的腾讯Java进阶笔记,总结太全了
移动互联网时代,IT 系统变得愈加复杂,对我们程序员的要求也是越来越高,技术不断更新,我们还不能停止学习,停下来了就会被打上一个‘不合格的程序员’的标签,如何成为一位「不那么差」的程序员?「不那么差」的程序员又需要掌握一些什么知识呢?这篇文章就与大家聊一聊这个话题。 以下内容仅代表我从业以来所积累的相关经验,不对不全的地方还请大家海涵,下面我会从硬技能、软实力这些方面尽量阐述我所认为的 “不那么差的程序员” 应当做到哪些技能。
|
设计模式 缓存 算法
全网首发“Java面试考点大全”,20+互联网公司,应有尽有
受疫情影响,今年似乎给人感觉时间比往年还要流逝得更快。显然,春节一过,我们又将迎来面试旺季金三银四。对于程序员来说,秋招的失利更意味着在金三银四要打一场“硬战”,可又有多少人做好了面试的准备呢?对于一线互联网公司的面试,你又了解多少呢?
|
存储 负载均衡 NoSQL
阿里高工熬夜18天码出Java150K字面试宝典,却遭Github全面封杀
大家都知道,现在的Java面试是越来越难了!主要原因无非是两个: 随着Java这个行业的兴起,不管是在家待业的、对自己现在工作不满意的、大学选错专业的、缺钱的、想自己学的等等这些人绝大部分都是选择了去学习Java!大量人才涌入,导致岗位竞争越来越大,面试也就越来越难! 另外一个就是这两年的疫情影响,很多公司都宣布倒闭、裁员。加上互联网行业内卷的推动,面试造火箭工作拧螺丝已经是一个很常见的现象了!
|
设计模式 算法 Java
11场面试无一被拒!Alibaba金九银十面试参考指南(Java版)真香
Java面试 今年时间属实过得挺快的,眼瞅着就要到8月了!想必有很多小伙伴这会已经在为金九银十面试跳槽做准备了。临近面试肯定是要想办法提升自己的面试能力,这个时候如果还去一昧地提升自己的代码能力对面试是毫无帮助的。大多数人在面试的时候都会遇到以下几种情况(大家可以看看自己中了几个):