阅读 627

网络请求工具-OkHttp

1.简介

大致的请求流程如下

请求大致被分为了三个阶段:准备、队列、预处理。

  1. 其中准备包括OkHttpClient、Request等等的实例化;
  2. 队列阶段在同步执行的网络请求由于直接调用:execute(),所以不存在队列阶段,并且会因为网络I/O阻塞掉当前的线程。而enqueue()方法则是异步方法,会通过Dispatcher利用ExecutorService实现,而最终进行网络请求时和同步execute()接口一致;
  3. 预处理:预处理则是针对网络请求的预处理,OkHttp的实现方式是采用了一个拦截器链(Interceptor Chain),这一系列的拦截器将会为我们的网络请求添加一些header信息,或者是预定好请求失败的重试方式、从连接池中获取可用的实例以复用等等。接下来将会按照这三个阶段进行阅读源代码。

2.准备阶段

OkHttpClient是一个请求端,我们可以通过new来创建一个OkHttpClient:

OkHttpClient httpClient = new OkHttpClient();
复制代码

点进入方法内,找到空参数的构造方法:

//OkHttpClient.decompiled.Java
public OkHttpClient() {
  this(new OkHttpClient.Builder());
}
复制代码

发现,我们实际上默认传入了一个Builder对象,而这个Builder则树我们的一个配置对象,我们可以通过Builder来对OkHttpClient进行一个配置,Builder的构造函数如下:

public Builder() {
     this.dispatcher = new Dispatcher();
     this.connectionPool = new ConnectionPool();
     boolean var1 = false;
     this.interceptors = (List)(new ArrayList());
     var1 = false;
     this.networkInterceptors = (List)(new ArrayList());
     this.eventListenerFactory = Util.asFactory(EventListener.NONE);
     this.retryOnConnectionFailure = true;
     this.authenticator = Authenticator.NONE;
     this.followRedirects = true;
     this.followSslRedirects = true;
     this.cookieJar = CookieJar.NO_COOKIES;
     this.dns = Dns.SYSTEM;
     this.proxyAuthenticator = Authenticator.NONE;
     SocketFactory var10001 = SocketFactory.getDefault();
     Intrinsics.checkNotNullExpressionValue(var10001, "SocketFactory.getDefault()");
     this.socketFactory = var10001;
     this.connectionSpecs = OkHttpClient.Companion.getDEFAULT_CONNECTION_SPECS$okhttp();
     this.protocols = OkHttpClient.Companion.getDEFAULT_PROTOCOLS$okhttp();
     this.hostnameVerifier = (HostnameVerifier)OkHostnameVerifier.INSTANCE;
     this.certificatePinner = CertificatePinner.DEFAULT;
     this.connectTimeout = 10000;
     this.readTimeout = 10000;
     this.writeTimeout = 10000;
     this.minWebSocketMessageToCompress = 1024L;
  }
复制代码

这提供了一长串的构造参数,具体的意义稍候再说,我们只需要知道OkHttpClient的Build流程。

我们可以将OkHttpClient理解为一个请求器,它用来将请求发送出去,而请求就是Request,我们在声明完OkHttpClient后,需要声明Request对象:

Request request = new Request.Builder()
            .url(url).post(requestBody).build();
         
复制代码

其中的URL自然而然就是请求的地址,而RequestBody则是请求的请求体,本身是一个抽象类,我们可以设置请求的MediaType、Content和ContentLength等等。如下代码就设置了Content-Type为“text/html”(create方法中还会在其后拼接上:"; charset=utf-8"),请求的数据为data,这个data为表单数据,最终会存放在请求的body中,格式为var1=x&var2=y。

    RequestBody requestBody = RequestBody.create(MediaType.parse("text/html;charset=utf-8"), data);
复制代码

接下来,我们通过OkHttpClient.newCall调用生成一个Call对象:

Call x = httpClient.newCall(request);

//newCall方法内:返回了一个RealCall对象;
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
复制代码

而RealCall的构造函数:

class RealCall(
  val client: OkHttpClient,
  /** The application's original request unadulterated by redirects or auth headers. */
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {
	//Omit
}
复制代码

我们在newCall方法中,填入了OkHttpClient、基本的请求、以及是否是WebSocket请求三个参数。这步走完,最终得到了一个Call对象。

3.同步请求

如果我们采用RealCall.execute()方法,那么将会走同步请求,即在当前线程执行这个请求,而execute方法如下:

//kotlin版本
override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed"}
    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }
 
//Java版本
@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);                                
  }
}
复制代码

这里由于OkHttp后来使用Kotlin重写了,所以暂时按照Java的来说明:Java中的executed是一个boolean类型的变量,用于检查该RealCall是否已经被execute过了,配合sychronized加锁,这也说明了这个ReadCall只会被执行一次。

dispatcher是client的成员之一,可以视作事件的分发器,调用executed(this)即执行网络请求,而getResponseWithInterceptorChain();则是走了第三步:预处理;使整个请求通过Interceptor Chain,最终获得Response,如果获得的Response为空,则说明被拦截器链所取消了。

而Kotlin版本中,executed被声明成了:“可以用原子方式更新的boolean值”,即高并发下,保证只有一个线程能够访问,其实就等同于Sychronized(){}包裹的一段代码。 compareAndSet(原先的值,要修改的值),可以参考CAS的实现,后面的代码大体上差不太多。

4. 队列(异步请求)

现在回到获得RealCall对象的时候,我们通常不希望网络请求在主线程直接执行,这样会导致画面无法及时渲染出帧,影响用户体验,甚至会在Android中导致ANR。我们通过:

            httpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {

            }
        });
复制代码

来将一个请求插入请求队列,其中我们要重写两个回调函数,分别是失败、成功时的回调函数。

enqueue的方法体如下:

  override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed"}
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }
复制代码

同样地,一个RealCall只能执行一次,CallStart()是开始执行的一个回调,而最后通过Dispatch进行事件分发,将一个异步的AsyncCall添加到发送队列中:

internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      readyAsyncCalls.add(call)

	  //如果不是WebSocket
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)//重用一个已经存在的Call
      }
    }
    promoteAndExecute()//通知线程池去取出一个Call来处理;
   
复制代码

在PromoteAndExecute方法中:

 private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    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
    }
	//遍历executableCalls
    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }
复制代码

首先,遍历readyAsyncCalls,有两种特殊情况:

  1. 当runningAsyncCalls超过maxRequests时,说明线程要处理的任务已经满了,直接跳出循环;
  2. 当asyncCall.callsPerHost().get()也就是当前Call所对应的host已经接受了足够多的Call请求,那么循环继续;

如果二者都不满足,则执行以下:(主要是往executableCalls中添加执行的请求和往runningAsyncCalls里添加要执行的请求。)

然后,遍历刚才的得到的executableCalls,并用调用线程池执行任务。具体是调用asyncCall的executeOn(executorService())方法实现的。

executeOn方法的方法如下:

fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }
复制代码

而AsyncCall的类如下:

  internal inner class AsyncCall(
		    private val responseCallback: Callback
		  ) : Runnable {
		    @Volatile var callsPerHost = AtomicInteger(0)
		      private set
		
		    fun reuseCallsPerHostFrom(other: AsyncCall) {
		      this.callsPerHost = other.callsPerHost
		    }
		
		    val host: String
		      get() = originalRequest.url.host
		
		    val request: Request
		        get() = originalRequest
		
		    val call: RealCall
		        get() = this@RealCall
		
		    fun executeOn(executorService: ExecutorService) {
		      client.dispatcher.assertThreadDoesntHoldLock()
		
		      var success = false
		      try {
		        executorService.execute(this)
		        success = true
		      } catch (e: RejectedExecutionException) {
		        val ioException = InterruptedIOException("executor rejected")
		        ioException.initCause(e)
		        noMoreExchanges(ioException)
		        responseCallback.onFailure(this@RealCall, ioException)
		      } finally {
		        if (!success) {
		          client.dispatcher.finished(this) // This call is no longer running!
		        }
		      }
		    }
		
		    override fun run() {
		      threadName("OkHttp ${redactedUrl()}") {
		        var signalledCallback = false
		        timeout.enter()
		        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("Callback failure for ${toLoggableString()}", Platform.INFO, e)
		          } else {
		            responseCallback.onFailure(this@RealCall, e)
		          }
		        } catch (t: Throwable) {
		          cancel()
		          if (!signalledCallback) {
		            val canceledException = IOException("canceled due to $t")
		            canceledException.addSuppressed(t)
		            responseCallback.onFailure(this@RealCall, canceledException)
		          }
			          throw t
	        } finally {
	          client.dispatcher.finished(this)
	        }
	      }
	    }
	  }
复制代码

终于,我们在重写的run()方法中,看到了和直接调用RealCall.execute相似的代码,通过getResponseWithInterceptorChain将一个请求通过第三步的拦截器链处理,再发送出去,最终成功获取到Response或者失败,报错。

try {
	val response = getResponseWithInterceptorChain()
	signalledCallback = true
	responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {//omit	}
复制代码

这里的responseCallBack是AsyncCall在构造时,传入的构造函数,调用处就在enqueue方法中:

  override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }
复制代码

这个responseCallback就是我们在

httpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {}

                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {}
});
复制代码

中new 出的Callback()。

5. 预处理(拦截器链Interceptor Chain)

不论是同步请求,还是异步请求,我们最终都走到了这个方法:

getResponseWithInterceptorChain()
复制代码

责任链模式的拦截器,整体可以在请求发出前再进行统一的包装,包括给请求加上请求头(Cookie或者是Token)、拦截不正确的请求等等。内置有五层拦截器,不包括(newWork拦截器),该方法的方法体如下:

 @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors//0(用户自定义的App拦截器,非netWorkInterceptor)
    interceptors += RetryAndFollowUpInterceptor(client)//1
    interceptors += BridgeInterceptor(client.cookieJar)//2
    interceptors += CacheInterceptor(client.cache)//3
    interceptors += ConnectInterceptor//4
    //如果不是WebSocket,则添加用户自己自定义的netWork拦截器
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)//5

	
    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )

    var calledNoMoreExchanges = false
    try {
	  //focus
      val response = chain.proceed(originalRequest)
      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }
复制代码

最终,走到了:

val response = chain.proceed(originalRequest)
复制代码

点开proceed方法:

  override fun proceed(request: Request): Response {
    check(index < interceptors.size)

    calls++

    if (exchange != null) {
      check(exchange.finder.sameHostAndPort(request.url)) {
        "network interceptor ${interceptors[index - 1]} must retain the same host and port"
      }
      check(calls == 1) {
        "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
      }
    }

    // Call 链中的下一个拦截器
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

    if (exchange != null) {
      check(index + 1 >= interceptors.size || next.calls == 1) {
        "network interceptor $interceptor must call proceed() exactly once"
      }
    }

    check(response.body != null) { "interceptor $interceptor returned a response with no body" }

    return response
  }	
复制代码

Interceptor是对拦截器的抽象接口定义,在intercept方法中,将会接收一个Chain。而Chain就是拦截链的抽象定义,这样一来,当拦截器的具体实现在调用intercept方法时,就可以通过Chain拦截链,调用request方法取出客户端请求,然后再调用process方法从而创建下一个节点的Chain,这时,在下一个节点的Chain中,将会定位到下一个Interceptor拦截器。由此类推,直至最后一个拦截器Interceptor,不再执行Chain的process方法。因为执行到最后一个拦截器时,后面不再有拦截器,所以在最后一个拦截器调用后,就需要将最终的响应结果,反馈给客户端了。各个拦截器的职责如下:

在getResponseWithInterceptorChain()方法中,我们传入最原始的:originalRequest

 val response = chain.proceed(originalRequest)
复制代码

而在RetryAndFollowUp拦截器中,我们找到:

//RetryAndFollowUpInterceptor.kt
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
	// 省略
    var request = chain.request
    // 省略 ······
    response = realChain.proceed(request)
    // 省略   
	}
//······
}
复制代码

我们在该拦截器中,有一次调用了chain.proceed(request),而BridgeInterceptor同理:

//BridgeInterceptor.kt
val networkResponse = chain.proceed(requestBuilder.build())
复制代码

这里甚至传入了一个新的利用RequestBuilder.build()构造的request,最终走到最后一层拦截器:CallServerInterceptor中,没有了chain.proceed,取而代之的是:

var response = responseBuilder
    .request(request)
    .handshake(exchange.connection.handshake())
    .sentRequestAtMillis(sentRequestMillis)
    .receivedResponseAtMillis(System.currentTimeMillis())
    .build()
复制代码

不出意外的话,我们在此处将会拿到这个请求的结果,然后逐层返回,返回到CallBack的回调处。

参考来源
  1. OKHttp拦截器之责任链模式
  2. OkHttp框架中拦截器设计精髓之责任链模式
  3. OkHttp解析
  4. 一些简单的拦截器interceptor
文章分类
Android
文章标签