OKHttp3源码解析

本文深入剖析OKHttp的源码实现,重点讲解任务调度、拦截器机制、缓存策略及连接池复用策略。揭示Dispatcher如何调度请求,AsyncCall如何执行,以及CacheInterceptor中的缓存逻辑。
摘要由CSDN通过智能技术生成

OKHttp的源码框架很大,涉及的内容也很多,每个类每行代码都去读不实际,这里主要从以下几个点去研究它的源码实现.

  • 任务调度过程
  • 拦截器
  • 缓存机制
  • 连接池复用策略

任务调度器Dispatcher

直接先看源码入口:

 //将request传给RealCall,然后将RealCall放入Dispatcher去调度
 okHttpClient.newCall(request).enqueue(this);
 

newCall(request):

public Call newCall(Request request) {
   return new RealCall(this, request, false);
}

RealCall.enqueue():

public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    //将这个请求放入dispatcher去调度,因为是异步请求,所以封装成AsyncCall,如果是同步请求,直接将RealCall本身传给Dispatcher, Dispatcher要么直接触发这个请求,要么就将它添加到等待队列中
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

其实可见,Dispatcher才是调度的角色.

Dispatcher的默认线程池

无界线程池,适合执行执行大量的耗时比较少的任务:

 public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

Dispatcher异步调度enqueue()

//当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时则把请求加载到runningAsyncCalls中并在线程池中执行,否则就再入到readyAsyncCalls中进行缓存等待。
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

AsyncCall异步任务执行过程

由于AsyncCall本身是一个Runnable,将它放入线程池执行,其实就是调用它的excute()方法:

protected void execute() {
      boolean signalledCallback = false;
      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) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

1.首先从拦截链中获取Response

Response getResponseWithInterceptorChain() throws IOException {

    List<Interceptor> interceptors = new ArrayList<>();
    //这里依次添加调用者的拦截器、重试拦截器、Bridge拦截器、缓存拦截器、连接拦截器、网络拦截器、CallServer拦截器,这是一个责任链模式
    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));
	//将请求添加到拦截器链中去处理,每个拦截器的处理结果是要么直接处理后返回Response,要么处理后,再将request往下传递,直至到CallServerInterceptor拦截器终结.
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

2.拦截器链的proceed()处理,假设第一个拦截器就是retryAndFollowUpInterceptor

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    //httpCodec为空
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    //httpCodec为空
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

 	//new一个RealInterceptorChain用于处理下一个拦截器
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next); //这里内部会一直传递下去,直至拿到最终的Response结果.

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

拦截器链的处理很简单,就是取出interceptor,然后调用它的intercept()方法,返回Response.所以最终的处理其实还是在拦截器,详细过程如图所示:

在这里插入图片描述

缓存机制

缓存策略主要是在CacheInterceptor中实现,具体代码如下所示:

public Response intercept(Chain chain) throws IOException { 
  //判断该request是否有缓存,缓存使用DiskLruCache来实现,request的url的MD5作为缓存key,
  Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;

    long now = System.currentTimeMillis();

	 //获取缓存策略对象CacheStrategy,根据request设置的缓存策略创建具体的CacheStrategy.例如如果不允许缓存,则直接返回一个不包含缓存结果的CacheStrategy
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    
    Request networkRequest = strategy.networkRequest; //request
    Response cacheResponse = strategy.cacheResponse; //缓存response

    ...
    //networkRequest为空只有一种情况,即request设置了不允许使用网络,即调用者在确实不需要网络请求,缓存就能够满足需求时设置.
    
    //1. 如果此时缓存也为空,那么直接new一个Response并设置相关参数返回给上层即可.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    //如果此时缓存不为空,则将缓存Response返回给上层.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
	
	 //2.如果没有设置不允许使用网络,那么就直接将request传给下层拦截器去处理
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // 关闭IO流
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //3.获取到下层返回的结果之后,再做以下判断
    if (cacheResponse != null) {
       //响应码是304,说明cache的数据是最新的,服务器此时不会返回具体的响应体,此时取出缓存,更新请求时间、接受时间等相关信息,再更新缓存,并将结果返回给上层即可.
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        cache.trackConditionalCacheHit();
        //更新缓存数据
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

	//4.如果返回码不是304,说明请求结果有更新,此时将缓存结果、新结果封装给Response返回给上层
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        //5.如果缓存策略是可缓存的,则将结果存入缓存
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
		//如果不能被缓存,则将缓存中对于数据删除.
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;

所以可以总结为,缓存策略为:

  1. 若request设置了不能请求网络,则先从缓存中获取结果,如果有,则返回缓存结果,如果没有,则返回空数据以及504错误;
  2. 否则直接请求网络结果;
  3. 拿到网络结果后,判断响应码为304,如果是,说明缓存为最新数据,则只要更新缓存数据的请求时间、响应时间等信息,再将缓存数据返回给上层;否则就直接返回网络请求结果.
  4. 再判读request是否可缓存的,是则将结果写入缓存,否则删除缓存,最后将结果返回给上层.

连接池复用策略

使用一个ConnectionPool去维护一个连接池,避免每个连接都要重新去执行TCP连接,握手等阶段这些耗时操作.

首先看下ConnectInterceptor中:

public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //这个StreamAllocation在retryAndFollowUpInterceptor中创建,顾名思义,就是分配流的一个类
    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的newStream去获取一个HttpCodec,再去看看这里面做了什么东西
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

streamAllocation.newStream(client, doExtensiveHealthChecks):

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
	 //首先获取连接的各个参数例如读写连接等超时时间
    int connectTimeout = client.connectTimeoutMillis();
    int readTimeout = client.readTimeoutMillis();
    int writeTimeout = client.writeTimeoutMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      //然后去找到一个可以使用的连接,可见复用连接的重点在这里
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      
      //3.最后获取连接的HttpCodec对象,它是用来解析Http请求,读取输入流,输出流,并封装Response的一个类
      HttpCodec resultCodec = resultConnection.newCodec(client, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

findHealthyConnection():

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
      
    while (true) {
      //这里是一个死循环,去寻找连接
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);
	   ...	
  }

findConnection():

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,boolean connectionRetryEnabled) throws IOException {
    Route selectedRoute;
    synchronized (connectionPool) {
     //异常判断
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      // 1. 首先获取StreamAllocation已分配的connection,判断是否不为空且允许复用,合法则可以直接返回该连接用于此次请求复用
      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
         return allocatedConnection;
      }

      // 2.如果不存在已分配的连接,则以空route从连接池里面取一个,如果能够取到,则直接return该连接
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        return connection;
      }
		//如果取不到,则将route赋值给selectedRoute
      selectedRoute = route;
    }

    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
    }
	 
    RealConnection result;
    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

	  //再次以route去尝试获取一个连接
      Internal.instance.get(connectionPool, address, this, selectedRoute);
      if (connection != null) {
        route = selectedRoute;
        return connection;
      }

      //3.如果找不到,则创建一个新的连接
      route = selectedRoute;
      refusedStreamCount = 0;
      result = new RealConnection(connectionPool, selectedRoute);
      acquire(result);
    }

    //4.并且为该连接建立TCP连接以及TLS握手
    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      //将该连接放入连接池
      Internal.instance.put(connectionPool, result);
      //如果另外同时创建了同一个指向的连接,那么就复用该连接,release这个连接
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    return result;
  }

可见,每次连接都是首先尝试去复用连接,而非每次新建连接,那么能够省去很多连接建立的时间,大大提高效率.新建了连接之后,会将它存储在连接池,实际上存于一个ArrayDeque中,当然连接池不可能无限大,ConnectionPool内部有一个清理线程去管理这个线程池,每隔一段时间去清理闲置时间最长的连接又或者是超过了keep-alive时间的连接又或者是超过了闲置限制的连接,连接池默认最大闲置连接是5个,keep-alive是5分钟.当超过了限制时,清理线程会根据连接的闲置时间或者数目进行清理,避免浪费连接资源.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值