美文网首页
Android面试Android进阶(十七)-OkHttp相关问

Android面试Android进阶(十七)-OkHttp相关问

作者: 肖义熙 | 来源:发表于2021-04-23 17:19 被阅读0次

    问:简述OkHttp简单使用流程(基本不会这么问,为了分析)

    答:OkHttp使用流程基本分为四个步骤。
    1、创建OkHttpClient实例
    2、创建Request
    3、OkHttpClient生成一个Call对象实例(RealCall)
    4、发送请求(同步、异步)
    如:

            //1、创建一个OkHttpClient 的实例
            val client = OkHttpClient.Builder().build()
            //2、创建一个request请求体
            val request = Request.Builder().url("http://www.baidu.com").get().build()
            //3、生成真实请求对象
            val newCall = client.newCall(request)
            //4、发送同步请求,请求同步的,需要在子线程中执行
            val response = newCall.execute()
            //发送异步请求,请求异步,请求结果自动Callback回调。
            newCall.enqueue(object : Callback{
                override fun onFailure(call: Call, e: IOException) {
                    //请求失败(正常来讲是网络之类的问题,无法请求到服务端,否则服务端都有会response,不管是200还是302还是500之类的)
                }
    
                override fun onResponse(call: Call, response: Response) {
                    //请求成功
                }
            })
    

    1、创建OkHttpClient实例:
    OkHttp中使用 建造者模式(将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。)来设计OkHttpClient,里面包含了如:调度器(Dispatcher)、连接池(ConnectionPool)、拦截器(Interceptor)等等,具体看一下源码,无需关注太多,后面会挑重点解析。

    class Builder constructor() {
        internal var dispatcher: Dispatcher = Dispatcher()      //调度器(用于分发)
        internal var connectionPool: ConnectionPool = ConnectionPool()      //连接池
        internal val interceptors: MutableList<Interceptor> = mutableListOf()        //拦截器list
        internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()      //网络拦截器list
        internal var retryOnConnectionFailure = true    //连接失败重试
        internal var cookieJar: CookieJar = CookieJar.NO_COOKIES      //cookies
        internal var cache: Cache? = null      //缓存
        internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS    //支持的协议列表
        internal var connectTimeout = 10_000      //连接超时时间默认10s
        internal var readTimeout = 10_000            //读取超时时间默认10s
        internal var writeTimeout = 10_000            //写入超时时间默认10s
        //...中间及后面省略一堆代码
    }
    

    2、创建一个request请求体:
    也是使用建造者模式来设计Request,里面包含了:请求url(url)、请求方式(method)、header头(headers)、请求体(body),如:

    open class Builder {
        internal var url: HttpUrl? = null                    //请求url
        internal var method: String                          //请求方式
        internal var headers: Headers.Builder        //header头
        internal var body: RequestBody? = null      //请求体
        //...省略一堆代码
    }
    

    3、OkHttpClient生成一个Call对象实例(RealCall)
    client调用newCall方法,获得一个Call对象(Call是一个接口,具体的实现是RealCall类)。

    //OkHttpClient.kt
    override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
    //其中,this就是OkHttpClient的对象实例,request就是第二步生成的request对象,默认websocket为false
    
    //RealCall
    class RealCall( val client: OkHttpClient, val originalRequest: Request, val forWebSocket: Boolean) : Call {
      private val connectionPool: RealConnectionPool = client.connectionPool.delegate
      //省略一堆代码,其实就是通过client获取Client中的参数设置
    }
    

    4、发送请求(同步、异步):
    通过第三步获取的call对象,调用其实现类RealCall的 同步方法execute()异步方法enqueue(callback)
    接下来可能就问了:

    问:OkHttpClient有几种发起请求的方式,有何不同?

    答: 有两种,同步请求和异步请求。同步请求将请求(任务)加入到调度器(Dispatcher)的runningSyncCalls(正在执行)双端队列中,然后直接调用了getResponseWithInterceptorChain,而异步请求将请求加入到调度器(Dispatcher)中,经历两个阶段:readyAsyncCalls、runningAsyncCalls,之后调用
    getResponseWithInterceptorChain。

      //同步:RealCall.kt
      override fun execute(): Response {
        //检查任务是否执行过
        check(executed.compareAndSet(false, true)) { "Already Executed" }
        //开始超时计时
        timeout.enter()
        //加入事件监听
        callStart()
        try {
          //加入到正在运行同步双端队列中
          client.dispatcher.executed(this)
          //调用getResponseWithInterceptorChain方法获取Response
          return getResponseWithInterceptorChain()
        } finally {
          //无论是否成功,都将任务从双端队列中移除
          client.dispatcher.finished(this)
        }
      }
    
      //异步:RealCall.kt
      override fun enqueue(responseCallback: Callback) {
        //检查任务是否执行过
        check(executed.compareAndSet(false, true)) { "Already Executed" }
        //加入事件监听
        callStart()
        //将任务加入到调度器中,调度器中将任务添加到readyAsyncCalls 准备执行的双端队列中
        client.dispatcher.enqueue(AsyncCall(responseCallback))
      }
    
      //Dispatcher.kt
      internal fun enqueue(call: AsyncCall) {
        synchronized(this) {
          //加入到准备执行的双端队列中
          readyAsyncCalls.add(call)
          // 更改AsyncCall,以使其共享到同一主机的现有运行调用的AtomicInteger,Dispathcer中定义了同一主机同时执行的最大并发数为5个
          if (!call.call.forWebSocket) {
            val existingCall = findExistingCallWithHost(call.host)
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
          }
        }
        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 // 这里会判断正在执行的队列是否大于最大的数量  这里定义的最大数为64
            if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // 同主机最大并行数  5
            //从准备执行队列中移除,使用迭代器不影响继续遍历。
            i.remove()
            //将要执行的任务的主机记录
            asyncCall.callsPerHost.incrementAndGet()
            //加入到要执行的队列中,后面遍历执行
            executableCalls.add(asyncCall)
            //加入到正在执行的队列中
            runningAsyncCalls.add(asyncCall)
          }
          //正在执行数是否大于0,没有执行的情况下,就空闲下来。
          isRunning = runningCallsCount() > 0
        }
    
        for (i in 0 until executableCalls.size) {
          val asyncCall = executableCalls[i]
          //executorService是一个核心线程池为0的无界线程池,且60秒空闲线程回收
          asyncCall.executeOn(executorService)
        }
    
        return isRunning
      }
    
      //先看一下ExecutorService线程池:
      @get:JvmName("executorService") val executorService: ExecutorService
        get() {
          if (executorServiceOrNull == null) {
            //核心线程数为0,无界线程池,60秒空闲回收,并指定了线程名称
            executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
                SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
          }
          return executorServiceOrNull!!
        }
    
       //接着执行asyncCall.executeOn()方法:
       fun executeOn(executorService: ExecutorService) {
          client.dispatcher.assertThreadDoesntHoldLock()
    
          var success = false
          try {
            //重点在这里,excute接收一个Runable对象,之后会执行run方法。
            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!
            }
          }
        }
      
      //到这里就执行到了Runable对象的run方法:
      override fun run() {
          threadName("OkHttp ${redactedUrl()}") {
            var signalledCallback = false
            //开始超时计时
            timeout.enter()
            try {
              //调用getResponseWithInterceptorChain方法获取Response
              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 {
              //这个方法会重新调用promoteAndExecute方法,这样就又开始了异步的下一轮的请求了。
              client.dispatcher.finished(this)
            }
          }
        }
      }
    

    源码一堆,不给个总结太对不起大家了(上面源码都有注释,也可以看看注释):
    1、异步和同步最后都会执行getResponseWithInterceptorChain()方法获取任务执行结果,这里说任务其实是因为这里暂时还没开始去进行网络请求,只能说是一个任务
    2、异步执行这个任务时,只是很简单将任务加入到正在执行的同步队列runningSyncCalls中,而异步执行经历了两个队列:readyAsyncCalls、runningAsyncCalls
    3、任务执行完成后,不管异步还是同步,都会将加入到队列中的任务清除
    4、同步执行实际上并没有排队的概念,异步执行是需要进行排队的,并且同主机任务最大支持5个并发,同时执行任务不超过64个

    到这里,经常出现的两个词:调度器是什么?线程池又是怎么实现的?

    问:OkHttp中的Dispatcher调度器是什么,是怎么实现调度的

    答:调度器就是对任务执行的调度,Dispatcher中维护了三个队列,分别是一个准备异步执行队列readyAsyncCalls,两个正在执行的队列(异步正在执行队列:runningAsyncCalls,同步正在执行队列:runningSyncCalls),在每个任务执行完成最后都会执行dispatch.finish()方法,之后会重新执行到promoteAndExecute()方法,意为:促进和执行。只要队列中有任务,就会一直重复执行。 同时,调度器中定义了两个并发执行数的变量,分别对并发做限制,最大同时执行数为64,同主机执行数为5。调度器构造方法中定义了一个 ExecutorService 线程池变量,利用线程池对任务进行执行操作。

    问:OkHttp中的线程池是怎么实现的?

    答:OkHttp的调度器中初始化了一个线程池,线程池没有核心线程,最大支持线程为Int.MAX_VALUE(无界线程池),同时定义了60秒空闲时间,超过60秒的空线程将会回收。并且通过ThreadFactory生成的线程定义了线程名称。其工作队列为SynchronousQueue同步队列。当一个任务到来时,会加入到同步队列中,如果有空闲线程则直接从同步队列中取出,在空闲线程中进行处理,当没有空闲线程时,则会先创建一个新的线程再接受任务进行执行。OkHttp线程池设计为核心线程为0是因为客户端可能在一段时间内不会有网络请求,为了避免浪费不必要的线程内存,所以不保留最低线程,同时最大线程设置为Int.MAX_VALUE为了防止同一时间有大量的请求进入,造成部分请求被抛弃的问题,设置60秒为线程空闲最大时间,在一段时间不使用的情况进行线程回收。

    前面说了,不管同步还是异步,最终都会执行 getResponseWithInterceptorChain()方法获取网络请求结果。
    问题又来了:

    问:OkHttp有哪些拦截器,作用分别是什么?

    答:总共有七个拦截器,五个内置拦截器,两个自己添加的拦截器:

    1、addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。
    2、RetryAndFollowUpInterceptor,这里会对连接做一些初始化工作,以及请求失败的重试工作,重定向的后续请求工作。
    3、BridgeInterceptor,这里会为用户构建一个能够进行网络访问的请求,同时后续工作将网络请求回来的响应Response转化为用户可用的Response,比如添加文件类型,content-length计算添加,gzip解包。
    4、CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
    5、ConnectInterceptor,这里主要就是负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec。
    6、networkInterceptors,这里也是开发者自己设置的,所以本质上和第一个拦截器差不多,但是由于位置不同,用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。
    7、CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。

    看看getResponseWithInterceptorChain()方法的源码

      //RealCall.kt
      internal fun getResponseWithInterceptorChain(): Response {
        val interceptors = mutableListOf<Interceptor>()
        interceptors += client.interceptors        //添加自己的拦截器,使用时通过addInterceptor()添加
        interceptors += RetryAndFollowUpInterceptor(client)    //添加重试跟踪拦截器
        interceptors += BridgeInterceptor(client.cookieJar)        //添加桥拦截器
        interceptors += CacheInterceptor(client.cache)        //添加缓存拦截器
        interceptors += ConnectInterceptor        //添加链接拦截器
        if (!forWebSocket) {
          //如果不是websocket,添加自己的network拦截器,通过addNetworkInterceptor()添加
          interceptors += client.networkInterceptors
        }
        interceptors += CallServerInterceptor(forWebSocket)    //添加呼叫服务器拦截器 
        //将拦截器封装到 RealInterceptorChain中
        val chain = RealInterceptorChain(
            call = this,
            interceptors = interceptors,
            index = 0,
            exchange = null,
            request = originalRequest,
            connectTimeoutMillis = client.connectTimeoutMillis,
            readTimeoutMillis = client.readTimeoutMillis,
            writeTimeoutMillis = client.writeTimeoutMillis
        )
         //...省略代码
        try {
          //通过RealInterceptorChain实例对象执行(实际是是对每个拦截器的实现调用)
          val response = chain.proceed(originalRequest)
           //...省略代码
          return response
        } catch (e: IOException) {
           //...省略代码
        } finally {
           //...省略代码
        }
      }
    

    getResponseWithInterceptorChain()方法实际上是对几个Interceptors拦截器加到RealInterceptorChain类(拦截器链)中,之后通过proceed方法进行执行(proceed方法意为继续的意思,实际上每个拦截器都会执行这个),我们这里先不管每个拦截器的实现以及原理是什么,我们先看看是怎么执行的。

      //RealInterceptorChain.kt
      override fun proceed(request: Request): Response {
        //省略一堆的判断逻辑...
        // 在这个链中调用下一个拦截器
        val next = copy(index = index + 1, request = request)
        val interceptor = interceptors[index]
        @Suppress("USELESS_ELVIS")
        //执行拦截器的 intercept方法
        val response = interceptor.intercept(next) ?: throw NullPointerException(
            "interceptor $interceptor returned null")
        //...省略一堆的代码逻辑
        //每个链都会返回一个response。
        return response
      }
    

    到这里知道,其实Interceptor是一个接口,实现类就是上面的几个拦截器。这里其实就是使用了 责任链模式,每个拦截器中都会调用proceed方法,也都会返回response,调用流程如下:(偷来的图,里面缺了第一个自己添加的拦截器及自己添加的network拦截器)

    image.png
    看看每个拦截器的intercept,添加的两个自定义拦截器都需要实现Interceptor接口,实现intercept方法,并且最后需要调用proceed方法:chain.proceed()。 如:
          //添加一个拦截器
          val client = OkHttpClient.Builder().addInterceptor(object : Interceptor{
                override fun intercept(chain: Interceptor.Chain): Response {
                    val builder = chain.request().newBuilder()
                    //在自己添加的拦截器中可以添加header头等操作
                    builder.addHeader("Content-Type", "application/json;charset=UTF-8")
                    val request = builder.build()
                    //1、需要调用proceed方法,继续调用后面的拦截器
                    val response = chain.proceed(request)
                    val mediaType = response.body!!.contentType()
                    val content = response.body!!.string()
                    //2、返回response,责任链模式的真实写照
                    return response.newBuilder()
                        .body(ResponseBody.create(mediaType, content))
                        .build()
                }
            }).build()
    

    回过头来,看看OkHttp内置的五个拦截器的实现:

    一、RetryAndFollowUpInterceptor拦截器:该拦截器从故障中恢复,并根据需要进行重定向

     class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
    
      @Throws(IOException::class)
      override fun intercept(chain: Interceptor.Chain): Response {
        while (true) {
          try {
            try {
              //调用链上后面的拦截器,获取结果
              response = realChain.proceed(request)
            } catch (e: RouteException) {
              //路由错误,退出本次循环,进入下次循环
              continue
            } catch (e: IOException) {
              // 请求错误,退出本次循环,进入下次循环
              continue
            }
    
            //获取响应码判断是否需要重定向
            val followUp = followUpRequest(response, exchange)
            if (followUp == null) {
              //没有重定向
              return response
            }
            if(followUp.body != null){
                //没有重定向
                return response
             }
            //赋予重定向请求,再次进入下一次循环
            request = followUp
          } 
        }
      }
    }
    

    1、重试和重定向的处理都是需要重新请求,所以这里用到了while循环。
    2、当发生请求过程中错误的时候,也就是通过continue进入下一次循环进行 《重试》 ,重新走到realChain.proceed方法调用后面的链的拦截器,最后再进行网络请求。
    3、当请求结果没有重定向,那么就直接返回response响应结果。
    4、当请求结果需要重定向的时候,就赋予新的请求,并进入下一次循环,重新请求网络。

    二、BridgeInterceptor桥拦截器:从应用程序代码到网络代码的桥梁。首先,它根据用户请求建立一个网络请求,然后,它继续呼叫网络,最后,它从网络响应建立用户响应。

    class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
    
      @Throws(IOException::class)
      override fun intercept(chain: Interceptor.Chain): Response {
        //添加各类Header头信息,省略了很多判断代码...
        requestBuilder.header("Content-Type", contentType.toString())
        requestBuilder.header("Host", userRequest.url.toHostHeader())
        requestBuilder.header("Connection", "Keep-Alive")
        requestBuilder.header("Accept-Encoding", "gzip")
        requestBuilder.header("Cookie", cookieHeader(cookies))
        requestBuilder.header("User-Agent", userAgent)
        //同样的,每个拦截器都会调用后面的拦截器,获取response
        val networkResponse = chain.proceed(requestBuilder.build())
        
        //如果我们添加“ Accept-Encoding:gzip”标头字段,我们还将负责解压缩//传输流。
        val responseBuilder = networkResponse.newBuilder()
            .request(userRequest)
        if (transparentGzip &&
            "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
            networkResponse.promisesBody()) {
          val responseBody = networkResponse.body
          if (responseBody != null) {
            val gzipSource = GzipSource(responseBody.source())
            responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
          }
        }
        //每个拦截器都会返回response
        return responseBuilder.build()
      }
    

    桥拦截器的作用其实就是给网络请求包装一层header头信息,链接应用程序代码和网络请求代码的一个桥梁。使用gzip(一种压缩方式,OkHttp中使用okio框架中的GzipSource类实现)压缩数据,进行数据传输,提高网络传输效率。

    三、CacheInterceptor缓存拦截器:服务于来自缓存的请求,并将响应写入缓存。

    class CacheInterceptor(internal val cache: Cache?) : Interceptor {
    
      @Throws(IOException::class)
      override fun intercept(chain: Interceptor.Chain): Response {
        //取缓存
        val cacheCandidate = cache?.get(chain.request())
    
        //缓存策略类
        val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
        val networkRequest = strategy.networkRequest
        val cacheResponse = strategy.cacheResponse
    
        // 不允许使用网络并且缓存为空,直接返回504(504这里是前端的code)
        if (networkRequest == null && cacheResponse == null) {
          return Response.Builder()
              .request(chain.request())
              .protocol(Protocol.HTTP_1_1)
              .code(HTTP_GATEWAY_TIMEOUT)//504
              .message("Unsatisfiable Request (only-if-cached)")
              .body(EMPTY_RESPONSE)
              .sentRequestAtMillis(-1L)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build().also {
                listener.satisfactionFailure(call, it)
              }
        }
    
        // 如果不允许使用网络,但是有缓存,直接返回缓存。
        if (networkRequest == null) {
          return cacheResponse!!.newBuilder()
              .cacheResponse(stripBody(cacheResponse))
              .build().also {
                listener.cacheHit(call, it)
              }
        }
        //调用链后面的拦截器继续网络请求
        networkResponse = chain.proceed(networkRequest)
    
        // 如果缓存不为空
        if (cacheResponse != null) {
          //304,表示数据未修改,直接返回数据,更新缓存
          if (networkResponse?.code == HTTP_NOT_MODIFIED) {
            cache.update(cacheResponse, response)
            return response
          } 
        }
        //获取真实网络请求数据
        val response = networkResponse!!.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build()
        //如果开发者设置了缓存,则将响应数据缓存
        if (cache != null) {
          if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
            //缓存header
            val cacheRequest = cache.put(response)
            //缓存body
            return cacheWritingResponse(cacheRequest, response)
          }
        }
        return response
      }
    }
    

    缓存拦截器主要做了以下几个事情:
    1、通过缓存策略(CacheStrategy)获取缓存
    2、判断是否可以使用网络,如果不允许使用网络时:缓存为空,则直接返回504,缓存不为空直接返回缓存
    3、可以使用网络时,调用后续的拦截器继续网络请求,获取网络请求结果
    4、请求结果下,如果有缓存比,比对code是否为304,如果是则说明缓存还可以使用,直接返回缓存,且更新缓存
    5、如果缓存不可用了,则新增/更新缓存,返回最新网络请求结果。

    这里会有几个问题:(1)缓存是怎么存储和获取的?(2)每次请求都会去存储和获取缓存吗?(3)缓存策略(CacheStrategy)到底是怎么处理网络和缓存的?networkRequest什么时候为空?

    (1)缓存是怎么存储和获取的?

    //获取缓存代码 
    val cacheCandidate = cache?.get(chain.request())
    //具体的方法实现
    internal fun get(request: Request): Response? {
        //使用Request的url作为缓存的key
        val key = key(request.url)
        //使用DiskLruCache算法存储缓存 
        val snapshot: DiskLruCache.Snapshot = try {
          cache[key] ?: return null
        } 
    
        val entry: Entry = try {
          Entry(snapshot.getSource(ENTRY_METADATA))
        } 
        val response = entry.response(snapshot)
        if (!entry.matches(request, response)) {
          response.body?.closeQuietly()
          return null
        }
        return response
      }
    

    OkHttp的缓存通过Request的url作为key进行存储,使用DiskLruCache算法对缓存进行存储。

    (2)每次请求都会去存储和获取缓存吗?
    这里需要看一下cache是否为空?cache变量为空的话不会有缓存,所以这个变量什么时候初始化的?

    image.png
    上图说明,cache应该是在创建OkHttpClient时创建的:
     val client = OkHttpClient.Builder().cache(Cache(cacheDir, 10 * 1024 * 1024))
    

    所以第二个问题应该是:当开发者设置了缓存以后才会去存储和获取缓存,并且缓存大小有限制。

    (3)缓存策略(CacheStrategy)到底是怎么处理网络和缓存的?networkRequest什么时候为空?

    class CacheStrategy internal constructor(
    val networkRequest: Request?,
    val cacheResponse: Response?
    )
    
      fun compute(): CacheStrategy {
        val candidate = computeCandidate()
        return candidate
      }
      private fun computeCandidate(): CacheStrategy {
        //没有缓存情况下,返回空缓存
        if (cacheResponse == null) {
          return CacheStrategy(request, null)
        }
        //...
        //缓存控制不是 no-cache,且未过期
        if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
          val builder = cacheResponse.newBuilder()
          return CacheStrategy(null, builder.build())
        }
        return CacheStrategy(conditionalRequest, cacheResponse)
      }
    

    在这个缓存策略生存的过程中,只有一种情况下会返回缓存,也就是缓存控制不是no-cache,并且缓存没过期情况下,就返回缓存,然后设置networkRequest为空。

    四、ConnectInterceptor连接拦截器:打开与目标服务器的连接,然后进入下一个拦截器。该网络可能用于返回的响应,或者用于使用条件GET验证缓存的响应。

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

    连接拦截器的代码是真的简单,其他不说,就调用了 initExchange方法,Exchange是交换的意思,其实就是用网络请求地址打开一个通道,使得前后端能通过这个通道进行数据传递。
    initExchange()方法最终执行到:findConnection()方法(返回用于托管新流的连接。如果存在现有连接,它将优先使用,然后是池,最后建立一个新连接。)

    private fun findConnection(): RealConnection {
      // 1、尝试复用当前连接
      val callConnection = call.connection 
      if (callConnection != null) {
          //检查这个连接是否可用和可复用
          if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
            toClose = call.releaseConnectionNoEvents()
          }
        return callConnection
      }
    
      //2、从连接池中获取可用连接
      if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
        val result = call.connection!!
        eventListener.connectionAcquired(call, result)
        return result
      }
    
      //3、从连接池中获取可用连接(通过一组路由routes)
      if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
          val result = call.connection!!
          return result
        }
      route = localRouteSelection.next()
    
    
      // 4、创建新连接
      val newConnection = RealConnection(connectionPool, route)
      newConnection.connect
    
      // 5、再获取一次连接,防止在新建连接过程中有其他竞争连接被创建了
      if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) { 
        return result
      }
    
      //6、还是要使用创建的新连接,放入连接池,并返回
      connectionPool.put(newConnection)
      return newConnection
    }
    

    实际上,连接拦截器是获取一个与服务端真实的连接。如果当前有可以复用的就用复用的,没有的话从连接池中取,连接池中也没有则创建一个新的连接,然后再放入连接池,再将这个连接返回。OkHttp中连接池最大可以容纳5个空闲连接,五分钟空闲时间后将回收,具体可与看一下ConnectionPool.kt类:

    class ConnectionPool internal constructor(
      internal val delegate: RealConnectionPool
    ) {
      constructor(
        maxIdleConnections: Int,
        keepAliveDuration: Long,
        timeUnit: TimeUnit
      ) : this(RealConnectionPool(
          taskRunner = TaskRunner.INSTANCE,
          maxIdleConnections = maxIdleConnections,
          keepAliveDuration = keepAliveDuration,
          timeUnit = timeUnit
      ))
      //连接池最大支持5个空闲连接,最大空闲时间为5分钟
      constructor() : this(5, 5, TimeUnit.MINUTES)
      //省略...
    }
    

    这里就留下一点坑吧,要不然篇幅太长太长了,我也是读源码读到这里发现这个连接池的东西,有时间再详细看看这个连接池的具体设计。

    五、CallServerInterceptor拦截器:这是链中的最后一个拦截器,它与服务器进行数据交换

    class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
    
      @Throws(IOException::class)
      override fun intercept(chain: Interceptor.Chain): Response {
    
        //写header数据
        exchange.writeRequestHeaders(request)
        //写body数据
        if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
          val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
          requestBody.writeTo(bufferedRequestBody)
        } else {
          exchange.noRequestBody()
        }
    
        //结束请求
        if (requestBody == null || !requestBody.isDuplex()) {
          exchange.finishRequest()
        }
    
        //获取响应数据
        var response = responseBuilder
            .request(request)
            .handshake(exchange.connection.handshake())
            .build()
    
        var code = response.code
        response = response.newBuilder()
              .body(exchange.openResponseBody(response))
              .build()
        return response
      }
    }
    

    实际上,在连接拦截器中,已经拿到连接,打开了与服务器的通道,这里就剩下read和write操作了,最后返回response。
    最后:
    OkHttp使用建造者模式来创建client以及request,调用newCall方法后获取Call对象,实际上是RealCall对象。之后不管是同步还是异步都会先执行自定义拦截器,五个内置拦截器,其中还会有一个network拦截器,定义在倒数第二个,用来获取最后数据交互的真实数据及响应前的数据。拦截器采用 责任链模式 来一层层顺序调用,顺序返回结果。

    相关文章

      网友评论

          本文标题:Android面试Android进阶(十七)-OkHttp相关问

          本文链接:https://www.haomeiwen.com/subject/lhqolltx.html