首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >OKHttp原理解析

OKHttp原理解析

作者头像
猿芯
发布于 2021-05-27 10:15:45
发布于 2021-05-27 10:15:45
87500
代码可运行
举报
运行总次数:0
代码可运行

Okhttp 应该是Android目前非常流行的第三方网络库,尝试讲解他的使用以及原理分析,分成几个部分:

  1. Okhttp同步和异步使用
  2. 同步和异步流程
  3. Dispatcher
  4. 拦截器
  5. 缓存
  6. 连接池复用

OKHttp的使用

OKHttp支持同步请求和异步请求。

同步请求

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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();

异步请求

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。

Request 和 Call 分不清两者的分别,既然有了Request为什么需要Call;

Request 表示Http的Request请求,用来封装网络请求信息及请求体,跟Response相对应;

Response 表示Http的Response响应,用来封装网络响应信息和响应体;

Call 表示请求执行器,负责发起网络请求;

同步和异步流程

1.同步请求

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Response response = call.execute();

call是一个接口,由RealCall实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


//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.异步请求

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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()方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 顾名思义,负责分发执行任务。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 线程池,负责执行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 内部构造了一条拦截器链,看下拦截器是怎么起作用的?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 自定义拦截器
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()真正处理了请求

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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()

应用拦截器

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cache.get(chain.request()) 从 DiskLruCache取出response
cache.update(cacheResponse, response); 从 DiskLruCache修改response
cache.put(response); // response加入 DiskLruCache缓存

连接池复用 ConnectInterceptor

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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生成构造的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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建立连接

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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

往期推荐

  1. 肝九千字长文 | MyBatis-Plus 码之重器 lambda 表达式使用指南,开发效率瞬间提升80%
  2. 用 MHA 做 MySQL 读写分离,频繁爆发线上生产事故后,泪奔分享 Druid 连接池参数优化实战
  3. 微服务架构下,解决数据库跨库查询的一些思路
  4. 一文读懂阿里大中台、小前台战略

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构荟萃 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
[教程] 创建第一条 Substrate 链
翻译自官方文档: https://substrate.dev/docs/en/tutorials/create-your-first-substrate-chain/
Tiny熊
2020/07/24
1.6K0
[教程] 创建第一条 Substrate 链
深入浅出Substrate:剖析运行时Runtime
基于Substrate开发自己的运行时模块,会遇到一个比较大的挑战,就是理解Substrate运行时(Runtime)。本文首先介绍了Runtime的架构,类型,常用宏,并结合一个实际的演示项目,做了具体代码分析,以帮助大家更好地理解在Substrate中它们是如何一起工作的。
MikeLoveRust
2019/08/28
1.4K0
深入浅出Substrate:剖析运行时Runtime
开发应用专用的Substrate区块链!
Substrate是开发应用特定区块链 (Application Specific Blockchain )的快速开发框架。与基于以太坊等公链开发的DApp相比,应用特定区块链是围绕单一应用特别构建的专用区块链,因此具有最大的灵活性和最少的限制。本文将详细介绍如何使用Substrate框架快速实现一个简单的游戏应用专用区块链。
用户1408045
2019/09/11
1.5K0
开发应用专用的Substrate区块链!
​Substrate 环境安装提速文档
Substrate 环境安装提速文档(Mike版,仅限Debian/Ubuntu Linux 和 Mac brew)
MikeLoveRust
2019/08/19
1.2K0
Substrate 开发系列 - 环境搭建
Polkadot 目标是成为一个连接各区块链的区块链(网络),Substrate 是 Polkadot 生态中重要的一环, 他是一套工具与框架的集合,让我们以模块化的方式来构建自己的区块链。Polkadot本身也是基于 Subsstrate 创建的。
Tiny熊
2020/06/04
1.5K0
Substrate 开发系列 - 环境搭建
【区块链开发框架】-substrate(Polkadot运用)
Substrate是一个用于构建区块链的开源的、模块化的和可扩展的区块链开发框架。它由Parity以及个人开发者和许多公司组成的社区共同维护。Substrate可以用作开发公链、联盟链和私有链的基础,它可以在短时间内构建完整、可配置的区块链系统。另外一个可选方案,可以将构建的区块链部署到Polkadot网络中,以此获得共享安全等其他优势。
帆说区块链
2022/04/27
1.1K0
【区块链开发框架】-substrate(Polkadot运用)
使用Substrate开发区块链存证应用V2.0
本文是《使用Substrate开发区块链存证dApp》一文的更新,在一台全新服务器上,从零起步,采用最新的v2.0.0版本开发一个自定义的区块链存证dApp。
jasonruan
2020/12/14
1.4K0
substrate 合约模块简要剖析(一)
本文主要介绍 substrate 合约模块的实现逻辑,srml/contracts 提供了部署和执行 WASM 智能合约的功能。作为一个模块化的区块链框架,不管是未来的波卡平行链还是基于 substrate 拥有独立共识的链,比如 ChainX, 只要引入其合约模块,就具备了合约功能,可以成为一个智能合约平台。ChainX 目前就计划引入合约功能,对区块链智能合约开发者提供支持, 欢迎有兴趣的同学持续关注。
用户1558438
2019/09/27
1.1K0
substrate 合约模块简要剖析(一)
Web3 开发框架及特点
Web3 的开发框架有助于开发者构建基于区块链的应用程序(如去中心化应用 DApps)。以下是一些主流的 Web3 开发框架和工具。
数字孪生开发者
2024/12/10
1980
Web3 开发框架及特点
使用Ink!开发Substrate ERC20智能合约
ERC20 通证标准(ERC20 Token Standard)是通过以太坊创建通证时的一种规范。按照 ERC20 的规范可以编写一个智能合约,创建“可互换通证”。它并非强制要求,但遵循这个标准,所创建的通证可以与众多交易所、钱包等进行交互,它现在已被行业普遍接受。
jasonruan
2020/08/12
1.3K0
创建第一条substrate2.0链
官网上的安装代码会出现一个bug,即安装的源码一致,编译工具版本一致,但是最后编译出现问题。原因是cargo下载的package没有成功checkout到对应版本的代码。这里如果之前已经安装了相关的rust工具链条,需要先行卸载:
Tiny熊
2020/11/03
6520
创建第一条substrate2.0链
Substrate区块链框架学习小组
Substrate是一个由Rust语言写的区块链开发框架,是目前业界最强大,特性最丰富的区块链框架之一。使用Substrate,你可以很方便地搭建出一条链(solo链)。它可以开发公链,联盟链,私有链。作为开发框架(脚手架),它提供了一切必要的完善的基础组件,让你不需要从轮子造起。
MikeLoveRust
2022/11/28
1K0
Substrate源码分析:启动流程
我们在命令行启动 substrate 节点,到底发生了什么呢?本文基于 substrate 源码,对其启动流程进行了简单的分析。
MikeLoveRust
2019/08/19
9840
web3.0区块链NFT链游系统开发流程源码部署方案
Chain game是一款基于区块链技术运营的手机游戏,它使游戏玩家首次成为手机游戏的真正所有者。游戏玩家在游戏中拥有的武器和装备是可以自由交易的NFT,不会被游戏开发者操纵。其他游戏开发商可以为游戏玩家的NFT开发新游戏。如果之前的游戏软件开发得不好,游戏玩家可以使用NFT来玩新游戏。我们的团队很早就开始布局区块链游戏。未来,连锁游戏开发技术是同行中的先锋。
开发v_StPv888
2023/01/30
5540
Rust今天4岁啦, 为什么越来越多的知名项目用Rust来开发?
4年前的今天(2015年5月15日),Rust编程语言核心团队正式宣布发布Rust 1.0版本。
区块链大本营
2019/05/17
7.5K0
ChainBridge跨链协议快速教程【EVM/Substrate】
ChainBridge是一个可扩展的跨链通信协议,目前兼容EMV和Substrate链,支持两个不同的EVM区块链、或者一个EVM链与一个Substrate链之间的跨链桥接与通证转移,支持ERC20、ERC721等多种类型的通证的跨链转移,以及普通数据的跨链转移。在这个教程中,我们将介绍ChainBridge的基本构成和安装方法,并利用ChainBridge实现Substrate原生资产和以太坊ERC20/ERC721通证之间的跨链转移。
用户5687508
2021/05/30
1.8K0
浅谈量化合约对冲系统APP开发方案
目前大数据交易平台借助区块链底层技术有两个方向的解决方案,一是借助区块链数据不可篡改的特性来记录数据所有使用过程,把区块链用来做数据之间使用权转移的记账,做数据确权。另一种方式是借助隐私计算,实现不交易数据本身,只交易数据的计算结果。
开发v_StPv888
2022/11/07
3420
区块链 RWA软件系统的开发框架
开发区块链 RWA(Real-World Asset)软件系统是一个复杂的过程,涉及多个技术层面。选择合适的开发框架至关重要,它将直接影响开发效率、系统性能、安全性和可维护性。以下是一些关键的开发框架,可以根据系统的具体需求进行选择和组合。
数字孪生开发者
2025/04/15
2180
区块链 RWA软件系统的开发框架
从第一行代码到发链只需一小时,用这款新工具,你也能做到
古罗马,“乘法”是只有御用数学家才能理解的深奥概念——而当阿拉伯数字出现,孩童亦能于纸上演算。
区块链大本营
2018/12/11
5510
从第一行代码到发链只需一小时,用这款新工具,你也能做到
使用 Ink!开发 Substrate ERC20 智能合约
ERC20 通证标准(ERC20 Token Standard)是通过以太坊创建通证时的一种规范。按照 ERC20 的规范可以编写一个智能合约,创建“可互换通证”。它并非强制要求,但遵循这个标准,所创建的通证可以与众多交易所、钱包等进行交互,它现在已被行业普遍接受。
Tiny熊
2020/07/21
1K0
推荐阅读
相关推荐
[教程] 创建第一条 Substrate 链
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档