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 之前执行 处理请求信息而他处理结果是最先的,

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

相关推荐
白乐天_n3 天前
OkHttp网络请求框架
android·网络·okhttp
前端与小赵4 天前
关于 AJAX 与 Promise
前端·ajax·okhttp·promise
孤客网络科技工作室7 天前
AJAX 全面教程:从基础到高级
android·ajax·okhttp
Liknana12 天前
OKHTTP断点续传
android·okhttp·面试
爱编程的鱼15 天前
web前后端交互方式有哪些?
前端·okhttp
鞠崽2333316 天前
【六袆 - WebSocket】WebSocket的认识;一次AJAX请求模型;一次长轮询请求模型;一次WebSocket请求模型;
websocket·ajax·okhttp
吃汉堡吃到饱19 天前
【Android】浅析OkHttp(1)
android·okhttp
wa的一声哭了22 天前
黑马JavaWeb-day03
数据结构·c++·人工智能·深度学习·算法·okhttp·eclipse
小R资源22 天前
Django CSRF Token缺失或不正确
okhttp·django·csrf
我就说好玩1 个月前
ajax嵌套ajax实现不刷新表单并向指定页面二次提交数据
android·ajax·okhttp