OkHttp三问—百度真题

Stella981
• 阅读 923

来吧,今天说说常用的网络框架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);  }
  • 单例模式

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

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

OkHttp三问—百度真题

点点在看你最好看

OkHttp三问—百度真题

OkHttp三问—百度真题

本文分享自微信公众号 - 码上积木(Lzjimu)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
浪人 浪人
3年前
OkHttp 源码分析 - Okhttp同步请求、异步请求过程
RxJava源码的基础部分分析的差不多,后续如果有新的内容话,会继续的补充。从今天开始,我们来看看OkHttp的相关源码。OkHttp的源码过于复杂,涉及到的方面非常的多,本系列文章目的是打通Okhttp的整个执行流程,不对某一个细节重点分析。  本篇文章是本系列文章的第一篇,我们先从最简单的Okhttp使用入手,进而分析Okhttp两种请求方式的流程。
希望的天 希望的天
3年前
Retrofit封装Okhttp逻辑原理
总结自retrofit封装了Okhttp本身并不能进行网络请求。只能在Android使用的网络请求框架。1.png2.pngrequest:统一完成(post/get/...)回调陷阱:完成上一步网络请求才能进行下一步网络请求。3.pngRetrofit简化了网络请求。优化了网络请求的使用。4.png5.png7.pngbuild设计模式:参数》5个;
Stella981 Stella981
3年前
Retrofit源码解析(上)
简介Retrofit是Square公司开发的一款针对Android网络请求的框架,官网地址http://square.github.io/retrofit/,在官网上有这样的一句话介绍retrofit,AtypesafeHTTPclientforAndroidandJava。我们知道Retrofit底层是基于OKHttp实现的。对ok
Stella981 Stella981
3年前
OkHttp配置HTTPS访问+服务器部署
1概述OkHttp配置HTTPS访问,核心为以下三个部分:sslSocketFactory()HostnameVerifierX509TrustManager第一个是ssl套接字工厂,第二个用来验证主机名,第三个是证书信任器管理类.通过OkHttp实现HTTPS访问需要自己实现以上三部分.另外还简单提及了服
Wesley13 Wesley13
3年前
Volley
OkHttp可以作为Volley底层传输协议,速度更快,传大量图片建议使用。OkHttp更多功能请看OkHttp的使用(https://my.oschina.net/zhangqie/blog/807474)xUtils支持大文件上传,更全面的http请求协议支持(10种谓词),拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响...
Stella981 Stella981
3年前
OkHttp3中的代理与路由
路由是什么呢?路由即是网络数据包在网络中的传输路径,或者说数据包在传输过程中所经过的网络节点,比如路由器,代理服务器之类的。那像OkHttp3这样的网络库对于数据包的路由需要做些什么事呢?用户可以为终端设置代理服务器,HTTP/HTTPS代理或SOCK代理。OkHttp3中的路由相关逻辑,需要从系统中获取用户设置的代理服务器的地址,将HTTP请求转换为代
Stella981 Stella981
3年前
Android中使用OkHttp的五种请求方式及注意事项
文章目录一、环境说明1、在gradle中引入依赖2、AndroidManifest中开启网络权限3、搭建测试接口4、编写OkHttp工具类二、发送请求1.发送无参的Get请求
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这