Android干货Android 面试集OKHTTP

OKHTTP异步和同步请求简单分析

2017-07-03  本文已影响2696人  未见哥哥

OKHTTP异步和同步请求简单分析
OKHTTP拦截器缓存策略CacheInterceptor的简单分析
OKHTTP拦截器ConnectInterceptor的简单分析
OKHTTP拦截器CallServerInterceptor的简单分析
OKHTTP拦截器BridgeInterceptor的简单分析
OKHTTP拦截器RetryAndFollowUpInterceptor的简单分析
OKHTTP结合官网示例分析两种自定义拦截器的区别

同步请求就是执行请求的操作是阻塞式,直到 HTTP 响应返回。它对应 OKHTTP 中的 execute 方法。

异步请求就类似于非阻塞式的请求,它的执行结果一般都是通过接口回调的方式告知调用者。它对应 OKHTTP 中的 enqueue 方法。

示例代码

下面的代码演示了如何进行同步和异步请求的操作。

OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
        .url("http://www.qq.com")
        .build();
Call call = okHttpClient.newCall(request);
//1.异步请求,通过接口回调告知用户 http 的异步执行结果
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        System.out.println(e.getMessage());
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (response.isSuccessful()) {
            System.out.println(response.body().string());
        }
    }
});
//2.同步请求
//Response response = call.execute();
//if (response.isSuccessful()) {
//    System.out.println(response.body().string());
//}

异步请求的基本原理

OKHTTP异步任务执行图解.png

Call

负责准备去执行一个 request 请求,一个 call 只能负责去执行一个请求,不能被执行两次。因为 OkHttpClient 是实现了 Call.Factory 因此它具备创建 Call 对象的功能,内部创建的就是 RealCall 对象。Call 是封装 request 的,它表示一个可以执行的请求。

Call 的实现类 RealCall

因为 Call 是接口,内部定义了同步与异步的请求,以及取消请求等操作,这些操作是由 RealCall 真正去实现的。

在 RealCall 中关键的几个属性:

Dispatcher 相关知识点

异步任务分发器,它会内部指定线程池去执行异步任务,并在执行完毕之后提供 finish 方法结束异步请求之后从等待队列中获取下一个满足条件的异步任务去执行。

1、在 Dispatcher 有几个比较重要的属性,这几个属性会影响异步请求的执行。

2、关于 Dispatcher 的功能在下面的异步和同步请求中我们再一一探索。

Call 的实现者 RealCall

它具备有异步和同步请求,还有取消请求的功能,它内部有一个 AsyncCall 内部类,在 Dispatcher 中分发的异步请求任务就是 AsyncCall 。这里分发的任务指的是异步任务,而不是同步任务。AsyncCall 就是用表示一个异步任务的,在 Dispatcher 内部有维护了两个队列来存储 AsyncCall,分别是 readyAsyncCalls 和
runningAsyncCalls 它们分别表示准备要执行的 AsyncCall 队列和正在执行的 AsycnCall 队列。当然还有一个 runningSyncCalls 这个队列,但是它适用于存放 RealCall ,也就是用于存储同步请求的任务。

@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    //检测该 call 是否被执行过了,如果已经执行了,那么就抛出异常
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
 //关键代码:将 AsycnCall 添加到队列中。将任务交给 Dispatcher 去执行。
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

合理性的校验操作,我们在介绍 Dispacther 的相关属性时已经说明,在 OKHTTP 中正在执行的请求不能超过 64 个,并且同一个主机不能超过 5 请求,当满足这两个条件,即可将任务添加到正在执行的队列 runningAsyncCalls 中,并且通知线程池安排线程去执行这个异步任务,否则就会被添加到等待队列中 readyAsyncCalls。

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    //当正在执行的请求小于64个&&该 call 对应的主机少于5个 Call 时
    //将任务添加到 runningAsycnCalls 中,标记为正在执行的任务。
    runningAsyncCalls.add(call);
    //在线程池中执行这个任务。
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

真正的异步执行者 AsyncCall

在前面提到了 AsyncCall 表示的是一个异步任务,在使用 Dispatcher 会将 AsyncCall 交给指定的线程去执行,而 AsyncCall 是 NamedRunnable 的子类,因此它也具备 Runnble 的特性,换句话说,在线程池中执行的任务就是 AsyncCall 了。

当线程池执行这个异步任务时,那么该 Runnable 的 run 方法就会被执行,我们查阅了源码,在 run 方法内部会去调用 AsyncCall 的 execute 方法。

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

总结该方法中它主要做了这 3 件事:

对于 AsyncCall 中所做的这 3 步中,前面两步都比较好理解,下面主要看看它是如何结束一个请求的并且开启下一个异步请求的?

我们在前面介绍 Dispacther 已经了解了它的作用,这里再次强调一下,它是负责去分发一个异步任务给指定的线程池去执行,并且可以在执行完毕之后去等待队列中获取下一个请求去执行。

在 AsyncCall 中的 execute 中执行一个异步请求,注意在 finally 块内部调用了 client.dispatcher().finished(this);它的作用是通知 Dispatcher 我的任务执行完毕了,你可以将我从集合中移除了,开启下一个异步任务吧。下面就是 finish 的源码:

private <T> void finished(Deque<T> 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();
  }
}

总结该方法中它主要做了这 3 件事:

通过 promoteCalls() 去执行下一个异步任务

该方法是用于在等待队列中获取下一个异步任务去执行。在内部会还是会对 Dispatcher 内部的几个属性进行判断,例如对正在执行的请求数量是否超过了 64 个,还有遍历等待队列里的所有的 AsyncCall ,每遍历出一个 AsycnCall 都校验它的主机是否有超过 5 个正在执行的异步请求在使用了,在满足条件的情况下,就马上会线程池去执行这个任务,以此类推,任务就这样一个一个的被执行。

private void promoteCalls() {
  if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
  if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
  for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
    AsyncCall call = i.next();
    if (runningCallsForHost(call) < maxRequestsPerHost) {
      //满足下一个要执行的任务的要求。
      i.remove();
      //添加到正在请求队列中
      runningAsyncCalls.add(call);
      //由线程池去执行这个任务。
      executorService().execute(call);
    }
    //超过了 64 个请求那么就直接 return
    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}

使用 RealCall 实现的同步请求

上面所描述的都是异步请求,现在来看看同步请求。

同步请求调用的是 execute 方法,在内部会调用 client.dispatcher().executed(this); 方法,进去看源码可知道它实际就是将 RealCall 添加到 Dispatcher 的 runningSyncCalls 中,表示当前正在执行的同步队列中。在这里使用 Dispacther 的中 execute 仅仅只是将其添加到集合中而已,没有作别的操作,而真正执行同步任务的核心代码是 getResponseWithInterceptorChain(); ,该方法负责去网络请求,并且得到一个响应,具体内部怎么实现日后再分析。在最后的 finally 代码块执行的功能跟异步任务一样,也是通过 Dispatcher 去 finish 该请求。

在 finish 中虽然同步和异步执行的方法是一样的,但是执行流程并不一样,异步任务需要通过 promoteCalls 去执行下一个异步任务,而同步请求是不需要的,这个的判断标记就 private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {...} 就是第三个参数,当该 promoteCalls 为 false 表示同步请求,true 表示异步请求,其他操作都是和异步请求是一样的。

@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();
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    client.dispatcher().finished(this);
  }
}
上一篇下一篇

猜你喜欢

热点阅读