OKHttp原理解析(okhttp的作用)
csdh11 2025-05-09 17:50 4 浏览
Okhttp 应该是Android目前非常流行的第三方网络库,尝试讲解他的使用以及原理分析,分成几个部分:
- Okhttp同步和异步使用
- 同步和异步流程
- Dispatcher
- 拦截器
- 缓存
- 连接池复用
OKHttp的使用
OKHttp支持同步请求和异步请求。
同步请求
OkHttpClient mClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = mClient.newCall(request);
Response response = call.execute();
异步请求
OkHttpClient mClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = mClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: " + response.body().string() );
}
});
这里只是很简单的使用了OKHttp去get请求,分别用了同步请求和异步请求。
前面的流程基本是一样的,构造OkHttpClient实例,构造一个Request表示Http的Request请求,再生成Call请求;
最后根据是同步还是异步,决定是直接在当前线程执行execute(),还是 enqueue()加入dispacter待执行队列。
注意enqueue()的回调并不是在主线程。如果需要切换线程的话可能需要借助Handler。
后台私信回复 1024 免费领取 SpringCloud、SpringBoot,微信小程序、Java面试、数据结构、算法等全套视频资料。
Request 和 Call 分不清两者的分别,既然有了Request为什么需要Call;
Request 表示Http的Request请求,用来封装网络请求信息及请求体,跟Response相对应;
Response 表示Http的Response响应,用来封装网络响应信息和响应体;
Call 表示请求执行器,负责发起网络请求;
同步和异步流程
1.同步请求
Response response = call.execute();
call是一个接口,由RealCall实现:
//RealCall
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
// dispatcher加入执行队列
client.dispatcher().executed(this);
// 拦截器责任链:真正执行请求、处理结果
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
// 从dispatcher正在执行队列中移除
client.dispatcher().finished(this);
}
}
Dispatcher负责控制执行哪个请求,内部有线程池执行请求,但是在同步请求里面并没有发挥它真正的作用,只是在开始时加入正在执行队列,执行完毕后从正在执行的队列中移除。
同步请求是在当前线程直接执行的,之所以加入dispatcher正在执行的队列,是为了方便判断哪些请求是正在进行的。
getResponseWithInterceptorChain() 是一个责任链模式,是OkHttp的精髓,内部是一连串的拦截器,每个拦截器各司其职,包含了从网络请求到网络响应以及各种处理,甚至可以加入用户自定义的拦截器,类似网络协议。
2.异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: " + response.body().string() );
}
});
具体看下,RealCall实现enqueue()
// RealCall
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
这里新建了一个AsyncCall,并加入dispatcher的待执行队列。在dispatcher的线程池执行到AsyncCall.executeOn()
// AsyncCall
// executorService是一个线程池
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this); // 真正执行
success = true;
} catch (RejectedExecutionException e) {
...
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
AsyncCall 继承自NameRunnable,NameRunnable的run方法会执行execute(),所以 executorService.execute(this) 最后会执行AsyncCall的execute()方法
protected void execute() {
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
// 失败回调
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
// 成功回调
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
...
} finally {
// 从dispatcher正在执行队列中移除
client.dispatcher().finished(this);
}
}
可见,最后还是走到了
getResponseWithInterceptorChain(),只不过跟同步请求不一样的是,它执行在dispatcher的线程池里面,最后在子线程回调
Dispatcher
Dispatcher 顾名思义,负责分发执行任务。
// 线程池,负责执行AsyncCall异步请求
private @Nullable ExecutorService executorService;
// AsyncCall异步请求待执行队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// AsyncCall异步请求正在执行队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
// RealCall同步请求正在执行队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
Dispatcher 的任务很简单,控制分发当前执行的请求
拦截器:责任链模式
RealCall 内部构造了一条拦截器链,看下拦截器是怎么起作用的?
// 自定义拦截器
public class CustomInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 1.发起请求前,获取到上一个拦截器传过来的request,对request处理
// 2.交给下个拦截器处理,最后获得response
Response response = chain.proceed(request);
// 对response 处理返回给上一层
return response;
}
}
这样说可能会比较抽象,画个流程图就很清晰了。
RealCall的
getResponseWithInterceptor()真正处理了请求
// RealCall
Response getResponseWithInterceptorChain() throws IOException {
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
不管是异步请求还是同步请求都会走到
getResponseWithInterceptorChain(),内部的拦截器链:client.interceptors()
后台私信回复 1024 免费领取 SpringCloud、SpringBoot,微信小程序、Java面试、数据结构、算法等全套视频资料。
应用拦截器
RetryAndFollowUpInterceptor
1.在网络请求失败后进行重试
2.重定向时直接发起新的请求
BridgeInterceptor
1.设置内容长度,内容编码
2.设置gzip压缩,并在接收到内容后进行解压
3.添加cookie
4.添加其他报头keep-alive
CacheInterceptor
管理服务器返回内容的缓存,由内存策略决定
ConnectInterceptor
为请求找到合适的连接,复用已有连接或重新创建的连接,由连接池决定
client.networkInterceptors()
网络拦截器
CallServerInterceptor
向服务器发起真正的访问请求,获取response
缓存 CacheInterceptor
http缓存机制
http分强缓存和协商缓存
强缓存:如果客户端命中缓存就不需要和服务器端发生交互,有两个字段Expires和Cache-Control
协商缓存:不管客户端是否命中缓存都要和服务器端发生交互,主要字段有
if-modified/if-modified-since 、Etag/if-none-match
Expires:表示缓存过期时间
Cache-Control :表示缓存的策略,有两个容易搞混的:no-store 表示绝不使用缓存,no-cache 表示在使用缓存之前,向服务器发送验证请求,返回304则表示资源没有修改,可以使用缓存,返回200则资源发生修改,需要替换
if-modified:服务器端资源的最后修改时间,响应头部会带上这个标识
if-modified-since:客户端请求会带上资源的最后修改时间,服务端返回304表示资源未修改,返回200则资源发生修改,需要替换
Etag:服务端的资源的标识,响应头部会带上这个标识
If-none-match:客户端请求会带上资源的额标识,服务端同样检查,返回304或200
CacheInterceptor
public Response intercept(Chain chain) throws IOException {
// 从cache中获取对应的响应的缓存
Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
networkResponse = chain.proceed(networkRequest);
// 获取到网络的Response
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
// 返回码304,资源没有发生改变
cache.update(cacheResponse, response); // 修改cache
return response;
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
...
CacheRequest cacheRequest = cache.put(response); // response加入缓存
...
}
OKHttp cache类底层实现是DiskLruCache,在之前的文章《LruCache 和 DiskLruCache 的使用以及原理分析》有过介绍。
CacheStrategy 缓存策略,根据上面的Http机制、request,确定是使用网络的response、还是缓存的response
cache.get(chain.request()) 从 DiskLruCache取出response
cache.update(cacheResponse, response); 从 DiskLruCache修改response
cache.put(response); // response加入 DiskLruCache缓存
连接池复用 ConnectInterceptor
// ConnectInterceptor
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
// 获取 StreamAllocation
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 通过streamAllocation 获取复用的或者新生成的连接Connection
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
ConnectInterceptor的intercept()流程很简单,基本靠StreamAllocation,StreamAllocation是在上一个拦截器
RetryAndFollowUpInterceptor生成构造的。
// RetryAndFollowUpInterceptor
public Response intercept(Chain chain) throws IOException {
...
// 构造StreamAllocation
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
...
}
StreamAllocation 是管理类,管理客户端请求call、客户端到服务端的连接和客户端到服务端之间的流
在这里StreamAllocation有两个作用:构造出HttpCodec、构造出RealConnection
HttpCodec 负责对request进行编码和对response解码,有两个实现类Http1Codec和Http2Codec,分别对应 HTTP/1.1 和 HTTP/2 版本
RealConnection 表示客户端到服务端的连接,底层利用socket建立连接
// StreamAllocation
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
StreamAllocation里面有一个重要的变量ConnectionPool连接池,定时维护管理RealConnection,该连接池内部是Deque,findHealthyConnection() 从连接池ConnectionPool里面寻找出可复用的连接或者生成一个新的连接。
总结
OKHttp 使用责任链模式,从上到下分发处理请求,又从下到上处理结果。
OKHttp 默认的缓存底层是DiskLruCache
OkHttp 底层是socket,支持Http、Https,复用连接
OkHttp 还大量使用了建造者模式 Builder
原文:https://u.nu/wd3ca
后台私信回复 1024 免费领取 SpringCloud、SpringBoot,微信小程序、Java面试、数据结构、算法等全套视频资料。
- 上一篇:快速教会你优雅的解决TCP客户端端口耗尽的问题
- 已经是最后一篇了
相关推荐
- OKHttp原理解析(okhttp的作用)
-
Okhttp应该是Android目前非常流行的第三方网络库,尝试讲解他的使用以及原理分析,分成几个部分:...
- 快速教会你优雅的解决TCP客户端端口耗尽的问题
-
Hello,我是Henry,相信各位开发老爷在使用大并发网络连接的时候,可能都遇到过TCP客户端端口耗尽的问题,这是一个常见问题,以下是系统性的解决方案及技术细节:1.理解端口限制的本质...
- 这3个接口基础知识,产品经理需要知道
-
产品经理在工作中,避免不了要阅读接口文档,希望本文能够帮助大家更好的了解接口。接口,即客户端(浏览器)向服务器提交请求,服务器向客户端返回响应。本质就是数据的传输与接收。本文主要介绍接口相关的基础知识...
- Java 11新特性对开发者的影响:让编程更高效、更自由
-
Java11新特性对开发者的影响:让编程更高效、更自由在这个瞬息万变的编程世界里,每一代Java的更新都承载着无数开发者对性能优化、生产力提升以及代码美感追求的期望。作为继Java8之后的首个长期...
- 干货-okHttp的优点-收藏了(okhttp的好处)
-
OkHttp相较于其它的实现有以下的优点.支持SPDY,允许连接同一主机的所有请求分享一个socket。如果SPDY不可用,会使用连接池减少请求延迟。使用GZIP压缩下载内容,且压缩操作对用...
- 如何在 Java 项目中集成 DeepSeek
-
一、使用官方SDK基础集成1.添加依赖(Maven)<dependency><groupId>com.deepseek</groupId>...
- spring cloud gateway 性能优化思路
-
SpringCloudGateway是一个高性能的API网关,但在实际的生产环境中,可能会遇到一些性能瓶颈。以下是一些SpringCloudGateway的性能优化方面:调整线程池...
- 你对Android中的okHttp的使用真的了解吗
-
框架下载地址:https://github.com/square/okhttp今天给大家讲解下网络框架okhttp的使用,这个框架非常强大,很多框架都用它来加载网络资源,目前很多开发者还在用As...
- 京东大佬问我,Nginx并发连接如何设置?详细说明
-
京东大佬问我,Nginx并发连接如何设置?详细说明首先,我需要回忆一下Nginx的并发模型。Nginx是基于事件驱动的异步架构,所以它的并发处理能力和配置参数有很大关系。主要的参数应该包括worker...
- 如何实现一个连接池?一文带你深入浅出,彻底搞懂
-
-前言-【2w1h】是技术领域中一种非常有效的思考和学习方式,即What、Why和How;坚持【2w1h】,可以快速提升我们的深度思考能力。...
- Golang 网络编程(golang 系统编程)
-
TCP网络编程存在的问题:拆包:对发送端来说应用程序写入的数据远大于socket缓冲区大小,不能一次性将这些数据发送到server端就会出现拆包的情况。通过网络传输的数据包最大是1500字节,当TCP...
- Spring6|Spring Boot3有哪些HTTP客户端可以选择
-
个人博客:无奈何杨(wnhyang)个人语雀:wnhyang...
- 10. 常用标准库(标准库有哪些)
-
本章深入解析Go语言核心标准库的关键功能与生产级应用技巧,结合性能优化与安全实践,提供高效开发指南。10.1fmt/io/os10.1.1fmt高级格式化...
- Nginx之连接池(nginx 长连接 连接复用)
-
我们知道Nginx利用连接池来增加它对资源的利用率。下面我们一起来看看Nginx是如何使用连接池的。从上一节模块开始已经慢慢会接触一些Nginx的源码部分来。每个worker进程都有一个独立的ngx...
- 开发者必备的Android开发资源之OkHttp
-
小编在这里给各位Android开发者介绍的资源包括工具、库和网站等。有效地利用它们,将有助于减轻我们的工作量,提高我们的工作效率。为什么需要一个HTTP库Android系统提供了两种HTTP通信类,H...
- 一周热门
- 最近发表
- 标签列表
-
- mydisktest_v298 (34)
- document.appendchild (35)
- 头像打包下载 (61)
- acmecadconverter_8.52绿色版 (39)
- word文档批量处理大师破解版 (36)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- parsevideo (33)
- 个人网站源码 (37)
- centos7.4下载 (33)
- mysql 查询今天的数据 (34)
- intouch2014r2sp1永久授权 (36)
- 先锋影音源资2019 (35)
- jdk1.8.0_191下载 (33)
- axure9注册码 (33)
- pts/1 (33)
- spire.pdf 破解版 (35)
- shiro jwt (35)
- sklearn中文手册pdf (35)
- itextsharp使用手册 (33)
- 凯立德2012夏季版懒人包 (34)
- 冒险岛代码查询器 (34)
- 128*128png图片 (34)
- jdk1.8.0_131下载 (34)
- dos 删除目录下所有子目录及文件 (36)