面试中通常是问些问题考察你对OkHttp的原理是否有深入理解。

使用OkHttp发送网络请求并根据请求结果刷新UI有哪几种方式

  1. 使用AsyncTask + OkHttp的同步请求

  2. 使用OkHttp的异步请求+runOnUiThread方法(或者通过Handler发送到UI线程)

可否介绍一下OkHttp的整个异步请求流程

1.创建OkHttpClient对象
2.创建Request对象
3.通过OkHttpClient对象和Request对象创建Call 对象

Call call = client.newCall(request);

4.执行Call(异步请求即AsyncCall)的enqueue方法,会调用分发器Dispather的enqueue方法,将AsyncCall入队并提交给线程池执行,线程池中的线程会调用Call的execute()方法。
5.Call的execute()方法会调用getResponseWithInterceptorChain()方法通过一系列拦截器对请求进行处理之后发出该请求并读取响应。

OkHttp对于网络请求都有哪些优化,如何实现的

1、分发器线程池的引入

2、缓存

我们都知道,好的框架都会有缓存的功能,通过缓存我们可以很快的访问我们的资源,那 okhttp 也不例外,从上面的流程中我们可以看到, CacheInterceptor 主要是做缓存的,那么我们来了解一下他的流程是什么:
okhttp 的缓存策略是,key 为 Request的 url 的 MD5 值,value 为 response。
1、如果在 okhttpclient 初始化的时候配置了 cache,那么我们则从缓存中读取 caseResponse。
2、如果没有指定,那么我们将 request 和 caseResponse 构建一个 CacheStrategy 的类
3、判断 cachestrategy 是否有效,如果 request 和 caseResponse 都为空,直接返回 504
4、如果 request == null ,cacheResponse 不为空,则返回
5、如果为空,那么我们就进行网络请求,如果返回了 304 且我们本地有缓存,那么说明我们的缓存没有过期,可以继续使用

3、多路复用

之前我们的请求是每一次请求会建立一个链接,请求结束就关闭链接,我们都知道 TCP/IP 请求是需要握手的,那握手就会消耗相应的时间,所以在我们的 okhttp 中,我们会复用之前的链接进行请求,这样请求速度就快了很多。

如果是这样的话就会出现两个问题,第一,我们怎么判断链接是否可用,第二是我们不需要的链接怎么回收。
从上面我们知道我们会在 StreamAllocation.newStream 方法中获取 RealConnection,在获取的时候我们会判断有没有之前的链接可以复用,复用的条件是这样判断的:
1、如果此链接的负载数目超过指定数目(表现为RealConnection的allocations集合的数量超过该链接指定的数量)或者noNewStreams为true时,此链接不可复用。
2、StreamAllocation 所持有的Address对象和RealConnection的Address非主机部分不同,则此链接不可复用。至于非主机部分的判定是在Address的equalsNonHost方法来体现。
两者Adress对象的非主机部分相等的标准就是dns,Authenticator对象、协议、CA授权验证标准、端口等信息全部相等。
3、在1、2判定条件都为true的话,如果两个Address对象的host或者说url中的host一样,则此链接可复用,正如注释说说,添加1、2、3都满足的话,那么此时这个链接就是This connection is a perfect match。
第一个问题我们解决了,现在我们来解决第二个问题,我们都知道链接如果多了我们如果不回收的话就会卡死,那么我们的链接是怎么回收的呢,链接回收主要降到的就是 ConnectionPool 这个类,这个类中有一个 cleanup 方法,我们来看一下他里面做了什么:

    /*** Performs maintenance on this pool, evicting the connection that has been idle the longest if* either it has exceeded the keep alive limit or the idle connections limit.** <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns* -1 if no further cleanups are required.*/long cleanup(long now) {int inUseConnectionCount = 0;int idleConnectionCount = 0;RealConnection longestIdleConnection = null; //连接池中的闲置了最久的连接long longestIdleDurationNs = Long.MIN_VALUE; //连接池中的闲置连接的最长闲置时间// Find either a connection to evict, or the time that the next eviction is due.synchronized (this) {//遍历连接池for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {RealConnection connection = i.next();// If the connection is in use, keep searching.// todo 检查连接是否正在被使用if (pruneAndGetAllocationCount(connection, now) > 0) {inUseConnectionCount++;continue;}//todo 否则记录闲置连接数idleConnectionCount++;// If the connection is ready to be evicted, we're done.//TODO 获得这个连接已经闲置多久// 执行完遍历,获得闲置了最久的连接以及最长闲置时间long idleDurationNs = now - connection.idleAtNanos;if (idleDurationNs > longestIdleDurationNs) {longestIdleDurationNs = idleDurationNs;longestIdleConnection = connection;}}//todo 最长闲置时间超过了保活时间(5分钟) 或者池内的连接数量超过了最大闲置连接数量(5个)// 马上移除这个闲置了最久的连接,然后返回0,0表示不等待,马上再次执行清理任务if (longestIdleDurationNs >= this.keepAliveDurationNs|| idleConnectionCount > this.maxIdleConnections) {// We've found a connection to evict. Remove it from the list, then close it below (outside// of the synchronized block).connections.remove(longestIdleConnection);} else if (idleConnectionCount > 0) {// A connection will be ready to evict soon.//todo 池内存在闲置连接,那就等待 保活时间(5分钟)-最长闲置时间 =还能闲置多久 后再次检查return keepAliveDurationNs - longestIdleDurationNs;} else if (inUseConnectionCount > 0) {// All connections are in use. It'll be at least the keep alive duration 'til we run again.//todo 池内所有的连接都在使用中,就等 keepAliveDurationNs(5分钟) 后再次检查return keepAliveDurationNs;} else {// No connections, idle or in use.//todo 都不满足,即池内没有任何连接,直接停止清理任务(put后会再次启动清理任务)cleanupRunning = false;return -1;}}closeQuietly(longestIdleConnection.socket());//移除闲置连接的同时关闭这个闲置连接的socket// Cleanup again immediately.return 0;}

标记清除法
1、首先标记出最不活跃的链接(空闲链接),之后进行清除
2、如果被标记的链接空闲 socket 超过 5 个,时间大于 5 分钟,那么直接清除。
3、如果此链接空闲,但是不足五分钟,则返回剩余时间,并进行标记,以供下次清除。
4、如果没用空闲链接的话,则五分钟之后再进行清理

判断是否为空闲链接:
1、遍历其中的 StreamAllocation,判断是否为空,如果为空,则没有引用这个 StreamAllocation
2、如果引用数量为 0 ,则为空闲链接

4、支持gzip压缩

当 response 通过 bridgeInterceptor 处理的时候会进行 gzip 压缩,这样可以大大减小我们的 response ,他不是什么情况下都压缩的,只有支持的时候才会 进行压缩。

OkHttp框架中都用到了哪些设计模式

单例模式:(建议用单例模式创建OkHttpClient)OkHttpClient, 可以通过 new OkHttpClient() 或 new OkHttpClient.Builder() 来创建对象, 但是—特别注意, OkHttpClient() 对象最好是共享的, 建议使用单例模式创建。 因为每个 OkHttpClient 对象都管理自己独有的线程池和连接池。 这一点很多同学,甚至在我经历的团队中就有人踩过坑, 每一个请求都创建一个 OkHttpClient 导致内存爆掉。
外观模式 : OKHttpClient 里面组合了很多的类对象。其实是将OKHttp的很多功能模块全部包装进这个类中,让这个类单独提供对外的API,这种设计叫做外观模式(外观模式:隐藏系统的复杂性,并向客户端提供了一个可以访问系统的接口)
Builder模式 : OkHttpClient 比较复杂, 太多属性, 而且客户的组合需求多样化, 所以OKhttp使用建造者模式(Build模式:使用多个简单的对象一步一步构建成一个复杂的对象,一个 Builder 类会一步一步构造最终的对象)
工厂方法模式:Call接口提供了内部接口Factory(用于将对象的创建延迟到该工厂类的子类中进行,从而实现动态的配置。(工厂方法模式:这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。)
享元模式:在Dispatcher的线程池中,所用到了享元模式,一个不限容量的线程池 , 线程空闲时存活时间为 60 秒。线程池实现了对象复用,降低线程创建开销,从设计模式上来讲,使用了享元模式。(享元模式:尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象,主要用于减少创建对象的数量,以减少内存占用和提高性能)

责任链模式:很明显,在okhttp中的拦截器模块,执行过程用到。OkHttp3 的拦截器链中, 内置了5个默认的拦截器,分别用于重试及重定向、桥接、缓存、链接、网络读写。(责任链模式:为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。)
策略模式 :CacheInterceptor 实现了数据的选择策略, 来自网络还是来自本地? 这个场景也是比较契合策略模式场景, CacheInterceptor 需要一个策略提供者提供它一个策略(锦囊), CacheInterceptor 根据这个策略去选择走网络数据还是本地缓存。
缓存的策略过程:
1、请求头包含 “If-Modified-Since” 或 “If-None-Match” 暂时不走缓存
2、客户端通过 cacheControl 指定了无缓存,不走缓存
3、客户端通过 cacheControl 指定了缓存,则看缓存过期时间,符合要求走缓存。
4、如果走了网络请求,响应状态码为 304(只有客户端请求头包含 “If-Modified-Since” 或 “If-None-Match” ,服务器数据没变化的话会返回304状态码,不会返回响应内容), 表示客户端继续用缓存。

(策略模式:一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。)

OkHttp源码中用到的核心类有哪些,简单讲一下

1.OkhttpClient :对外的API,OkHttp的很多功能模块,全部包装进这个类;创建分为两种:一种是new OkHttpClient()的方式;另一种是使用建造者(Builder)模式 – new OkHttpClient.Builder()…Build()。
那么这两种方式有什么区别呢?
第一种:new OkHttpClient(),OkHttp做了很多工作,很多我们需要的参数在这里都获得默认值,也就是默认值设定。
第二种:默认的设置和第一种方式相同,但是我们可以利用建造者模式单独的设置每一个属性;
注意事项:OkHttpClient强烈建议全局单例使用,因为每一个OkHttpClient都有自己单独的连接池和线程池,复用连接池和线程池能够减少延迟、节省内存。

2.RealCall类:集成Call类,从源代码中,可看到使用Call类,发送出(同步/异步)请求.RealCall的主要作用:发送请求,当中还有拦截器的建立过程,异步回调。

3.Dispatcher类(分发器,调度器,多线程):保存同步和异步Call的地方,并负责执行异步AsyncCall

4.Interceptor类:有用户自定义的Interceptor、RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、 CallServerInterceptor。拦截器之所以可以依次调用,并最终再从后向前返回Response,都依赖于RealInterceptorChain的proceed方法.

5.RealInterceptorChain(拦截器链):
getResponseWithInterceptorChain()方法中,先创建了一个拦截器列表interceptors,当拦截器列表组装完成,就会实例化拦截器链对象RealInterceptorChain:

Response getResponseWithInterceptorChain() throws IOException {...Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());return chain.proceed(originalRequest);
}

然后调用该对象的proceed方法进行发送请求并接收响应。

HttpEngine与Interceptor

五大Interceptor都是在okhttp3后才被引入,它们非常重要,负责了重试及重定向、组装请求头部、读/写缓存、建立socket连接、向服务器发送请求/接收响应的全部过程。
在okhttp3之前,这些行为都封装在HttpEngine类中。okhttp3之后,HttpEngine已经被删去,取而代之的是这5个Interceptor,可以说一次网络请求中的细节被解耦放在不同的Interceptor中,不同Interceptor只负责自己的那一环节工作(对Request或者Response进行获取/处理),使得拦截器模式完全贯穿整个网络请求。

为什么OkHttp好用呢?OkHttp有什么特点呢?

1)支持http2,对一台主机的所有请求共享同一个socket 连接

2)内置连接池,支持连接复用,减少延迟

3)支持透明的gzip压缩响应体

4)通过缓存避免不必要的网络请求

5)请求失败时自动重试主机的其他ip,自动重定向

6)好用的API,比如提供配置dns的api,可以配置httpdns

分发器线程池的原理

为什么要用两个队列(即running队列和ready队列)?

对于异步请求,Dispatcher使用了两个Deque,一个保存准备执行的请求,一个保存正在执行的请求,为什么要用两个呢?因为Dispatcher默认支持最大的并发请求是64个,单个Host最多执行5个并发请求,如果超过,则Call会先被放入到readyAsyncCall中,当出现空闲的线程时,再将readyAsyncCall中的线程移入到runningAsynCalls中,执行请求。

如果正在执行的请求总数<64 && 单个Host正在执行的请求数量<5,则将请求加入到runningAsyncCalls集合中,紧接着就是利用线程池执行该请求,否则就将该请求放入readyAsyncCalls集合中。

如何决定将请求放入ready还是running?

满足两个条件则加入正在执行队列runningAsyncCalls,同时提交给线程池。
1.正在请求的数量是有限制的(maxRequests ,默认是64),即
2.同一域名正在请求的个数也是有限制的(maxRequestsPerHost,默认是5)
即满足(正在请求的数量<64 且 同一域名正在请求的数量<5),放入正在执行队列runningAsyncCalls;否则放入等待队列readyAsyncCalls。

OkHttp怎么实现断点续传流程,用什么保存

OkHttp的同步请求和异步请求

1.异步使用了Dispatcher来将存储在 Deque 中的请求分派给线程池执行。
2.当任务执行完成后,无论是否有异常,finally代码段总会被执行,也就是会调用Dispatcher的finished方法,它将正在运行的任务Call从running队列中移除后,主动判断是否要将任务从ready队列移动到running队列。

参考:
Okhttp面试简答
OKHttp3–拦截器链RealInterceptorChain源码解析【五】

Android Okhttp 断点续传面试解析
Android Okhttp断点续传面试深入解析

分享几个重要的Android面试题

OkHttp原理解析之面试题分析相关推荐

  1. OkHttp原理流程源码分析

    OkHttp已经是非常流行的android客户端的网络请求框架,我其实在项目中使用也已经好几年了,之前一直把重心放在如何快速的搞定业务上.迭代的效率上,这一点来讲,对于一个公司优秀员工是没有毛病的.但 ...

  2. OkHttp原理解析(二)

    前言 上一篇我们学习了OKHttp的请求执行流程,知道了最终请求流程都会交给getResponseWithInterceptorChain方法来执行,接下来我们就详细分析执行getResponseWi ...

  3. OkHttp 原理解析

    一.前言: HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽.OkHttp是一个高效的HTTP客户端,它有以下默认特性: 支持HTTP/2,允许所有同 ...

  4. OkHttp原理解析(一)

    OkHttp 3.10.0版本,最新OkHttp为:4.0.1逻辑与3版本并没有太大变化,但是改为kotlin实现. OkHttp介绍 OkHttp是当下Android使用最频繁的网络请求框架,由Sq ...

  5. OkHttp原理解析

    OkHttp是当下Android使用最频繁的网络请求框架. Google在Android4.4以后,开始将源码中 的HttpURLConnection底层实现 替换为 OKHttp,(即HttpURL ...

  6. OkHttp原理解析(1)

    基于OkHttp 3.10.0版本,最新OkHttp为:4.0.1逻辑与3版本并没有太大变化,但是改为kotlin实现. OkHttp介绍 OkHttp是当下Android使用最频繁的网络请求框架,由 ...

  7. PRI变换法原理解析及其matlab分析

    ---------------------------------------------------------------------------------------------------- ...

  8. Android逆向之八门神器原理解析(主要分析其修改内存原理)

    实现dump出指定进程的内存文件 进阶 进程注入 参考教程:Android注入完全剖析 问题解决 1.执行memdump时报错:error: only position independent exe ...

  9. Android:安卓学习笔记之OkHttp原理的简单理解和使用

    Android OkHttp使用原理的简单理解和使用 OkHttp 0.前言 1.请求与响应流程 1.1 请求的封装 1.2 请求的发送 1.3 请求的调度 1.4 请求的处理 2.拦截器 2.1 R ...

最新文章

  1. 深度学习之梯度下降法
  2. linux 查本机公网ip 网站接口 nmap工具
  3. 单片机入门学习笔记7:人机交互界面
  4. 2021ICPC(澳门) - LCS Spanning Tree(广义后缀自动机)
  5. 【转发活动】Hey, 是你吗? | 寻粉启示
  6. linux 0.11 源码学习(二)
  7. SQL Server:简繁转换
  8. numpy 矩阵 秩_大规模电商推荐数据分析-基于矩阵分解的召回
  9. java基本变量的堆栈_JAVA经验谈:尽可能使用堆栈变量
  10. DPDK 网卡绑定和解绑
  11. 鸿蒙OS可以装电脑吗,组装台式机可以装鸿蒙系统吗?
  12. 利用matlab将三维数据画成三维立体图
  13. C++/C语言-基本语法
  14. Backtrader(十一) - Indicator指标
  15. 一步步教你搭建Android开发环境(有图有真相)--“自吹自擂:史上最详细、最啰嗦、最新的搭建教程”
  16. SparkRDD函数详解
  17. 8.五言律诗和绝句的句型及平仄格式
  18. SAP MIGO批次特性增强
  19. FatMouse's Speed(LIS+路径记录)
  20. ISE impact:923 “Can not find cable, check cable setup !” 解决方法

热门文章

  1. echo控制OneNET硬件设备
  2. PHP 调试 - Xdebug
  3. 广宇修炼困难重重 沃美毫不气馁
  4. 湖南大学平台2串口部分第二次实验
  5. java读取每个字符_详解Java String字符串获取每一个字符及常用方法
  6. 柔性压阻式传感器电路采集原理
  7. MySQL8从入门到精通\\数据库和数据表的基本操作
  8. express框架学习笔记
  9. iPhone 13外形调整:苹果将启用屏下指纹
  10. 计算机被老师控制该怎么退出控制,电脑被老师控制如何解控