Okhttp 源码学习 (一)- 同步和异步提交请求

2,959 阅读9分钟

Okhttp是一个支持HTTP和HTTP/2的客户端,可以在android和java应用中使用

  • 支持同步和异步请求,同步请求会阻塞当前线程,异步请求不会阻塞当前线程
  • 支持HTTP/2协议,可以让客户端中到同一服务器的所有请求共用同一个Socket链接
  • 如果请求不支持HTTP/2协议,那么okhttp会在内部维护一个连接池,通过该连接池,可以对HTTP/1.x的连接进行重用,减少延迟
  • 透明的GZIP处理降低了下载数据的大小
  • 请求的数据会进行相应的缓存处理

HTTP

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是互联网上应用最为广泛的一种网络协议。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。通过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。

以下是HTTP/2与HTTP/1.1的区别(面试经常问到)

Okhttp同步和异步请求

流程图:

核心类

OkHttpClient

OkHttpClient表示了HTTP请求的客户端类,在绝大多数的App中,我们只应该执行一次new OkHttpClient(),将其作为全局的实例进行保存,从而在App的各处都只使用这一个实例对象,这样所有的HTTP请求都可以共用Response缓存、共用线程池以及共用连接池。
可以配置OkHttpClient的一些参数,比如超时时间、缓存目录、代理、Authenticator等,那么就需要用到内部类OkHttpClient.Builder,参数设置完成后,调用Builder的build方法得到一个配置好参数的OkHttpClient对象

OkHttpClient client = new OkHttpClient.Builder()
        .readTimeout(30, TimeUnit.SECONDS)
        .cache(cache)
        .proxy(proxy)
        .authenticator(authenticator)
        .build();

Request

Request类封装了请求报文信息:请求的Url地址、请求的方法(如GET、POST等)、各种请求头(如Content-Type、Cookie)以及可选的请求体。一般通过内部类Request.Builder的链式调用生成Request对象。

Call

Request类封装了请求报文信息:请求的Url地址、请求的方法(如GET、POST等)、各种请求头(如Content-Type、Cookie)以及可选的请求体。一般通过内部类Request.Builder的链式调用生成Request对象。
执行Call对象的execute()方法,会阻塞当前线程去获取数据,该方法返回一个Response对象。 执行Call对象的enqueue()方法,不会阻塞当前线程,该方法接收一个Callback对象,当异步获取到数据之后,会回调执行Callback对象的相应方法。如果请求成功,则执行Callback对象的onResponse方法,并将Response对象传入该方法中;如果请求失败,则执行Callback对象的onFailure方法。

Response

Response类封装了响应报文信息:状态吗(200、404等)、响应头(Content-Type、Server等)以及可选的响应体。可以通过Call对象的execute()方法获得Response对象,异步回调执行Callback对象的onResponse方法时也可以获取Response对象。

Dispatcher

  • 同步
    Dispatcher会在同步执行任务队列中记录当前被执行过得任务Call,同时在当前线程中去执行Call的个体ResponseWithInter测评投入Chain()方法,直接获取当前的返回数据Response
  • 异步
    异步执行是通过Call.enqueue(Callback responseCallback)来执行,在Dispatcher中添加一个封装了Callback的Call的匿名内部类Runnable来执行当前 的Call。这里一定要注意的地方这个AsyncCall是Call的匿名内部类。AsyncCall的execute方法仍然会回调到Call的 getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。

Dispatcher类如下:

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;  
  /** Executes calls. Created lazily. */
  private ExecutorService executorService;  
  /** readyAsyncCalls队列用于当runningAsyncCalls的尺寸达到maxRequests参数时(默认64)存储新加的异步请求 */
  private final Deque readyAsyncCalls = new ArrayDeque<>();  
  /** runningAsyncCalls队列用于存储进入队列的异步请求AsyncCall对象 */
  private final Deque runningAsyncCalls = new ArrayDeque<>();  
  /** runningSyncCalls用于存储同步请求RealCall对象 */
  private final Deque runningSyncCalls = new ArrayDeque<>();  
  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }  
  public Dispatcher() {
  }  
  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }
  ...
}

同步请求的执行流程

同步请求,先创建一个Request对象,然后再创建一个Call对象,调用Call对象的execute方法即可。那么就从execute方法看起。Call是一个接口,具体实现是RealCall,下面是RealCall的execute方法实现:

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  try {
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    client.dispatcher().finished(this);
  }
}

首先是设置executed标志为true,同一个Call只允许执行一次,执行多次就会抛出异常。接下来是调用OkHttpClient的dispatcher()方法获得Dispatcher对象,然后调用其executed(RealCall)方法,然后就是调用getResponseWithInterceptorChain方法同步获取响应,最后调用Dispatcher的finished方法,下面先看executed方法:

/** Used by {@code Call#execute} to signal it is in-flight. */
 synchronized void executed(RealCall call) {
   runningSyncCalls.add(call);
 }

从代码中可以看出,Dispatcher的executed方法只是将同步请求加入到了runningSyncCalls队列中。下面再看finished方法:

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
  finished(runningSyncCalls, call, false);
}

从上面代码中,可以看到finished方法再调用另一个finished方法,并将runningSyncCalls队列传入,具体实现如下:

private  void finished(Deque calls, T call, boolean promoteCalls) {
  int runningCallsCount;
  Runnable idleCallback;
  synchronized (this) {
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    if (promoteCalls) promoteCalls();
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
  }  
  if (runningCallsCount == 0 && idleCallback != null) {
    idleCallback.run();
  }
}

首先是从队列中移除请求,如果不能移除,则抛出异常;在上面finished方法调用中看出传入第三个参数为false,所以不会调用promoteCalls方法,该参数用于异步请求时为true,这个下面分析异步请求时再讲。然后调用runningCallsCount统计目前还在运行的请求,最后,如果正在运行的请求数为0表示Dispatcher中没有可运行的请求了,进入Idle状态,此时如果idleCallback不为null,则调用其run方法。下面是runningCallsCount()方法的实现:

public synchronized int runningCallsCount() {
  return runningAsyncCalls.size() + runningSyncCalls.size();
}

可以看到这个方法返回的请求包括同步请求和异步请求。
至此,同步请求的执行流程分析完成,可以看到Dispatcher只是保存了一下同步请求和移除同步请求,而对于异步请求,Dispatcher的工作就不只是这么简单了。

异步请求的执行流程

我们知道如果要发起异步请求,那么就调用Call的enqueue方法并传入回调,依然从RealCall的enqueue方法看起:

@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

可以看到,依然是首先将executed参数设为true,同样地,异步请求也不可以被执行两次,然后调用Dispatcher的enqueue方法,但是这儿涉及到了一个新的类,AsyncCall。AsyncCall是RealCall的一个内部类并且继承NamedRunnable,那么首先看NamedRunnable类是什么样的,如下:

public abstract class NamedRunnable implements Runnable {
protected final String name;  
public NamedRunnable(String format, Object... args) {
  this.name = Util.format(format, args);
}  
@Override public final void run() {
  String oldName = Thread.currentThread().getName();
  Thread.currentThread().setName(name);
  try {
    execute();
  } finally {
    Thread.currentThread().setName(oldName);
  }
}  
protected abstract void execute();
}

可以看到NamedRunnable实现了Runnbale接口并且是个抽象类,其抽象方法是execute(),该方法是在run方法中被调用的,这也就意味着NamedRunnable是一个任务,并且其子类应该实现execute方法。下面再看AsyncCall的实现:

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;
  }  
  @Override 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);
    }
  }
}

AsyncCall实现了execute方法,首先是调用getResponseWithInterceptorChain()方法获取响应,然后获取成功后,就调用回调的onReponse方法,如果失败,就调用回调的onFailure方法。最后,调用Dispatcher的finished方法。
由于AsyncCall的execute()方法是在run中被调用的,所以getResponseWithInterceptorChain是在非调用线程中被调用的,然后得到响应后再交给Callback。
从上面的流程看出,与Dispatcher的交互主要涉及enqueue方法和finished方法,与同步请求类似。下面先看enqueue方法:

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

首先如果正在运行的异步请求的数量小于maxRequests并且与该请求相同的主机数量小于maxRequestsPerHost,也就是说符合放入runningAsyncCalls队列的要求,那么放入队列,然后将AsyncCall交给线程池;如果不符合,那么就放入到readyAsyncCalls队列中。
当线程池执行AsyncCall任务时,它的execute方法会被调用,getResponseWithInterceptorChain()会去获取响应,最后调用Dispatcher的finished方法,下面看finished方法:

/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
  finished(runningAsyncCalls, call, true);
}

从上面的代码可以看出,与同步请求的finished方法不同的是第一个参数传入的是正在运行的异步队列,第三个参数为true,下面再看有是三个参数的finished方法:

private  void finished(Deque calls, T call, boolean promoteCalls) {
  int runningCallsCount;
  Runnable idleCallback;
  synchronized (this) {
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    if (promoteCalls) promoteCalls();
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
  }  
  if (runningCallsCount == 0 && idleCallback != null) {
    idleCallback.run();
  }
}

与同步请求相同的是,移除请求,获取运行数量判断是否进入了Idle状态,不同的是会调用promoteCalls()方法,下面是promoteCalls()方法:

private void promoteCalls() {
  if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
  if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.  
  for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
    AsyncCall call = i.next();  
    if (runningCallsForHost(call) < maxRequestsPerHost) {
      i.remove();
      runningAsyncCalls.add(call);
      executorService().execute(call);
    }  
    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}

总结

至此,分析完了同步请求和异步请求的提交流程,Dispatcher负责异步请求是放入运行队列还是等待队列中,并且在每个异步请求执行完后,需要判断是否需要把等待队列中的请求移到运行队列中并运行。不管是同步请求还是异步请求,最终都会调用getResponseWithInterceptorChain()方法进行具体的网络请求,该方法下篇博客Okhttp源码学习(二)—责任链模式获取响应会具体介绍。

本文标题:Okhttp源码学习(一)-同步和异步提交请求

文章作者:Hpw123

发布时间:2016-12-28, 19:56:01

最后更新:2016-12-29, 01:19:46

原始链接:hpw123.coding.me/2016/12/28/…

许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。