十七 OkHttp详解

1,708 阅读4分钟

概述

OkHttp这个框架,由 Square公司开发,在 android app领域,制霸级别的存在。Retrofit+okHttp几乎成了移动开发中网络框架的标配。

本文分析的okhttp是基于 3.10.0的版本。

基本使用

以异步请求为例:

OkHttpClient client = new OkHttpClient(); // 创建新的client

// 或者  利用工厂类创建一个定制化的OkHttp

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
builder.connectTimeOut(60,TimeUnit.SECOND) // 设置超时时间
       .addInterceptor(interceptor) // 添加拦截器 
       .proxy(proxy) // 设置请求代理 
       .cache(cache); // 设置缓存策略
client = clientBuilder.builder();
       

// 建造者模式,将参数设置步骤和对象创建步骤的创建分离,避免了超级多个构造函数重载的尴尬现象
Request request = new Request.Builder()
        .url(url)
        .build(); 

// 这是一个异步请求的用法。
// 请求创建出来之后,先进入到执行队列
client.newCall(request).enqueue(new Callback(){
    @Override
    public void onFailure(Call call,IOException e){
        
    }
    
    @Override
    public void onResponse(Call call,Response response) throws IOException {
        
    }
    
});

请求流程分析

操作的起点是:client.newCall(request).enqueue() 将异步请求加入执行队列

image.png

加入队列的具体代码为: client.dispatcher().enqueue(new AsyncCall)

image.png

Dispatcher为 请求调度器,它是一种门户模式,所有的请求,都必须经过这唯一的门户才能执行,或者取消操作。 它内部维护了一个线程池去执行异步操作,保证了最大并发数,以及同一个host允许执行请求的线程个数。

enqueue的具体实现如下:

image.png

仔细观察这段代码会发现:

  • 存在两个队列,一个running队列,一个 ready队列,前者的优先级比后者高
  • 当running队列没满的时候,并且同一个host的执行线程数没超标的时候,请求都会进入到 running队列
  • running队列满了,或者 同一个host执行线程数超标了,请求会进入到 ready队列
  • 如果成功进入了running队列,那么 就会 使用线程池 executorService,启动一个子线程去执行它,执行的自然就是 Call的run()方法。

以下是 AsyncCall的 源代码:

image.png

图中1处,是run方法中另外调用了一个execute方法。 图中2处,是 执行网络请求的真实代码。

以下是 getResponseWithInterceptorChain()的源代码: image.png

此处分析一下:

  • 这是按照责任链模式设计的拦截器链条。
  • 拦截器的执行顺序为:
    • 用户自定义的拦截器
    • 重试和重定向拦截器
      • 在发送失败时,尝试重新发送请求。
      • 当请求需要重定向时,将它重新指向另一个地址
    • 桥接拦截器
      • 对request header设置默认值,比如 content-type,keep-alive,cookie等
    • 缓存拦截器
      • 负责http请求的缓存处理
    • 网络连接拦截器
      • 负责与服务器建立TCP连接
    • 用户自定义的网络拦截器
    • 服务调用拦截器
      • 调用底层硬件,向服务器发送request,并拿到response

可以看到一共有7个拦截器,其中两个是用户自定义的,暂时不关注。 下面重点分析其中的 缓存拦截器服务调用拦截器

缓存拦截器

  1. 根据request获取已有缓存的response,并根据缓存情况创建CacheStrategy对象来判断缓存是否有效(是否过期等)。

image.png

  1. 如果缓存无效,则继续调用chain.proceed();将流程转到下一个拦截器

image.png

  1. 如果从服务器成功获取到了response,判断是否将此response进行缓存

image.png

Okhttp只是设计了一套缓存策略,具体如何将响应缓存到本地,并且如何从本地缓存中获取响应,都是由开发者自己决定,并通过 okhttp.builderer.cache()方法设置。

okhttp提供了一个默认的Cache类,我们可以通过下面的方式设置缓存区域的最大size。 image.png

Cache类的内部则使用了 DiskLruCache来实现具体的缓存功能。

image.png

服务调用拦截器

它是 okhttp的最后一个拦截器,是真正执行网络请求的底层核心代码,包括发出请求,以及接收回应。 image.png

okhttp使用扩展

okio是Square为okhttp打造的io流。 在构建response时,需要传入一个 ResponseBody对象。 ResponseBody内封装了对请求结果的读取操作。可以通过继承并扩展ResponseBody的方式来获取网络请求的进度。(这个还是挺有用的)

如下图,自定义一个ResponseBody,通过它可以向上层汇报网络请求的进度。 image.png

下面的代码:

  1. 指定了响应的缓存位置,以及缓存的空间大小
  2. 添加了一个自定义的网络拦截器,将进度值向外反馈 image.png

扩展实践

Picasso 也是 Square公司的一个图片加载框架,实际上它的内部还是 okhttp在请求图片数据,

image.png

要实现在图片加载时显示原型进度:

image.png

image.png

总结

OkHttp框架中使用的设计模式有

  • builder建造者模式
    • 避免了大量的构造函数重载
  • dispatcher门户模式
    • 使用统一的入口来接收所有的请求,并进行分发
  • chain责任链模式
    • 链式调用各个拦截器的interceptor方法,并支持添加自定义拦截器逻辑