OkHttp 源码学习(一) 请求与调度

下面放一下okhttp 的基本使用,在项目中 一般okhttpclient 都是单例使用, 测试代码如下

kotlin 复制代码
fun test(){
    var client=OkHttpClient.Builder()
        .connectTimeout(5*1000,TimeUnit.SECONDS)
        .addInterceptor {
                chain -> chain?.proceed(chain.request())
        }
        .addNetworkInterceptor {
            it.proceed(it.request())
        }
        .build()


    var call=Request.Builder()
        .url("http://www.baidu.com")
        .get().build()

    var realCall = client.newCall(call)
    realCall.enqueue(object : Callback{
        override fun onFailure(call: Call?, e: IOException?) {
        }

        override fun onResponse(call: Call?, response: Response?) {
        }
    })
}

这段代码中添加了两个拦截器 一个是 addInterceptor 另外一个是 addNetworkInterceptor , 这是一个比较重要逻辑,我们先记录一下

整体的分析从 client.newCall(call) 开始,下面开始上代码

typescript 复制代码
@Override public Call newCall(Request request) {
  return new RealCall(this, request);
}

在 newCall 的时候,创建了一个 RealCall ,这个RealCall 实现自 Call

csharp 复制代码
public interface Call {
  Request request();
  Response execute() throws IOException;
  void enqueue(Callback responseCallback);
  void cancel();
  boolean isExecuted();
  boolean isCanceled();
  interface Factory {
    Call newCall(Request request);
  }
}

Call 当中有两个比较重要的方法,一个是 execute ,另外一个是 enqueue 方法 ,其中 execute 是 同步请求方法, enqueue 是异步请求方法,由于 execute 逻辑简单,这里就分析一下RealCall.enqueue 这个方法

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

从方法中看到 这里使用了对象锁, 如果这个RealCall 已经被执行了,那么他不能再次被使用,然后在标记完成后使用使用 AsyncCall 包装一下 callback , 并且使用 client.dispatcher 调度 这个请求 ,下面继续分析 Dispatcher 的 enqueue 方法

scss 复制代码
synchronized void enqueue(AsyncCall call) {
    //todo 同时异步请求不能超过64个,同一个host 的请求不能超过5个
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

我在注释里面标记了这个方法的主要逻辑, 同时异步请求不能超过64个,同一个host 的请求不能超过5个,讲到这里就必须说一下 Dispatcher 中的任务队列

swift 复制代码
/** 异步请求等待队列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** 异步请求执行队列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** 同步请求队列 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

在 Dispatcher 中一共存在 3 个队列, 2个是 异步队列 , 异步队列包括等待执行队列和正在执行的队列,当我们向 Dispatcher 中添加异步任务的时候, 他会判断上面的逻辑,如果超出那么将任务放入等待队列中,

如果满足上述条件,就会使用 线程池来调度任务, 这里的线程池创建的也是非常有意思

csharp 复制代码
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;
}

上述线程中主要线程数 0 , 最大线程数是 Integer.MAX_VALUE ,同时创建了一个 队列长度是 0 的 SynchronousQueue , 想要弄明白这3个参数的实际意义就要知道 线程池的调用逻辑

当有任务需要交给线程池调度时,如果当前正在执行的线程数小于主要线程数,那么则会创建线程执行任务,当正在执行的线程数大于或等于主要线程数,任务需要先进入队列,等待任务队列满时,再判断正在执行线程数是否小于最大线程数,如果小于最大线程数,则创建线程执行任务,否则执行抛弃任务策略

了解了线程池的执行逻辑,再来看一下 okhttp 中的线程池参数下,他的逻辑是什么

由于他的主要线程数为0,同时队列的个数是0, 同时最大线程数是 Integer.MAX_VALUE ,当任务来临时 主要线程数不满足,执行添加队列逻辑,队列容量是0,执行最大线程数逻辑,最大线程数是 Integer.MAX_VALUE , 开启线程执行任务,这样即保证了任务的执行速度,同时也保证了任务执行完成后释放线程资源

线程池调度后就回到了 AsyncCall 这个Runnable 的 run 方法, AysncCall 继承自 NameRunnable ,run 方法会调用他的execute 方法

java 复制代码
@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);
  }
}

这个方法就开始了责任链调用, 但是我们先不看他调用的流程,我们先看 这个方法中的 finally 代码块 ,他调用了 Dispatcher 的 finished 方法

scss 复制代码
void finished(AsyncCall call) {
  finished(runningAsyncCalls, call, true);
}

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;
  }
   // 没有正在执行的任务,并且有 idleCallback ,则调用 idleCallback 执行闲时逻辑
  if (runningCallsCount == 0 && idleCallback != null) {
    idleCallback.run();
  }
}

这里 promoteCalls 就是检查是否有等待的异步任务,如果有那么则等待任务放入执行中的队列,没有任务则执行空闲逻辑

scss 复制代码
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);
    }

    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}

这个方法的逻辑还是判断最大请求数和同一host的请求数,如果满足条件,将等待任务放入执行中队列,

下面我们继续分析责任链 getResponseWithInterceptorChain 方法

csharp 复制代码
private Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> 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(
      retryAndFollowUpInterceptor.isForWebSocket()));

  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

到了这里就到了我们开篇所说的那个 Interceptor 逻辑, 我们调用的 addInterceptor 添加的 Interceptor 是放在所有的 Interceptor 之前, 而 addNetworkInterceptor 方法添加的 Interceptor 是请求前 最后的拦截器, 那么他们在请求执行过程中的逻辑就是 addInterceptor 添加的 Interceptor 先处理请求信息最后处理返回结果, addNetworkInterceptor添加的 Interceptor 只在 CallServerInterceptor 之前执行 处理请求信息而他处理结果是最先的,

到了这里请求与调度的逻辑就执行完毕了

相关推荐
学习使我快乐0114 小时前
AJAX 2——Bootstrap弹框使用、图书管理案例、图片上传方法
ajax·okhttp·bootstrap
前端李易安1 天前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp
学习使我快乐012 天前
AJAX 1——axios体验、认识URL、常用请求方法、HTTP协议、错误处理、form-serialize插件
前端·http·ajax·okhttp·axios
帅次3 天前
解决 Android WebView 无法加载 H5 页面常见问题的实用指南
android·okhttp·gradle·binder·webview·retrofit·appcompat
懒洋洋大魔王5 天前
7.Javaweb-Ajax
前端·ajax·okhttp
被迫学习Java5 天前
前端工程化17-邂逅原生的ajax、跨域、JSONP
前端·ajax·okhttp
Liuxu09036 天前
Ajax开发技术
java·前端·ajax·okhttp·javaweb
上官花雨6 天前
第七章综合实践:JPA+Thymeleaf增删改查
spring boot·后端·okhttp
丶白泽7 天前
重修设计模式-行为型-责任链模式
okhttp·设计模式·责任链模式
快乐就好ya7 天前
AJAX(简介以及一些用法)
前端·ajax·okhttp