Android okhttp 의 시작 절차 및 소스 코드 분석

머리말
이 글 은 주로 okhttp 의 주요 작업 절차 와 소스 코드 에 대한 분석 을 설명 했다.
OKhttp 가 뭐야?
쉽게 말 하면 OkHttp 는 클 라 이언 트 가 HTTP 메 시 지 를 보 내 고 서버 의 응답 을 처리 하 는 응용 층 프레임 워 크 입 니 다.그렇다면 그것 은 어떤 장점 이 있 습 니까?
4.567917.사용 하기 쉽 고 확장 하기 쉽다
  • HTTP/2 프로 토 콜 을 지원 합 니 다.같은 호스트 의 모든 요청 에 같은 socket 으로 연결 할 수 있 습 니 다
  • HTTP/2 를 사용 할 수 없 으 면 연결 풀 재 활용 을 사용 하여 요청 지연 을 줄 입 니 다
  • GZIP 을 지원 하여 다운로드 크기 를 줄 였 습 니 다4.567917.캐 시 처 리 를 지원 하여 중복 요청 을 피 할 수 있 습 니 다4.567917.서비스 에 여러 개의 IP 주소 가 있 으 면 첫 번 째 연결 에 실패 하면 OkHttp 는 예비 주 소 를 시도 합 니 다OkHttp 는 프 록 시 문제 와 SSL 악수 실패 문제 도 처리 했다.
    OkHttp 는 네트워크 요청 을 어떻게 하 는 지.
    1.그것 은 어떻게 사용 합 니까?
    1.1 구조 자 모드 를 통 해 url,method,header,body 등 을 추가 하여 요청 한 정보 요청 대상 을 완성 합 니 다.
    
      val request = Request.Builder()
        .url("")
        .addHeader("","")
        .get()
        .build()
    
    1.2 구조 자 모드 를 통 해 OkHttpClicent 인 스 턴 스 를 만 들 고 필요 에 따라 설정 할 수 있 습 니 다.
    
      val okHttpClient = OkHttpClient.Builder()
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(15, TimeUnit.SECONDS)
        .addInterceptor()
        .build()
    
    1.3 콜 을 만 들 고 네트워크 요청 을 시작 합 니 다.
    
     val newCall = okHttpClient.newCall(request)
     //      
     newCall.enqueue(object :Callback{
     override fun onFailure(call: Call, e: IOException) {}
     override fun onResponse(call: Call, response: Response) {}
     })
     //      
     val response = newCall.execute()
    
    전체 사용 절 차 는 매우 간단 하 다.주요 한 부분 은 Call 대상 을 통 해 동/비동기 요 구 를 어떻게 하고 후속 적 인 소스 추적 을 방법 으로 시작 하 는 지 에 있다.
    2.어떻게 콜 을 통 해 요청 을 합 니까?
    2.1 Call 은 무엇 입 니까
    
     /** Prepares the [request] to be executed at some point in the future. */
     override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
    
    2.2 요청-비동기 요청
    
    //RealCall#enqueue(responseCallback: Callback) 
    
     override fun enqueue(responseCallback: Callback) {
     synchronized(this) {
      //    call     ,   call        
      check(!executed) { "Already Executed" }
      executed = true
     }
     //      EventListener#callStart(call: Call),
                 HTTP     ,          
     callStart()
     //  AsyncCall,    Runnable
     client.dispatcher.enqueue(AsyncCall(responseCallback))
     }
    
    enqueue 의 마지막 방법 은 두 단계 로 나 뉜 다.
  • 첫 번 째 단 계 는 응답 하 는 반전 을 AsyncCall 대상 에 넣 었 고 AsyncCall 대상 은 RealCall 의 내부 클래스 로 Runnable 인 터 페 이 스 를 실현 했다
  • 두 번 째 단 계 는 Dispatcher 류 의 enqueue()를 통 해 AsyncCall 대상 을 전달 합 니 다
  • 
    //Dispatcher#enqueue(call: AsyncCall)
    
     /** Ready async calls in the order they'll be run. */
     private val readyAsyncCalls = ArrayDeque<AsyncCall>()
     
     internal fun enqueue(call: AsyncCall) {
     synchronized(this) {
      // call            
      readyAsyncCalls.add(call)
      ...
      promoteAndExecute()
     }
    
    //Dispatcher#promoteAndExecute() 
    // [readyAsyncCalls]   [runningAsyncCalls]
    
     private fun promoteAndExecute(): Boolean {
     ...
     for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      //       ExecutorService    run()
      asyncCall.executeOn(executorService)
     }
     return isRunning
     }
    
    //RealCall.kt     
    
     internal inner class AsyncCall(
     private val responseCallback: Callback
     ) : Runnable {
     fun executeOn(executorService: ExecutorService) {
      ...
      //  Runnable
      executorService.execute(this)
      ...
     	}
     	
     override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
      ...
      try {
       //                
       val response = getResponseWithInterceptorChain()
       signalledCallback = true
       //             
       responseCallback.onResponse(this@RealCall, response)
      } catch (e: IOException) {
       if (signalledCallback) {
       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)
      }
      }
     }
     }
    
    2.3 RealCall\#execute 동기 화 요청()
    
     override fun execute(): Response {
     //         
     synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
     }
     timeout.enter()
     //    
     callStart()
     try {
      //    
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
     } finally {
      //    
      client.dispatcher.finished(this)
     }
     }
    
    3.어떻게 차단 기 를 통 해 요청 과 응답 을 처리 합 니까?
    같은 비동기 요청 이 든 getResponse With Interceptor Chain()으로 호출 됩 니 다.이 방법 은 주로 책임 체인 모드 를 사용 하여 전체 요청 을 몇 개의 차단기 로 나 누 어 호출 합 니 다.각자 의 책임 과 논 리 를 간소화 하고 다른 차단 기 를 확장 할 수 있 습 니 다.차단기 OkHttp 를 보면 알 수 있 는 차이 가 많 지 않 습 니 다.
    
     @Throws(IOException::class)
     internal fun getResponseWithInterceptorChain(): Response {
     //         
     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   //   OkHttpClient    ,      
     }
     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)
     	 ...
     } catch (e: IOException) {
    	 ...
     } finally {
    	 ...
     }
     }
    
    //1.RealInterceptorChain#proceed(request: Request)
    @Throws(IOException::class)
     override fun proceed(request: Request): Response {
     ...
     // copy      ,        index+1
     val next = copy(index = index + 1, request = request)
     val interceptor = interceptors[index]
    
     //      intercept(chain: Chain): Response                    
     @Suppress("USELESS_ELVIS")
     val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")
     ...
     //        
     return response
     }
    
    차단기 가 Request 를 시작 합 니 다.
    3.1 차단 기 는 어떻게 차단 합 니까?
    차단 기 는 모두 Interceptor 클래스 를 계승 하여 fun intercept(chain:Chain):Response 방법 을 실현 했다.
    intercept 방법 에서 chain 대상 이 proceed()를 호출 한 다음 proceed()방법 에서 다음 차단 기 를 복사 한 다음 intercept(chain:Chain)을 호출 한 다음 chain.proceed(request)를 마지막 차단기 return response 까지 한 층 한 층 위로 피드백 합 니 다.
    3.2 RetryAndFollowUpInterceptor
    이 차단 기 는 방향 을 바 꾸 는 후속 요청 과 실 패 를 처리 하 는 데 사 용 됩 니 다.즉,일반적으로 첫 번 째 요청 은 방향 을 바 꾸 지 않 아 도 다음 차단 기 를 호출 합 니 다.
    
    @Throws(IOException::class)
     override fun intercept(chain: Interceptor.Chain): Response {
     val realChain = chain as RealInterceptorChain
     var request = chain.request
     val call = realChain.call
     var followUpCount = 0
     var priorResponse: Response? = null
     var newExchangeFinder = true
     var recoveredFailures = listOf<IOException>()
     while (true) {
      ...//             
      var response: Response
      try {
      ...
      try {
       //        
       response = realChain.proceed(request)
       newExchangeFinder = true
      } catch (e: RouteException) {
      ...
       continue
      } catch (e: IOException) {
       ...
       continue
      }
    
      ...
      //            response
      val followUp = followUpRequest(response, exchange)
    	 ...
    	 //                       response
    	 //         Request
      request = followUp
      priorResponse = response
      } finally {
      call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
     }
     }
     
     @Throws(IOException::class)
     private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
     val route = exchange?.connection?.route()
     val responseCode = userResponse.code
    
     val method = userResponse.request.method
     when (responseCode) {
    	 //3xx    
      HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
      //           Request       
      return buildRedirectRequest(userResponse, method)
      }
    	 ...      code
      else -> return null
     }
     }
    
    followUpRequest(userResponse:Response,exchange:Exchange?):Request? 방법 에 서 는 response 의 서버 응답 코드 가 서로 다른 조작 을 한 것 으로 판단 되 었 다.
    3.3 BridgeInterceptor
    이 는 Http 에 대한 추가 예비 처 리 를 책임 집 니 다.예 를 들 어 Content-Length 의 계산 과 추가,gzip 의⽀支(Accept-Encoding:gzip),gzip 압축 데이터 의 해 지 등 은 비교적 간단 하면 코드 를 붙 이지 않 고 알 고 싶 으 면 스스로 볼 수 있 습 니 다.
    3.4 CacheInterceptor
    이 종 류 는 Cache 의 처 리 를 책임 집 니 다.만약 에 로 컬 에 스토리 보드 가 있 으 면 요청 은 실제 적 으로 12131℃를 보 내지 않 은 상태 에서 캐 시 결 과 를 되 돌려 주 고 다음 과 같이 실현 할 수 있 습 니 다.
    
     @Throws(IOException::class)
     override fun intercept(chain: Interceptor.Chain): Response {
     // Cache(DiskLruCache)     request.url  response
     val cacheCandidate = cache?.get(chain.request())
     //       
     val now = System.currentTimeMillis()
     //           
     //networkRequest     
     //cacheResponse      
     val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
     val networkRequest = strategy.networkRequest
     val cacheResponse = strategy.cacheResponse
     //           
     cache?.trackResponse(strategy)
     ...
     //                  ,  504  body Response
     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()
     }
    
     //            ,      response    
     if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
       .cacheResponse(stripBody(cacheResponse))
       .build()
     }
     //     process       
     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()
      }
     }
     
     //      Response
     if (cacheResponse != null) {
      //        code 304         
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
      //         Response    
      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
      } 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)) {
      //      
      val cacheRequest = cache.put(response)
      return cacheWritingResponse(cacheRequest, response)
      }
      //              remove 
      if (HttpMethod.invalidatesCache(networkRequest.method)) {
      try {
       cache.remove(networkRequest)
      } catch (_: IOException) {
       // The cache cannot be written.
      }
      }
     }
     
     return response
     }
    
    3.5 ConnectInterceptor
    이런 종류의 건설 과 연결 을 책임 진다.⽹络 요청 에 필요 한 TCP 연결(HTTP)이나 TCP 이전의 TLS 연결(HTTPS)이 포함 되 어 있 으 며,이에 대응 하 는 HttpCodec 대상(인 코딩 된 HTTP 요청 에 있 음)을 생 성 합 니 다.
    
    @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)
     }
    
    짧 고 네 줄 로 보이 지만 실제 일 은 비교적 많다.
    
    /** Finds a new or pooled connection to carry a forthcoming request and response. */
     internal fun initExchange(chain: RealInterceptorChain): Exchange {
    	...
    	//codec   HTTP        ,     :Http1Codec Http2Codec,   HTTP/1.1   HTTP/2。
     val codec = exchangeFinder.find(client, chain)
     val result = Exchange(this, eventListener, exchangeFinder, codec)
     ...
     return result
     }
     
     #ExchangeFinder.find
     fun find(client: OkHttpClient,chain: RealInterceptorChain):ExchangeCodec {
     try {
      //         
      val resultConnection = findHealthyConnection(
       connectTimeout = chain.connectTimeoutMillis,
       readTimeout = chain.readTimeoutMillis,
       writeTimeout = chain.writeTimeoutMillis,
       pingIntervalMillis = client.pingIntervalMillis,
       connectionRetryEnabled = client.retryOnConnectionFailure,
       doExtensiveHealthChecks = chain.request.method != "GET"
      )
      return resultConnection.newCodec(client, chain)
     } catch (e: RouteException) {
      trackFailure(e.lastConnectException)
      throw e
     } catch (e: IOException) {
      trackFailure(e)
      throw RouteException(e)
     }
     }
     
     @Throws(IOException::class)
     private fun findHealthyConnection(
     connectTimeout: Int,
     readTimeout: Int,
     writeTimeout: Int,
     pingIntervalMillis: Int,
     connectionRetryEnabled: Boolean,
     doExtensiveHealthChecks: Boolean
     ): RealConnection {
     while (true) {
     	//    
      val candidate = findConnection(
       connectTimeout = connectTimeout,
       readTimeout = readTimeout,
       writeTimeout = writeTimeout,
       pingIntervalMillis = pingIntervalMillis,
       connectionRetryEnabled = connectionRetryEnabled
      )
    
      //            
      if (candidate.isHealthy(doExtensiveHealthChecks)) {
      return candidate
      }
    	 ...
      throw IOException("exhausted all routes")
     }
     }
     
     @Throws(IOException::class)
     private fun findConnection(
     connectTimeout: Int,
     readTimeout: Int,
     writeTimeout: Int,
     pingIntervalMillis: Int,
     connectionRetryEnabled: Boolean
     ): RealConnection {
     if (call.isCanceled()) throw IOException("Canceled")
    
     // 1.       call                             
     val callConnection = call.connection
     if (callConnection != null) {
      var toClose: Socket? = null
      synchronized(callConnection) {
      if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
       toClose = call.releaseConnectionNoEvents()
      }
      }
    	 //      
      if (call.connection != null) {
      check(toClose == null)
      return callConnection
      }
    
      // The call's connection was released.
      toClose?.closeQuietly()
      eventListener.connectionReleased(call, callConnection)
     }
    	...
     // 2.                     
     if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
     }
    
     // 3.                      
     val routes: List<Route>?
     val route: Route
     if (nextRouteToTry != null) {
      // Use a route from a preceding coalesced connection.
      routes = null
      route = nextRouteToTry!!
      nextRouteToTry = null
     } else if (routeSelection != null && routeSelection!!.hasNext()) {
      // Use a route from an existing route selection.
      routes = null
      route = routeSelection!!.next()
     } else {
      // Compute a new route selection. This is a blocking operation!
      var localRouteSelector = routeSelector
      if (localRouteSelector == null) {
      localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
      this.routeSelector = localRouteSelector
      }
      val localRouteSelection = localRouteSelector.next()
      routeSelection = localRouteSelection
      routes = localRouteSelection.routes
    
      if (call.isCanceled()) throw IOException("Canceled")
    
      // Now that we have a set of IP addresses, make another attempt at getting a connection from
      // the pool. We have a better chance of matching thanks to connection coalescing.
      if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
      }
    
      route = localRouteSelection.next()
     }
    
     // Connect. Tell the call about the connecting call so async cancels work.
     // 4.                    route       socket/tls  
     val newConnection = RealConnection(connectionPool, route)
     call.connectionToCancel = newConnection
     try {
      newConnection.connect(
       connectTimeout,
       readTimeout,
       writeTimeout,
       pingIntervalMillis,
       connectionRetryEnabled,
       call,
       eventListener
      )
     } finally {
      call.connectionToCancel = null
     }
     call.client.routeDatabase.connected(newConnection.route())
    
     // If we raced another call connecting to this host, coalesce the connections. This makes for 3
     // different lookups in the connection pool!
     // 4.         (http2)   ,    
     if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
      val result = call.connection!!
      nextRouteToTry = route
      newConnection.socket().closeQuietly()
      eventListener.connectionAcquired(call, result)
      return result
     }
    
     synchronized(newConnection) {
      //      
      connectionPool.put(newConnection)
      call.acquireConnectionNoEvents(newConnection)
     }
    
     eventListener.connectionAcquired(call, newConnection)
     return newConnection
     }
     
    
    연결 을 어떻게 만 드 는 지 살 펴 보 겠 습 니 다.
    
    fun connect(
     connectTimeout: Int,
     readTimeout: Int,
     writeTimeout: Int,
     pingIntervalMillis: Int,
     connectionRetryEnabled: Boolean,
     call: Call,
     eventListener: EventListener
     ) {
     ...
     while (true) {
      try {
      if (route.requiresTunnel()) {
       //  tunnel,    http    https
       //    connectSocket、createTunnel
       connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
       if (rawSocket == null) {
       // We were unable to connect the tunnel but properly closed down our resources.
       break
       }
      } else {
       //   tunnel   socket         
       connectSocket(connectTimeout, readTimeout, call, eventListener)
      }
      //      tsl
      establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
      eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
      break
      } catch (e: IOException) {
      ...
      }
     }
    	...
     }
    
    tsl 연결 만 들 기
    
    @Throws(IOException::class)
     private fun establishProtocol(
     connectionSpecSelector: ConnectionSpecSelector,
     pingIntervalMillis: Int,
     call: Call,
     eventListener: EventListener
     ) {
     	//ssl    http       
     if (route.address.sslSocketFactory == null) {
      if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
      socket = rawSocket
      protocol = Protocol.H2_PRIOR_KNOWLEDGE
      startHttp2(pingIntervalMillis)
      return
      }
    
      socket = rawSocket
      protocol = Protocol.HTTP_1_1
      return
     }
    	//   https       sslSocket                 tsl     
     eventListener.secureConnectStart(call)
     connectTls(connectionSpecSelector)
     eventListener.secureConnectEnd(call, handshake)
    
     if (protocol === Protocol.HTTP_2) {
      startHttp2(pingIntervalMillis)
     }
     }
    
    이로써 연결 을 만 들 고 최초의 find()방법 으로 되 돌아 가 Exchange Codec 대상 을 되 돌려 주 고 Exchange 대상 으로 포장 하여 다음 차단기 작업 에 사용 합 니 다.
    3.6 CallServerInterceptor
    이 클래스 는 실질 적 인 요청 과 응답 을 담당 하 는 I/O 작업 입 니 다.즉,Socket 전체 12197℃로 요청 데 이 터 를 쓰 고 Socket 전체 12197℃에서 응답 데 이 터 를 읽 습 니 다.
    총결산
    @piasy 의 그림 으로 정리 하면 세련 되 고 구조 도 뚜렷 합 니 다.

    이상 은 안 드 로 이 드 okhttp 의 시작 프로 세 스 와 소스 코드 분석 에 대한 상세 한 내용 입 니 다.안 드 로 이 드 okhttp 의 시작 프로 세 스에 대한 자 료 는 다른 관련 글 을 주목 하 십시오!

    좋은 웹페이지 즐겨찾기