OkHttp

1,概述

客户端使用的网络框架,

简单来说几个关键字:同步异步/拦截器

2,实例


public class Get {
    private static final String TAG = "Get";
    public static final String URL = "http://www.baidu.com";

    public static void test(){
        // 创建客户端
        OkHttpClient client = new OkHttpClient();

        //创建请求
        Request.Builder builder = new Request.Builder();
        Request request = builder.url(URL)
                .get() // 默认get
                .build();

        Request postRequest = new Request.Builder().post(
                new FormBody.Builder().add("key","value").build()
        ).url(URL).build();

        Call call = client.newCall(request);
        //异步请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.d(TAG, "onFailure");
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                Log.d(TAG, "onResponse");
            }
        });

        try {
            // 执行,一个call只可以执行一次,通过cas判断
            Response execute = call.execute();
            //同步请求
            if (execute.isSuccessful() && execute.body() != null){
                Log.d(TAG, " "+ execute.body().string());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

3,源码解读

使用起来很简单,咱们分析下源码,看看如何发起一个网络请求;

从异步请求说起,传入回调;

//异步请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.d(TAG, "onFailure");
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                Log.d(TAG, "onResponse");
            }
        });

执行RealCall.enqueue,进入异步队列,继续跟进,

这会,会用acs判断是否启动过一次,如果重复exqueue抛异常处理;

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

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

跟进client.dispatcher分发器,这儿将RealCall置换为AsyncCall,client是OkHttpClient,因为Call是从OkHttpClient.newClient创建,自然将自己传入RealCall构造方法中,

进入分发器enqueue方法,复用同host的call,看起来是一种优化;

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

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

假设没有复用,那么只是单纯加入readAsyncCalls队列,接着调用promoteAndExecute方法;

进入此方法会assert是否持有锁,否则会抛异常,防止死锁发生;

然后,简单创建一个list,用来保存可执行的call;

加锁,迭代readyAsyncCalls队列,检查call;

这有一个最大执行限制maxRequests,查看源码为64,对同一host也有最大限制,maxRequestsPerHost为5;

等一切检查通过,asynCall自增调用次数,加入executableCalls和runningAsyncCalls队列;

接下来,在可执行list中,调用AsyncCall.executOn方法,传入线程池ExecutorService;

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
    }

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

    return isRunning
  }

如果不满足线程池执行条件,回调call.onFailure,即我们传入的回调;

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.run方法查看,

此处涉及tineout机制、拦截器机制;

当然,如果此处成功返回一个response,自然回调response.onResponse方法,执行我们传入的异步逻辑,否则在异常处会调用onFailure方法;

最后,在finally处finish自己,即还原一些配置,如主机数目(acs),从runningAsyncCall移除自己等;当然,如果移除失败,抛出异常,再做一些处理,如让分发器再次调用promoteAndExecute方法,初始队列call执行;

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

现在重点方法是getResponseWithInterceptorChain,即通过拦截器访问真正的网络,看了android底层源码后,这个函数也不长(手动狗头);

函数逻辑清晰,首先这在RealCall中,先创建拦截器list;

将一些list加入此拦截器list,如OkHttpClient的拦截器等等等等,再写入RealInterceptorChain拦截链中,同时传入一些参数,如call本身,保存在call中的request等,

@Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)

    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 {
      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)
      }
    }
  }

执行拦截链processd方法,不过看到这有个疑问,创建拦截链的时候不是将request传入了吗?看来这么写说明拦截链是可复用多个request的;

此方法前,检查参数,如拦截器index,为什么这么检查呢?check函数接受false,就抛出异常;

calls计数;

exchange是个Exchange类,注释说:“Excjamge是传输单个 HTTP 请求和响应对。 这在ExchangeCodec上分层连接管理和事件,它处理实际的 I/O。”

看来处理io就在exchange中了,重点是exchange.finder类的sameHostAndPort方法,接收url,此处check检查拦截器是否作用相同host,否则抛出异常;

再次检查calls,第一次执行process为true,看来这种check编程方式挺好,kotlin语言特性;

于是乎,进入真正拦截操作,index++,request不变,按顺序执行拦截器,调用intercept方法,

@Throws(IOException::class)
  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 the next interceptor in the chain.
    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,OkHttp提供拦截器有如下几个,

 随便看一个,如ConnectInterceptor,这个用于打开与service的连接,显然是上述拦截器加入的最后一个,因为拦截器之前是可以做一些缓存策略的,如CacheInterceptor;

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }
}

再看下CacheInterceptor,别说,这个intercept函数还挺长;

主要就是缓存思想嘛,如果对于相同的request,直接从缓存中拿去,如果缓存中不存在,则真实访问后放入缓存,并且设置有效时间;当然还有更多细节,需要研究这段代码,但缓存思想本质是一样的;

class CacheInterceptor(internal val cache: Cache?) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    val cacheCandidate = cache?.get(chain.request())

    val now = System.currentTimeMillis()

    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse

    cache?.trackResponse(strategy)
    val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE

    if (cacheCandidate != null && cacheResponse == null) {
      // The cache candidate wasn't applicable. Close it.
      cacheCandidate.body?.closeQuietly()
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(HTTP_GATEWAY_TIMEOUT)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }

    if (cacheResponse != null) {
      listener.cacheConditionalHit(call, cacheResponse)
    } else if (cache != null) {
      listener.cacheMiss(call)
    }

    var networkResponse: Response? = null
    try {
      networkResponse = chain.proceed(networkRequest)
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        cacheCandidate.body?.closeQuietly()
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        val response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers, networkResponse.headers))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build()

        networkResponse.body!!.close()

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache!!.trackConditionalCacheHit()
        cache.update(cacheResponse, response)
        return response.also {
          listener.cacheHit(call, it)
        }
      } else {
        cacheResponse.body?.closeQuietly()
      }
    }

    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()

    if (cache != null) {
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        val cacheRequest = cache.put(response)
        return cacheWritingResponse(cacheRequest, response).also {
          if (cacheResponse != null) {
            // This will log a conditional cache miss only.
            listener.cacheMiss(call)
          }
        }
      }

      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }

    return response
  }

还有更多拦截器,如重试重定向、桥接呀等等,本文更新在后文研究,现在本文是书写OkHttp访问网络全流程,继续回到chain.proces方法处,代码后半段如下,

 @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

process方法如何递进调用,当然是在拦截器中执行intercept方法递进调用,

 在process中,有个next,本质是拦截链的copy版本,不过将index++而已罢,所以process开头check(inde<size)就是这个意义,这就是设计模式中责任链模式;

ok,当所有拦截链函数调用完毕,返回response,这就是咱们拿到的response了;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值