深入浅出 OkHttp 源码解析及应用实践

科技资讯 投稿 6900 0 评论

深入浅出 OkHttp 源码解析及应用实践

OkHttp 在 Java 和 Android 世界中被广泛使用,深入学习源代码有助于掌握软件特性和提高编程水平。

一、背景介绍

在生产实践中,常常会遇到这样的场景:需要针对某一类 Http 请求做统一的处理,例如在 Header 里添加请求参数或者修改请求响应等等。这类问题的一种比较优雅的解决方案是使用拦截器来对请求和响应做统一处理。

二、OkHttp 基本原理

2.1 从一个请求示例出发

OkHttp 可以用来发送同步或异步的请求,异步请求与同步请求的主要区别在于异步请求会交由线程池来调度请求的执行。使用 OkHttp 发送一个同步请求的代码相当简洁,示例代码如下:

    // 1.创建OkHttpClient客户端
    OkHttpClient client = new OkHttpClient(;
    public String getSync(String url throws IOException {
          OkHttpClient client = new OkHttpClient(;
          // 2.创建一个Request对象
          Request request = new Request.Builder(
                  .url(url
                  .build(;
          // 3.创建一个Call对象并调用execute(方法
          try (Response response = client.newCall(request.execute( {
              return response.body(.string(;
          }
      }

    其中 execute( 方法是请求发起的入口,RealCall 对象的 execute( 方法的源代码如下:

      @Override public Response execute( throws IOException {
        synchronized (this { // 同步锁定当前对象,将当前对象标记为“已执行”
          if (executed throw new IllegalStateException("Already Executed";
          executed = true;
        }
        captureCallStackTrace(; // 捕获调用栈
        eventListener.callStart(this; // 事件监听器记录“调用开始”事件
        try {
          client.dispatcher(.executed(this; // 调度器将当前对象放入“运行中”队列
          Response result = getResponseWithInterceptorChain(; // 通过拦截器发起调用并获取响应
          if (result == null throw new IOException("Canceled";
          return result;
        } catch (IOException e {
          eventListener.callFailed(this, e; // 异常时记录“调用失败事件”
          throw e;
        } finally {
          client.dispatcher(.finished(this; // 将当前对象从“运行中”队列移除
        }
      }

      execute( 方法首先将当前请求标记为“已执行”,然后会为重试跟踪拦截器添加堆栈追踪信息,接着事件监听器记录“调用开始”事件,调度器将当前对象放入“运行中”队列 ,之后通过拦截器发起调用并获取响应,最后在 finally 块中将当前请求从“运行中”队列移除,异常发生时事件监听器记录“调用失败”事件。其中关键的方法是 getResponseWithInterceptorChain(,其源代码如下:

      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, ……;
       
          return chain.proceed(originalRequest;
        }

      该方法中按照特定的顺序创建了一个有序的拦截器列表,之后使用拦截器列表创建拦截器链并发起proceed( 方法调用。在chain.proceed( 方法中会使用递归的方式将列表中的拦截器串联起来依次对请求对象进行处理。拦截器链的实现是 OkHttp 的一个巧妙所在,在后文我们会用一小节专门讨论。在继续往下分析之前,通过以上的代码片段我们已经大致看到了一个请求发起的整体流程。

      2.2 OkHttp 核心执行流程

      图 2-1 OkHttp请求执行流程图

        OkHttpClient:是整个 OkHttp 的核心管理类,从面向对象的抽象表示上来看它代表了客户端本身,是请求的调用工厂,用来发送请求和读取响应。在大多数情况下这个类应该是被共享的,因为每个 Client 对象持有自己的连接池和线程池。重复创建则会造成在空闲池上的资源浪费。Client对象可以通过默认的无参构造方法创建也可以通过 Builder 创建自定义的 Client 对象。Client 持有的线程池和连接池资源在空闲时可以自动释放无需客户端代码手动释放,在特殊情况下也支持手动释放。

      • Request:一个 Request 对象代表了一个 Http 请求。它包含了请求地址 url,请求方法类型 method,请求头 headers,请求体 body 等属性,该对象具有的属性普遍使用了 final 关键字来修饰,正如该类的说明文档中所述,当这个类的 body 为空或者 body 本身是不可变对象时,这个类是一个不可变对象。

      • Response:一个 Response 对象代表了一个 Http 响应。这个实例对象是一个不可变对象,只有 responseBody 是一个可以一次性使用的值,其他属性都是不可变的。

      • RealCall:一个 RealCall 对象代表了一个准备好执行的请求调用。它只能被执行一次。同时负责了调度和责任链组织的两大重任。

      • Dispatcher:调度器。它决定了异步调用何时被执行,内部使用 ExecutorService 调度执行,支持自定义 Executor。

      • EventListener:事件监听器。抽象类 EventListener 定义了在一个请求生命周期中记录各种事件的方法,通过监听各种事件,可以用来捕获应用程序 HTTP 请求的执行指标。从而监控 HTTP 调用的频率和性能。

      • Interceptor:拦截器。对应了软件设计模式中的拦截器模式,拦截器可用于改变、增强软件的常规处理流程,该模式的核心特征是对软件系统的改变是透明的和自动的。OkHttp 将整个请求的复杂逻辑拆分成多个独立的拦截器实现,通过责任链的设计模式将它们串联到一起,完成发送请求获取响应结果的过程。

      2.3 OkHttp 整体架构

      图 2-2 OkHttp架构图(图片来自网络)

      2.4 OkHttp 拦截器的种类和作用

        client.interceptors:由开发者设置的拦截器,会在所有的拦截器处理之前进行最早的拦截处理,可用于添加一些公共参数,如自定义 header、自定义 log 等等。

      • RetryAndFollowUpInterceptor:主要负责进行重试和重定向的处理。

      • BridgeInterceptor:主要负责请求和响应的转换。把用户构造的 request 对象转换成发送到服务器 request对象,并把服务器返回的响应转换为对用户友好的响应。

      • CacheInterceptor:主要负责缓存的相关处理,将 Http 的请求结果放到到缓存中,以便在下次进行相同的请求时,直接从缓存中读取结果,提高响应速度。

      • ConnectInterceptor:主要负责建立连接,建立 TCP 连接或者 TLS 连接。

      • client.networkInterceptors:由开发者设置的拦截器,本质上和第一个拦截器类似,但是由于位置不同,所以用处也不同。

      • CallServerInterceptor:主要负责网络数据的请求和响应,也就是实际的网络I/O操作。将请求头与请求体发送给服务器,以及解析服务器返回的 response。

      图 2-3 拦截器(图片来自OkHttp官网)

      不同的拦截器有不同的适用场景,他们各自的优缺点如下:

      应用程序拦截器

        无需担心重定向和重试等中间响应。

      • 总是被调用一次,即使 HTTP 响应是从缓存中提供的。

      • 可以观察到应用程序的原始请求。不关心 OkHttp 注入的标头。

      • 允许短路而不调用 Chain.proceed(方法。

      • 允许重试并多次调用 Chain.proceed(方法。

      • 可以使用 withConnectTimeout、withReadTimeout、withWriteTimeout 调整呼叫超时。

      网络拦截器

        能够对重定向和重试等中间响应进行操作。

      • 缓存响应不会调用。

      • 可以观察到通过网络传输的原始数据。

      • 可以访问携带请求的链接。

      2.5 责任链模式串联拦截器调用

      2.5.1 责任链模式

      责任链模式 是一种行为设计模式, 允许将请求沿着处理者链发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下一个处理者。

      图 2-4 责任链(图片来自网络)

      适用场景 包括:

        当程序需要使用不同方式处理不同种类的请求时

      • 当程序必须按顺序执行多个处理者时

      • 当所需要的处理者及其顺序必须在运行时进行改变时

      优点

        可以控制请求处理的顺序

      • 可对发起操作和执行操作的类进行解耦。

      • 可以在不更改现有代码的情况下在程序中新增处理者。

      2.5.2 拦截器的串联

      public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection throws IOException {
          if (index >= interceptors.size( throw new AssertionError(;
       
          // ……  
          // Call the next interceptor in the chain.
          RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout;
          Interceptor interceptor = interceptors.get(index;
          Response response = interceptor.intercept(next;
       
          // ……
          return response;
        }

      这段代码可以看成三个步骤:

        索引判断。index 初始值为0,它指示了拦截器对象在列表中的索引顺序,每执行一次 proceed 方法该参数自增1,当索引值大于拦截器列表的索引下标时异常退出。

      1. 创建下一个责任链对象。

      2. 按照索引顺序获取一个拦截器,并调用 intercept( 方法。

        责任链串联

       

        递归方法

       

      图 2-5 Interceptor 和 Chain 的关系图

      三、OkHttp 拦截器在项目中的应用

        添加请求头的拦截器

      public class EncryptInterceptor implements Interceptor {
          @Override
          public Response intercept(Chain chain throws IOException {
              Request originRequest = chain.request(;
       
              // 计算认证信息
              String authorization = this.encrypt(originRequest;
               
              // 添加请求头
              Request request = originRequest.newBuilder(
                      .addHeader("Authorization", authorization
                      .build(;
              // 向责任链后面传递
              return chain.proceed(request;
          }
      }

      之后在创建 OkHttpClient 客户端的时候,使用 addInterceptor( 方法将我们的拦截器注册成应用程序拦截器,即可实现自动地、无感地向请求头中添加实时的认证信息的功能。

        OkHttpClient client = new OkHttpClient.Builder(
            .addInterceptor(new EncryptInterceptor(
            .build(;

        四、回顾总结

        OkHttp 在 Java 和 Android 世界中被广泛使用,通过使用 OkHttp 拦截器可以解决一类问题——针对一类请求统一修改请求或响应内容。深入了解 OkHttp 的设计和实现不仅可以帮助我们学习优秀开源软件的设计和编码经验,也有利于更好地使用软件特性以及对特殊场景下问题的排查。本文尝试从一个同步 GET 请求的例子开始,首先通过源代码片段简要分析了一个请求发起过程中涉及的核心代码,接着用流程图的形式总结了请求执行过程,然后用架构图展示了OkHttp的分层设计,介绍了各种拦截器的用途、工作层次及优缺点,之后着重分析了拦截器的责任链模式设计——本质是一个递归调用,最后用一个简单的例子介绍了 OkHttp 拦截器在实际生产场景中的应用。

        参考:

          OkHttp官方文档

        1. OkHttp源码解析系列文章

        编程笔记 » 深入浅出 OkHttp 源码解析及应用实践

        赞同 (38) or 分享 (0)
        游客 发表我的评论   换个身份
        取消评论

        表情
        (0)个小伙伴在吐槽