OkHttp3源码解析 - 请求流程

系列文章目录

第一章 OkHttp3源码解析 - 请求流程
第二章 OkHttp3源码解析 - 拦截器
第三章 OkHttp3源码解析 - 连接机制和缓存机制



前言

在Android蛮荒年代,我们做网络请求通常会选用HttpURLConnection或者Apache HTTP Client,这两者均支持HTTPS、流的上传和下载、配置超时和连接池等特性,但随着业务场景的复杂化以及对流量消耗的优化需求,OkHttp应运而生。

现在谈起网络请求,大家肯定下意识想到的就是 okHttp 或者 retrofit 这样的三方请求库。 Google官方 也将源码当中的 HttpURLConnection 底层实现改成 okhttp 了,同时 retrofit 的底层也是 okhttp,足以说明其在日常开发中的重要性。

官方网站:https://github.com/square/okhttp

本文基于okhttp3.12.13源码进行分析

api 'com.squareup.okhttp3:okhttp:3.12.13'

技能树:Builder建造者模式、责任链模式、http、线程池、库里面对代码的封装和组合等。


一、OkHttp的基本使用流程

// 1.创建client
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
        .cookieJar(CookieJar.NO_COOKIES)
        .callTimeout(10000, TimeUnit.MILLISECONDS)
        .build();
        
// 2.创建request
Request request = new Request.Builder()
        .url("http://10.34.12.156:68080/admin-api")
        .addHeader("Content-Type", "application/json")
        .get();
        .build();

// 3.发起请求
okHttpClient.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {// 失败回调
    
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {// 成功回调

    }
});

核心对象:

  • OkHttpClient
  • Request
  • Call

OkHttpClient 和 Request 都是通过建造者模式构建的,这样的好处就在于用户可以根据自己的需求轻松简洁的配置一些可选参数,而不必通过传统方式将不需要的参数写成 null 。

二、OkHttp架构

从上述使用示例中,我们可以发现 OkHttpClient相当于是个上下文或者说是大管家,它接到我们给的任务以后,将具体的工作分发到各个子系统中去完成。Okhttp的子系统层级结构图如下所示:
在这里插入图片描述

  • 网络配置层:利用Builder模式配置各种参数,例如:超时时间、拦截器等,这些参数都会由Okhttp分发给各个需要的子系统。
  • 重定向层:负责重定向。
  • Header拼接层:负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。
  • HTTP缓存层:负责读取缓存以及更新缓存。
  • 连接层:连接层是一个比较复杂的层级,它实现了网络协议、内部的拦截器、安全性认证,连接与连接池等功能,但这一层还没有发起真正的连接,它只是做了连接器一些参数的处理。
  • 数据响应层:负责从服务器读取响应的数据。

在整个Okhttp的系统中,我们还要理解以下几个关键角色:

  • OkHttpClient:通信的客户端,用来统一管理发起请求与解析响应。
  • Call:Call是一个接口,它是HTTP请求的抽象描述,具体实现类是RealCall,它由CallFactory创建。
  • Request:请求,封装请求的具体信息,例如:url、header等。
  • RequestBody:请求体,用来提交流、表单等请求信息。
  • Response:HTTP请求的响应,获取响应信息,例如:响应header等。
  • ResponseBody:HTTP请求的响应体,被读取一次以后就会关闭,所以我们重复调用responseBody.string()获取请求结果是会报错的。
  • Interceptor:Interceptor是请求拦截器,负责拦截并处理请求,它将网络请求、缓存、透明压缩等功能都统一起来,每个功能都是一个Interceptor,所有的Interceptor最终连接成一个Interceptor.Chain。典型的责任链模式实现。
  • StreamAllocation:用来控制Connections与Streas的资源分配与释放。
  • RouteSelector:选择路线与自动重连。
  • RouteDatabase:记录连接失败的Route黑名单。

我们首先来分析连接的请求与响应流程,这样我们就可以对整个OkHttp系统有一个整体的认识。

三、请求与响应流程

1.请求与响应流程概览

OkHttp的整个请求与响应流程如下所示:
在这里插入图片描述
Okhttp的整个请求与响应的流程就是Dispatcher不断从Request Queue里取出请求(Call),根据是否已经存在缓存,从内存缓存或者服务器获取请求的数据。

仔细看一下这个流程图,是不是很像计算机网络的OSI七层模型,Okhttp正式采用这种思路,利用拦截器Interceptor将整套框架纵向分层,简化了设计逻辑,提升了框架扩展性。

我们带着几个需要重点关注的问题,去源码中一探究竟,这样效率会更高。

Question:
1.Dispatcher是如何进行请求调度的?
2.各个拦截器是如何实现的?
3.连接与连接池是如何建立和维护的?

我们先来看一下具体的函数调用链,请求与响应的序列图如下所示:
在这里插入图片描述
OkHttp请求分为同步和异步两种,同步请求通过调用Call.exectute()方法直接返回当前请求的Response,异步请求调用Call.enqueue()方法将请求(AsyncCall)添加到请求队列中去,并通过回调(Callback)获取服务器返回的结果。

2.同步请求的执行流程

我们将 OkhttpClient 中的 newCall() 作为入口,开启整个同步请求的过程,

详情见OkHttpClient.java

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
  	// 这里是构建一个 RealCall 对象
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

RealCall 实现了 Call 接口,我们先来看看 Call 接口:

public interface Call extends Cloneable {
  // 同步请求方法  
  Response execute() throws IOException;

  // 异步请求方法
  void enqueue(Callback responseCallback);

  // OkHttpClient实现了Factory接口,所以才有newCall方法
  interface Factory {
    Call newCall(Request request);
  }
}

现在我们通过 newCall() 得到了一个 RealCall 对象,然后就能通过 RealCall 当中的 execute() 和 enqueue() 进行网络请求。

详情见:RealCall.java

@Override public Response execute() throws IOException {
    synchronized (this) {// // 一个call对象只能执行一次execute方法
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
     // 这里主要是个监听器,表示开始进行网络请求了
    eventListener.callStart(this);
    // 重点关注这块
    try {
      //通过分发器进行任务分发,其实这里还体现不出分发器的效果,仅仅是将当前请求加入到一个同步队列当中
      client.dispatcher().executed(this);
      // 通过 getResponseWithInterceptorChain() 获得相应结果
      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 {
    // 完成一些收尾工作,在同步请求中,几乎没什么用
      client.dispatcher().finished(this);
    }
  }

现在来看看同步请求中分发器做了什么工作呢?

Dispatcher.java

public final class Dispatcher {
	// 正在执行的同步请求队列
	private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

	// 简单的将当前请求加入到同步请求队列中
	synchronized void executed(RealCall call) {
      runningSyncCalls.add(call);
    }
	
	// finish方法 --> 前面说的首尾工作方法,但是在同步请求中用处不大
	void finished(RealCall call) {
    	finished(runningSyncCalls, call);
  	}

  private <T> void finished(Deque<T> calls, T call) {
  	 顾名思义,分发器空闲时得回调
    Runnable idleCallback;
    synchronized (this) {
      // 对于完成请求的 call ,在这里移除掉
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }
    
	//这里是进行收尾工作的,主要体现在异步请求中,因此这个方法先放一放
    boolean isRunning = promoteAndExecute();

	//进行空闲回调方法
    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }
  
}

可以看到,在整个同步请求的过程中,分发器仅仅是将当前的请求加入到一个同步请求队列中,请求完成后再将其移除。因为在同步请求中 finished() 方法只有一个回调作用,因此我们将它放一放,重点看一看异步请求中的 finished()。

3.异步请求的执行流程

实际业务中,异步请求往往较多。异步请求就比同步请求稍微复杂了一点,我们仍然是从 RealCall 中看起。

RealCall.java

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {//一个call对象只能执行一次execute方法 
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    
    // 这里面依旧加上了监听
    eventListener.callStart(this);

	// 构建一个 AsyncCall对象,再交给dispatcher进行分发流程
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

AsyncCall 是 RealCall 的内部类, 它实现了 Runnable 接口,主要是为了能在线程池中去执行它的 run() 方法。

AsyncCall.java

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

	// 将asyncCall添加到线程池中去执行的方法
    void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
      	// 线程池去执行当前AsyncCall对象的run方法
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
         // 收尾工作,其实内部调用的是 promoteAndExecute()
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      timeout.enter();
      try {
      	// getResponseWithInterceptorChain() 获得请求的响应结果
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
         // 请求成功的回调
        responseCallback.onResponse(RealCall.this, response);
      } catch (IOException e) {
        e = timeoutExit(e);
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } catch (Throwable t) {
        cancel();
        if (!signalledCallback) {
          IOException canceledException = new IOException("canceled due to " + t);
          // 请求失败的回调
          responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
      } finally {
        // 进行收尾工作,相比同步请求的finished方法,这儿更重要
        client.dispatcher().finished(this);
      }
    }
  }

现在我们回到 Dispatcher 中去。

Dispatcher.java

public final class Dispatcher {
	// 准备执行的异步请求队列
	private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
	// 正在执行的异步请求队列
	private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
	
	void enqueue(AsyncCall call) {
	    synchronized (this) {
	      // 添加当前asyncCall加到准备执行的异步请求队列中
	      readyAsyncCalls.add(call);
	    }
	    // dispatcher进行分发call任务的方法
	    promoteAndExecute();
  	}

	// 关键方法,dispatcher进行任务分发的方法,进行收尾工作时,也是调用的它
	private boolean promoteAndExecute() {
	    assert (!Thread.holdsLock(this));
	
		// 需要开始执行的任务集合
	    List<AsyncCall> executableCalls = new ArrayList<>();
	    boolean isRunning;
	    synchronized (this) {// 迭代等待执行异步请求
	      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
	        AsyncCall asyncCall = i.next();
			// 正在执行异步请求的总任务数不能大于64个,否则直接退出这个循环,不再将请求加到异步请求队列中
	        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
	        // 同一个host的请求数不能大于5,否则直接跳过此call对象的添加,去遍历下一个asyncCall对象
	        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
	
	        i.remove();
	        // 加到需要开始执行的任务集合中
	        executableCalls.add(asyncCall);
			// 将当前call加到正在执行的异步队列当中
	        runningAsyncCalls.add(asyncCall);
	      }
	      isRunning = runningCallsCount() > 0;
	    }
	
	    for (int i = 0, size = executableCalls.size(); i < size; i++) {
	      // 遍历每一个集合中的asyncCall对象,将其添加到线程池中,执行它的run方法
	      AsyncCall asyncCall = executableCalls.get(i);
	      asyncCall.executeOn(executorService());
	    }
	
	    return isRunning;
	  }

}

现在我们通过 Dispatcher 将 AsyncCall 对象通过挑选,加到了线程池中。挑选的限制有两个:

  1. 当前执行的总请求数要小于64个。
  2. 对于连接的同一个host请求,要保证数量小于5。
//Dispatcher.java
private int maxRequests = 64;
private int maxRequestsPerHost = 5;

接下来,我们再回头看看将 AsyncCall 对象加到线程池后的一些细节:

//Dispatcher.java

// 将asyncCall添加到线程池中去执行的方法
private boolean promoteAndExecute() {
	for(...){
		asyncCall.executeOn(executorService());
	}
}

void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
      	// 这里是之前自定义了创建了一个ExecutorService
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          // 这里也是会执行收尾工作
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
}

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      //这里的corePoolSize是 0 !!这里的corePoolSize是 0
      // !!阻塞队列是 SynchronousQueue!!阻塞队列是 SynchronousQueue
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

Dispatcher再次分发

我们先来看 executeOn() 方法,它的主要工作就是执行添加到线程池的 AsyncCall 对象的 run() 方法,去进行网络请求。其次我们目光移动到 finally 语句块,会发现每次执行完 run() 方法后,即完成网络请求后,都会去执行这个 finished() 方法。前面讲到过,内部其实是再次调用了 promoteAndExecute() 方法。那这是为什么呢?

还记得到我们从准备执行的异步队列中挑选一些 AsyncCall 对象拿到线程池中执行吗?如果记得,那你是否还记得我们是有挑选条件的,正因如此,可能在准备执行的异步请求队列中会有一些 AsyncCall 对象不满足条件仍然留在队列里!那我们难道最后就不执行这些网络请求了吗?当然不是!原来每完成一次网络请求就会再次触发 Dispatcher 去分发 AsyncCall 对象!

corePoolSize 为0线程池设计

然后我们再来看看这里用到的线程池是一个什么样的线程池。在上面我贴出来的代码中可以看到,这个线程池的 corePoolSize 是 0,BlockingQueue 是 SynchronousQueue,这样构建出来的线程池有什么特殊之处吗?熟悉线程池的同学都应该知道,当任务数超过了 corePoolSize 就会将其加到阻塞队列当中。也就是说这些任务不会立马执行,而我们的网络请求可不想被阻塞着,因此这里的 corePoolSize 就设置成了 0。BlockingQueue 设置成 SynchronousQueue 也是类似道理,SynchronousQueue 是不储存元素的,只要提交的任务数小于最大线程数就会立刻新起线程去执行任务。

4.okhttp网络请求执行过程总结

总结一下整个 okhttp 网络请求的整个过程:

  1. 首先通过我们通过 构造者 的方式构建好了 OkHttpClient 和 Request 对象,
  2. 然后调用 OkHttpClient 对象的 newCall() 方法得到一个 RealCall 对象,
  3. 再调用其 execute() 或者 enqueue() 方法发起同步或者异步请求,
  4. 通过Dispatacher 分发器去筛选并执行请求。
    在这里插入图片描述
    如果是同步请求,Dispatacher 分发器去只是简单地将其加入到正在执行的同步请求队列中做一个标记,如果是异步请求就会根据 两个条件 去筛选合适的请求,并将其分发给一个特定的线程池中去进行网络请求,最后通过 getResponseWithInterceptorChain() 得到最终结果。

四、Interceptor拦截器

okhttp的又一大特点是整个请求流程是由拦截器一层层分发下去,最后得到结果再一层层返回上来。 如图:
在这里插入图片描述
okhttp 内置了五大拦截器,这五大拦截器各司其职,通过责任链模式将请求逐层分发下去,每层完成自己这层该做的事,最后拿到相应结果逐层往上返回结果:

  1. RetryAndFollowUpInterceptor:重试重定向拦截器,负责失败重试以及重定向。
  2. BridgeInterceptor:桥接拦截器,负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。
  3. CacheInterceptor:缓存拦截器,负责读取缓存以及更新缓存。
  4. ConnectInterceptor:连接拦截器,负责与服务器建立连接。
  5. CallServerInterceptor:服务请求拦截器,负责从服务器读取响应的数据。

注意:这五个是系统内置的拦截器,我们也可以通过 addInterceptor() 加入我们自己写的拦截器.
关于拦截器,详细可看 第二章 OkHttp3源码解析 - 拦截器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值