美文网首页
OkHttp 面试题汇总

OkHttp 面试题汇总

作者: 我要离开浪浪山 | 来源:发表于2023-03-18 20:35 被阅读0次

    一、前言

    如今面试中高级开发工程师岗位,OKhttp 原理是必问环节,只会使用已经无法满足 Android 开发市场的需求,优秀的第三方框架源码剖析不仅能深度理解框架,也能对自己学习带来很大的帮助。

    本篇文章根据朋友反馈和亲身经历简单整理的一些关于 Okhttp 常见面试题目。

    1.Okhttp 基本实现原理

    OkHttp 主要是通过 5 个拦截器和 3 个双端队列(2 个异步队列,1 个同步队列)工作。内部实现通过一个责任链模式完成,将网络请求的各个阶段封装到各个链条中,实现了各层的解耦。

    OkHttp 的底层是通过 Socket 发送 HTTP 请求与接受响应,但是 OkHttp 实现了连接池的概念,即对于同一主机的多个请求,可以公用一个 Socket 连接,而不是每次发送完 HTTP 请求就关闭底层的 Socket,这样就实现了连接池的概念。而 OkHttp 对 Socket 的读写操作使用的 OkIo 库进行了一层封装。

    执行流程:

    • 通过构建者构建出OkHttpClient对象,再通过newCall方法获得RealCall请求对象.
    • 通过RealCall发起同步或异步请求,而决定是异步还是同步请求的是由线程分发器dispatcher来决定.
    • 当发起同步请求时会将请求加入到同步队列中依次执行,所以会阻塞UI线程,需要开启子线程执行.
    • 当发起异步请求时会创建一个线程池,并且判断请求队列是否大于最大请求队列64,请求主机数是否大于5,如果大于请求添加到异步等待队列中,否则添加到异步执行队列,并执行任务.

    2.Okhttp 网络缓存如何实现?

    OKHttp 默认只支持 get 请求的缓存。

    • 第一次拿到响应后根据头信息决定是否缓存。
    • 下次请求时判断是否存在本地缓存,是否需要使用对比缓存、封装请求头信息等等。
    • 如果缓存失效或者需要对比缓存则发出网络请求,否则使用本地缓存。

    3.Okhttp 网络连接怎么实现复用?

    HttpEngine 在发起请求之前,会先调用nextConnection()来获取一个Connection对象,如果可以从ConnectionPool中获取一个Connection对象,就不会新建,如果无法获取,就会调用createnextConnection()来新建一个Connection对象,这就是 Okhttp 多路复用的核心,不像之前的网络框架,无论有没有,都会新建Connection对象。

    20210312101552861.png

    4.Dispatcher 的功能是什么?

    Dispatcher中文是分发器的意思,和拦截器不同的是分发器不做事件处理,只做事件流向。他负责将每一次Requst进行分发,压栈到自己的线程池,并通过调用者自己不同的方式进行异步和同步处理。 通俗的讲就是主要维护任务队列的作用。

    • 记录同步任务、异步任务及等待执行的异步任务。
    • 调度线程池管理异步任务。
    • 发起/取消网络请求 API:execute、enqueue、cancel。

    Dispatcher 类,该类中维护了三个双端队列(Deque):

    readyAsyncCalls:准备运行的异步请求
    runningAsyncCalls:正在运行的异步请求
    runningSyncCalls:正在运行的同步请求

    OkHttp 设置了默认的最大并发请求量 maxRequests = 64 和单个 Host 主机支持的最大并发量 maxRequestsPerHost = 5

    5.addInterceptor 与 addNetworkInterceptor 的区别?

    二者通常的叫法为应用拦截器和网络拦截器,从整个责任链路来看,应用拦截器是最先执行的拦截器,也就是用户自己设置request属性后的原始请求,而网络拦截器位于ConnectInterceptor和CallServerInterceptor之间,此时网络链路已经准备好,只等待发送请求数据。

    1、首先,应用拦截器在RetryAndFollowUpInterceptor和CacheInterceptor之前,所以一旦发生错误重试或者网络重定向,网络拦截器可能执行多次,因为相当于进行了二次请求,但是应用拦截器永远只会触发一次。另外如果在CacheInterceptor中命中了缓存就不需要走网络请求了,因此会存在短路网络拦截器的情况。
    2、其次,如上文提到除了CallServerInterceptor,每个拦截器都应该至少调用一次realChain.proceed方法。实际上在应用拦截器这层可以多次调用proceed方法(本地异常重试)或者不调用proceed方法(中断),但是网络拦截器这层连接已经准备好,可且仅可调用一次proceed方法。
    3、最后,从使用场景看,应用拦截器因为只会调用一次,通常用于统计客户端的网络请求发起情况;而网络拦截器一次调用代表了一定会发起一次网络通信,因此通常可用于统计网络链路上传输的数据。

    6、Okhttp 拦截器的作用是什么?

    1、应用拦截器

    拿到的是原始请求,可以添加一些自定义header、通用参数、参数加密、网关接入等等。

    • RetryAndFollowUpInterceptor 处理错误重试和重定向
    • BridgeInterceptor 应用层和网络层的桥接拦截器,主要工作是为请求添加cookie、添加固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,然后保存响应结果的cookie,如果响应使用gzip压缩过,则还需要进行解压。
    • CacheInterceptor 缓存拦截器,如果命中缓存则不会发起网络请求。
    • ConnectInterceptor 连接拦截器,内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。

    2、网络拦截器

    用户自定义拦截器,通常用于监控网络层的数据传输。

    • CallServerInterceptor 请求拦截器,在前置准备工作完成后,真正发起了网络请求。

    7、Okhttp 有哪些优势?

    • 支持 http2,对一台机器的所有请求共享同一个 Socket
    • 内置连接池,支持连接复用,减少延迟
    • 支持透明的 gzip 压缩响应体
    • 响应缓存可以完全避免网络重复请求
    • 请求失败时自动重试主机的其他 ip,自动重定向
    • 丰富的 API,可扩展性好

    8、response.body().string() 为什么只能调用一次?

    我们可能习惯在获取到Response对象后,先response.body().string()打印一遍 Log,再进行数据解析,却发现第二次直接抛异常,其实直接跟源码进去看就发现,通过source拿到字节流以后,直接调用closeQuietly()方法关闭了,这样第二次再去通过source读取就直接流已关闭的异常了。

    public final String string() throws IOException {
      BufferedSource source = source();
      try {
        Charset charset = Util.bomAwareCharset(source, charset());
        return source.readString(charset);
      } finally {
        //这里讲resource给悄悄close了
        Util.closeQuietly(source);
      }
    }
    

    解决方案:
    1.内存缓存一份response.body().string();
    2.自定义拦截器处理 Log。

    9、OkHttp请求整体流程是怎么样?

    • Request-》OkHttpClient-》RealCall
    • 同步 -》 在调用线程 执行五大拦截器
    • 异步 -》 使用分发器将任务在线程池执行 五大拦截器
    var okHttpClient = OkHttpClient.Builder().build()
    var request = Request.Builder().url("https://www.baidu.com")
           .cacheControl(CacheControl.FORCE_CACHE)
           .build()
    var call = okHttpClient.newCall(request)
    val result = call.execute()
    println(result.isSuccessful)
    result.close()
    
    280da08ecf58408515783ab19d468ec.png
    • 分发器:内部维护队列与线程池,完成请求调配;
    • 拦截器:完成整个请求过程。

    10、分发器是如何工作的?

    • 对于同步请求,分发器只记录请求,用于判断IdleRunnable是否需要执行;
    • 对于异步请求,向分发器中提交请求;
    • 同步-》记录同步任务:RealCall
    • 异步-》首先将任务加入ready队列等待执行 -》是否需要将ready中的任务放入running 执行
    • 同时请求的异步任务数不得大于64个
    • 从ready中取出来的异步任务,与其相同的HOST,不得大于5个
    • 若已经存在5个相同HOST的任务在执行,则继续从ready中检查下一个等待任务

    Q:如何决定将请求放入ready还是running?
    A:如果当前正在请求数为64,则将请求放入ready等待执行;如果小于64,但是已经存在同一域名主机的请求5个,也会放入ready;否则放入running队列立即执行;

    Q:从ready移动running的条件是什么?
    A:每个请求执行完成就会从running移除,同时进行第一步相同逻辑的判断,决定是否移动!

    11、拦截器是如何工作的?

    • 责任链设计模式 将请求者 与 执行者 解耦
    • 让请求者 只需要将请求发给责任链即可,无需关系请求过程与细节。
    • 重试重定向、桥接、缓存、连接、请求服务

    12、应用拦截器与网络拦截器的区别?

    OkHttp中拦截器有:自定义应用拦截器重试重定向桥接缓存连接自定义网络拦截器请求服务

    自定义应用拦截器与自定义网络拦截器的区别主要是顺序的区别,由于以上拦截器采用责任链设计模式组合执行,因此顺序不同,带来的影响是:应用拦截器不需要关心是否重定向或者失败重连(只会执行一次);同时它也能决定是否执行其他拦截器而网络拦截器则可以操作重定向与重试并且可能不会执行(直接在缓存中获得结果),同时它也可以观察到真正的Request以及相关的连接信息(经过其他拦截器处理完毕后

    13、OkHttp缓存机制

    • OkHttp基于Http协议实现了缓存,但是默认是关闭状态,需要在配置OkHttpClient时候使用:OkHttpClient.Builder().cache(Cache(文件,大小)) .build() 开启

    • OkHttp只会缓存GET请求的响应,在RFC7231中GET,HEAD和某些情况下的POST都是可缓存的,但是绝大多数的实现里只支持GET和HEAD的缓存,这是因为post做的一般是修改和删除的工作,所以必须与服务端交互,所以不能使用缓存
      而Http的缓存又分为强缓存协商缓存

    14、强缓存

    • 命中强缓存时,浏览器并不会将请求发送给服务器。强缓存是利用http的返回头中的Expires或者Cache- Control两个字段来控制的,用来表示资源的缓存时间;
    • 若未命中强缓存,则浏览器会将请求发送至服务器。服务器根据http头信息中的Last-Modify/If-Modify- Since或Etag/If-None-Match来判断是否命中协商缓存。如果命中,则http返回码为304,客户端从缓存中加载资源。
    1. expires,它的值为一个绝对时间,如果发送请求的时间在expires之前,那么本地缓存有效,能够直接使用缓存。

    2. cache-control:max-age=number,资源第一次的请求时间和该值相加,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则就不行。另外此响应头还能设置为:

      • no-cache:不使用本地缓存。
      • no-store:不允许被缓存
      • public:可以被任何用户缓存,包括终端用户和CDN等中间代理服务器。
      • private:只能被终端用户缓存,不允许CDN等中间代理服务器缓存。
      • immutable:(响应)资源不会改变

    15、协商缓存

    协商缓存的意思是:浏览器会将请求发送至服务器。服务端可能响应304(不包含响应体数据)表示缓存可用,可能正常响应,如200同时携带响应体。为了让服务端判断是否可用缓存,在请求时,需要携带标识:**If-Modified-Since或者If-None-Match**

    其中If-Modified-Since需要和响应的Last-Modified 配合使用,而If-None-Match 则与Etag 配合。

    Last-Modified/ If-Modified-Since
    在缓存的响应中响应头包含:Last-Modified ,表示服务端告知的对应请求的资源在服务器上的最后修改时间。再次发起请求,需要在请求头中携带:If-Modified-Since。其值就是响应中的Last-Modified的值。意思就是告诉服务端我的缓存,在服务端什么时候修改后拿到的。服务端判断这个时间后是否修改过资源,未修改则返回3-04,否则正常返回响应数据。

    Etag/If-None-Match
    这两个值是由服务器生成的每个资源的唯一标识字符串,只要资源有变化就这个值就会改变。在请求时携带If-None-Match,其值是缓存的响应头中的Etag,服务端获取到请求头中的If-None-Match会重新计算此次请求资源的标识,如果一致则返回304。

    16、Okhttp 运用了哪些设计模式?

    Okhttp 运用了六种设计模式:

    • 构造者模式(OkhttpClient,Request 等各种对象的创建)
    • 工厂模式(在 Call 接口中,有一个内部工厂 Factory 接口。)
    • 单例模式(Platform 类,已经使用 Okhttp 时使用单例)
    • 策略模式(在 CacheInterceptor 中,在响应数据的选择中使用了策略模式,选择缓存数据还是选择网络访问。)
    • 责任链模式(拦截器的链式调用)
    • 享元模式(Dispatcher 的线程池中,不限量的线程池实现了对象复用)

    17、HTTP1和HTTP2的区别

    • 1.新的二进制格式:HTTP2采用二进制格式而HTTP1使用文本格式。
    • 2.多路复用:HTTP2是完全多复用的,而非有序并阻塞的,只需一个连接即可实现并行。HTTP1一个连接只能发送一个请求。
    • 3.头部压缩:HTTP 1.1中,每一次发送和响应,都有HTTP头信息。HTTP 2压缩头信息,减少带宽。
    • 4.服务器推送:HTTP 1只能客户端发送数据,服务器端返回数据。HTTP2中,服务器可以主动向客户端发起一些数据传输(如css和png等),服务器可以并行发送html,css,js等数据。。

    18、为什么需要头部压缩?

    HTTP协议是不带有状态的,每次请求头部都会附上所有的信息,而且很多的信息都是重复的,这会浪费很多宽带也会影响速度,所以HTTP2对头部进行了压缩,一方面使用gzip或compress进行头部压缩,另一方面,客户端和服务器会同时维护同一张头信息表,所有的字段都会存入这张表中,生成一个索引号,以后就不需要再发送同样的字段了,只发送索引号,提示了速度。

    相关文章

      网友评论

          本文标题:OkHttp 面试题汇总

          本文链接:https://www.haomeiwen.com/subject/pfterdtx.html