OkHttp源码分析

那么我今天给大家简单地讲一下Okhttp这款网络框架及其原理。它是如何请求数据,如何响应数据的  有什么优点?它的应用场景是什么?6z


说到Okhtpp的原理他是基于原生的Http,他的优点主要是如下几点:

1.支持同步、异步。

2.支持GZIP减少数据流量

3.缓存响应数据(从而减少重复的网络请求)

4.自动重连(处理了代理服务问题和SSL握手失败的问题)

5.支持SPDY(共享同一个Socket来处理同一个服务器的请求,如果SPDY不可用,则通过连接池来减少请求延时)


它的应用场景呢是在数据量特别大重量级的网络请求


上面说了一下他的优点,下面说一下他是如何发起网络请求如何响应数据的


OkHttp中的重要类:

1,OkHttpClient:OkHttp请求客户端,Builder模式实现

2,Dispatcher:本质是异步请求的调度器,负责调度异步请求的执行,控制最大请求并发数和单个主机的最大并发数,并持有有一个线程池负责执行异步请求,对同步请求只是作统计操作。

3,Request:封装网络请求,就是构建请求参数(如url,header,请求方式,请求参数),Builder模式实现

4,Response:网络请求对应的响应,Builder模式实现,真正的Response是通过RealCall.getResponseWithInterceptorChain()方法获取的。

5,Call:是根据Request生成的一个具体的请求实例,且一个Call只能被执行一次。

6,ConnectionPool:Socket连接池

7,Interceptor:Interceptor可以说是OkHttp的核心功能,它就是通过Interceptor来完成监控管理,重写和重试请求的。

8,Cache:可以自定义是否采用缓存,缓存形式是磁盘缓存,DiskLruCache。

不管是同步请求还是异步请求,都是通过RealCall.getResponseWithInterceptorChain()方法获取请求结

果的,只不过在前者在主线程中执行,而后者在线程池中的线程中执行的。


一个典型的请求过程是这样的,用一个构造好的OkHttpClient和Request获取到一个Call,然后执行call的异步或者同步方法取得Response或者处理异常,如下所示:


OkHttpClient  okHttpClient = new OkHttpClient();

Request request = new Request.Builder()

        .url("")

        .build();

okHttpClient.newCall(request).enqueue(new Callback() {

    @Override

    public void onFailure(Call call, IOException e) {


    }


    @Override

    public void onResponse(Call call, Response response) throws IOException {


    }

});

OkHttp3,网络请求库,同步请求RealCall.execute()和异步请求RealCall.enqueue(),请求任务都是交给Dispatcher调度请求任务的处理,请求通过一条拦截链,每一个拦截器处理一部分工作,最后一个拦截器,完成获取请求任务的响应,会将响应沿着拦截链向上传递。



这里实际上,Call的实现是一个RealCall的类,execute的代码如下:

final class RealCall implements Call {

@Override public Response execute() throws IOException {

  synchronized (this) {

    if (executed) throw new IllegalStateException("Already Executed");

    executed = true;

  }

  try {

    client.dispatcher().executed(this);

    Response result = getResponseWithInterceptorChain(false);

    if (result == null) throw new IOException("Canceled");

    return result;

  } finally {

    client.dispatcher().finished(this);

  }

}

}


检查这个call是否已经被执行了,每个call 只能被执行一次

而enqueue实际上是RealCall的将内部类AsyncCall扔进了dispatcher中:client.dispatcher().enqueue(new AsyncCall(responseCallback));


public final class Dispatcher {

synchronized void enqueue(AsyncCall call) {

  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {

    runningAsyncCalls.add(call);

    executorService().execute(call);

  } else {

    readyAsyncCalls.add(call);

  }

}


。AsyncCall实际上是一个Runnable,我们看一下进入线程池后真正执行的代码:


final class AsyncCall extends NamedRunnable

public abstract class NamedRunnable implements Runnable

AsyncCall 实现NameRunnable接口,丹NameRunnable又实现了Runnable接口


 @Override protected void execute() {

    boolean signalledCallback = false;

    try {

      Response response = getResponseWithInterceptorChain(forWebSocket);

      if (canceled) {

        signalledCallback = true;

        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));

      } else {

        signalledCallback = true;

        responseCallback.onResponse(RealCall.this, response);

      }

    } catch (IOException e) {

      if (signalledCallback) {

        // Do not signal the callback twice!

        logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);

      } else {

        responseCallback.onFailure(RealCall.this, e);

      }

    } finally {

      client.dispatcher().finished(this);

    }

  }

}

 client.dispatcher().finished(this);这里边this代表着AsycCall

于是这里需要介绍一个Dispatcher的概念。Dispatcher的本质是异步请求的管理器,控制最大请求并发数和单个主机的最大并发数,并持有一个线程池负责执行异步请求。

对同步的请求只是用作统计。

他是如何做到控制并发呢,

其实原理就在上面的execute代码里面,

真正网络请求执行前后会调用executed和finished方法,执行和完成

而对于AsyncCall的finished方法后,

会根据当前并发数目选择是否执行队列中等待的AsyncCall。

并且如果修改Dispatcher的maxRequests或者maxRequestsPerHost也会触发这个过程。最大请求主机

好的,

在回到RealCall中,我们看到无论是execute还是enqueue,真正的Response是通过这个函数getResponseWithInterceptorChain获取的,

其他的代码都是用作控制与回调。而这里就是真正请求的入口,也是到了OkHttp的一个很精彩的设计:Interceptor与Chain


上面分析到了,网络请求的入口实质上是在这里getResponseWithInterceptorChain


private Response getResponseWithInterceptorChain() throws IOException {

 // Build a full stack of interceptors.                                                                                                    //构建一个完整的拦截器堆栈  List 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {

  interceptors.addAll(client.networkInterceptors());

 }

 interceptors.add(new CallServerInterceptor(

    5.isForWebSocket()));


 Interceptor.Chain chain = new RealInterceptorChain(

    interceptors, null, null, null, 0, originalRequest);

 return chain.proceed(originalRequest);

}




下面这是OkHttp内置拦截器的思维导图↓





RetryAndFollowUpInterceptor    后继拦截器



@Override public Response intercept(Chain chain) throws IOException {

  Request request = chain.request();


  streamAllocation = new StreamAllocation(

      client.connectionPool(), createAddress(request.url()), callStackTrace);

后继拦截器 拦截

在OkHttp的RetryAndFollowUpInterceptor请求重定向的Interceptor中是根据当前请求获取的Response,来决定是否需要进行重定向操作。在followUpRequest方法中,将会根据响应userResponse,获取到响应码,并从连接池StreamAllocation中获取连接,然后根据当前连接,得到路由配置参数Route。观察以下代码可以看出,这里通过userResponse得到了响应码responseCode。



接下来该方法会通过switch…case…来进行不同的响应码处理操作。

从这段代码开始,都是3xx的响应码处理,这里就开始进行请求重定向的处理操作。





重试与重定向拦截器,用来实现重试和重定向功能,内部通过while(true)死循环来进行重试获取Response(有重试上限,超过会抛出异常)。followUpRequest主要用来根据响应码来判断属于哪种行为触发的重试和重定向(比如未授权,超时,重定向等),然后构建响应的Request进行下一次请求。当然,如果没有触发重新请求就会直接返回Response。



当网络连接失败时重试

当服务器返回当前请求需要进行重定向是直接发起新的去请求,并在条件允许的情况下复用此链接。

主要做了三件事StreamAllocation:流量分配


[if !supportLists] [endif]创建了StreamAllocation,用于Socket管理

[if !supportLists] [endif]处理重定向

[if !supportLists] [endif]失败重连

BridgeInterceptor     桥接拦截器


在请求阶段自动补全请求头,在响应阶段对GZIP进行解压缩

就是告诉服务器客户端能够接受的数据编码类型,OKHTTP默认就是 GZIP 类型



GZIP:

GZIP是网站压缩加速的一种技术,对于开启后可以加快我们网站的打开速度,原理是经过服务器压缩,客户端浏览器快速解压的原理,可以大大减少了网站的流量。

Gzip开启以后会将输出到用户浏览器的数据进行压缩的处理,这样就会减小通过网络传输的数据量,提高浏览的速度。

是一种流行的文件压缩算法,现在的应用十分广泛,尤其是在Linux平台。当应用Gzip压缩到一个纯文本文件时,效果是非常明显的,大约可以减少70%以上的文件大小。这取决于文件中的内容。

BridgeInterceptor 功能主要有以下三点:

是负责将用户构建的一个Request请求转化为能够进行网络访问的请求。



将这个符合网络请求的Request进行网络请求。


Response networkResponse = chain.proceed(requestBuilder.build());

将网络请求回来的响应Response转化为用户可用的 Response



Transfer-Encoding值为 chunked 表示请求体的内容大小是未知的。



Host请求的 url 的主机



Connection默认就是 "Keep-Alive",就是一个 TCP 连接之后不会关闭,保持连接状态。



Accept-Encoding默认是 "gzip" 告诉服务器客户端支持 gzip 编码的响应。



Cookie当请求设置了 Cookie 那么就是添加 Cookie 这个请求头。



User-Agent "okhttp/3.4.1"这个值根据 OKHTTP 的版本不一样而不一样,它表示客户端 的信息。



上面就是将一个普通的Request添加很多头信息,让其成为可以发送网络请求的 Request 


CacheInterceptor缓存拦截器

CacheInterceptor负责在request阶段判断是否有缓存,是否需要重新请求。在response阶段负责把response缓存起来


缓存其实是一个非常复杂的逻辑,单独的功能模块,它其实不属于OkHttp上的功能,只是通过Http协议和DiskLruCache做了处理而已。


DiskLruCache是Android提供的一个管理磁盘缓存的类。该类可用于在程序中把从网络加载的数据

保存到磁盘上作为缓存数据,例如一个显示网络图片的gridView,可对从网络加载的图片进行缓存,

提高程序的可用性。



刚才也提到了OkHttp的缓存拦截器不是属于OkHttp上的功能,他是通过Http协议和DiskLruCache做的处理  一张图来看一下缓存策略↓



ETag响应头部字段值是一个实体标记,它提供一个“不透明”的缓存验证器


两种缓存的区别:

对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行对比缓存策略。

对于对比缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。


HTTP的缓存规则是优先考虑强制缓存,然后考虑对比缓存。

[if !supportLists] [endif]首先判断强制缓存中的数据的是否在有效期内。如果在有效期,则直接使用缓存。如果过了有效期,则进入对比缓存。

[if !supportLists] [endif]在对比缓存过程中,判断ETag是否有变动,如果服务端返回没有变动,说明资源未改变,使用缓存。如果有变动,判断Last-Modified。

[if !supportLists] [endif]判断Last-Modified,如果服务端对比资源的上次修改时间没有变化,则使用缓存,否则重新请求服务端的数据,并作缓存工作。


CacheStrategy缓存策略

直接看CacheStrategy的get方法。缓存策略是由请求和缓存响应共同决定的。

[if !supportLists] [endif]如果缓存响应为空,则缓存策略为不使用缓存。

[if !supportLists] [endif]如果请求是https但是缓存响应没有握手信息,同上不使用缓存。

[if !supportLists] [endif]如果请求和缓存响应都是不可缓存的,同上不使用缓存。

[if !supportLists] [endif]如果请求是noCache,并且又包含If-Modified-Since或If-None-Match,同上不使用缓存。

[if !supportLists] [endif]然后计算请求有效时间是否符合响应的过期时间,如果响应在有效范围内,则缓存策略使用缓存。

[if !supportLists] [endif]否则创建一个新的有条件的请求,返回有条件的缓存策略。

[if !supportLists] [endif]如果判定的缓存策略的网络请求不为空,但是只使用缓存,则返回两者都为空的缓存策略。



CacheInterceptor的总体流程大致是: 

推荐阅读更多精彩内容

  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 78,744评论 25 511
  • OkHttp源码分析-同步篇 很早就想拿okhttp开刀了,这次就记一次使用OKhttp的网络请求。首先需要说明的...
    埃赛尔阅读 597评论 1 2
  • OkHttp作为时下最受欢迎的网络请求框架之一,它有着自己的优点: 使用了众多的设计模式(如:Builder模式、...
    allenjones_23阅读 48评论 1 1
  • 一、Okhttp的使用 1、创建一个OKHttpClient对象 2、创建一个request对象,通过内部类Bui...
    千涯秋瑟阅读 108评论 0 0
  • 好教师不仅仅是上好课那么简单,还应做一个超越者。 通过校本研修 引领教师做到三个超越 : 1、超越课堂界限,把教育...
    郑国永阅读 19评论 0 0