OkHttp(二)

在(一)我们粗略的介绍了一下OkHttp的使用,现在我们来慢慢探究这个框架。

首先我们看下OkHttp发送HTTP请求的大致流程图

20190522191648150.png

我将其分为三个阶段(纯主观):

  • 外部构建网络请求
  • 网络请求调度
  • 真正执行网络请求

第一个阶段,就是我们能看到最外层的代码,如OkHttpClient的构建,Request的构建等,这阶段的一些内容在(一)中有描述,这里不再赘述。这个阶段主要是根据我们实际需求,将其抽象成的网络请求对象。

第二个阶段,我们对封装网络对象,进行调度。请求分为同步请求和异步请求,这两种调度情况有所不同,该阶段也是我们这篇文章要探究的东西。

第三个阶段,就是利用责任链模式来进行真正的网络请求。(后面详细探究)

Call调度

//Call对象的构建
client.newCall(request)

//从以下可以看出,真正构建的请求对象是RealCall
interface Factory {
    Call newCall(Request request);
  }
  ...
   @Override
    public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false /* for web socket */);
    }

在实际应用中,我们调度的是RealCall对象。其主要方法有

  • 同步请求:RealCall.execute()
  • 异步请求:RealCall.enqueue()

我们先来看看异步请求的步骤。

RealCall.enqueue()

 @Override 
 //CallBack 在异步回调的时候,我们都会重写匿名内部类,用以进行回调,以便进行请求抵达(失败)后面的操作
 public void enqueue(Callback responseCallback) {
    //TODO 保证每个Call只能被执行一次,不能重复执行。
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //TODO 交给 dispatcher调度器 进行调度
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

在源码中,出现了Dispatch调度器,其初始化在OkHttpClient中,其作用就是将Call加入队列,并通过线程池来执行Call。

Dispatchar类的属性和方法(异步相关)

private int maxRequests = 64;
    //TODO 同时请求的相同HOST的最大个数 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
    //TODO 如 https://restapi.amap.com  restapi.amap.com - host
    private int maxRequestsPerHost = 5;
    /**
     * Ready async calls in the order they'll be run.
     * TODO 双端队列,支持首尾两端 双向开口可进可出,方便移除
     * 异步等待队列
     *
     */
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

    /**
     * Running asynchronous calls. Includes canceled calls that haven't finished yet.
     * TODO 正在进行的异步队列
     */
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
    
 // 构建线程池
 public synchronized ExecutorService executorService() {
        if (executorService == null) {
            //TODO 核心线程 最大线程 非核心线程闲置60秒回收 任务队列
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }

我们可以看到,Dispatch类规定最大同时请求数,和对同一主机的最大请求数。并构建了线程池(延迟加载),两个关于异步的队列,正在执行和即将执行的队列。

接着就是enqueue方法(kotlin)

// 将请求加入队列后,执行了promoteAndExecute()方法
 internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      // 往准备队列中加入call
      readyAsyncCalls.add(call)

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      // 判断是否有相同的host
      // 并用原子类来记录host的个数,每次使用完后会删去
      if (!call.get().forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host())
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }
// 在该方法中,主要是判断是否超过64与5,将等待队列符合条件的加入正在执行的队列中,并执行这些AsyncCall请求
private fun promoteAndExecute(): Boolean {
    assert(!Thread.holdsLock(this))

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      // 对队列进行迭代, 将符合条件的加入队列之中
      while (i.hasNext()) {
        val asyncCall = i.next()
        // 队列数大于最大容量时,就会跳出此次循环
        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // Host max capacity.

        i.remove()
        asyncCall.callsPerHost().incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }
 // 回归了AsyncCall方法中的执行方法,
 
 fun executeOn(executorService: ExecutorService) {
      assert(!Thread.holdsLock(client.dispatcher))
      var success = false
      try {
        // 由于本身实现了Runnable接口,所以直接执行本身
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        transmitter.noMoreExchanges(ioException)
        // 失败的回调
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        // 运行不成功时,也需要结束该请求,finish的作用主要是将Call移除队列,并重新执行promoteandexecute方法,或者开启闲置调用
        if (!success) {
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }
//AsyncCall方法的Run方法
override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        transmitter.timeoutEnter()
        try {
          // 实质的调用
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          // 成功后回调
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log(INFO, "Callback failure for ${toLoggableString()}", e)
          } else {
            responseCallback.onFailure(this@RealCall, e)
          }
        } finally {
          // finish()方法的作用同上
          client.dispatcher.finished(this)
        }
      }
    }

以上就是异步请求的全过程,从RealCall.enqueue()开始,借助AsyncCall和Dispatch来进行整体的调度,最后到异步执行的run()来结束。在run()将结果回调。

RealCall.execute()

同步请求,在Android中,由于网络请求是一个耗时操作,所以如果用同步请求,很容易会照成主线程的阻塞等问题,所以用的较少。

不过也让我们看看这部分的源码。

override fun execute(): Response {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    transmitter.timeoutEnter()
    transmitter.callStart()
    try {
      //依然使用Dispatcher类来进行调度
      client.dispatcher.executed(this)
      // 实质上的请求
      return getResponseWithInterceptorChain()
    } finally {
      // 结束请求,效果与前面异步的类似
      client.dispatcher.finished(this)
    }
  }
  // 只将请求添加到队列之中,Dispatch类实际有三个队列,有一个是为同步请求准备的队列就是这个
  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }

同步请求的方法较异步请求简便很多,但是都要经过Dispatch类的一个调度。其基本的调度思路如一开始的图所示。

两者都有调用getResponseWithInterceptorChain()方法,这也是我所说的第三阶段的内容,这部分放在后一章探究。

小结

本文中,我们对OkHttp的了解从表面的使用,往前走了一步,了解了具体的同/异步请求的调度过程。我们从Call类出发,到Dispatch类终止,算是对整体的框架有了一个大体的了解。接下来,就是对OkHttp的核心-责任链模式进行探究了。


JathonW
1 声望2 粉丝

精致唯一