okHttp源碼解讀(一)

okHttp 出來有一段時(shí)間了,也不是沒用過而是沒在項(xiàng)目中實(shí)際用過,如今新項(xiàng)目就用了這個(gè)網(wǎng)絡(luò)框架隧哮,在項(xiàng)目中做了簡單的封裝和使用艳汽,對于源碼我過了一遍,主要看整體流程,更容易把握整體架構(gòu)静陈。

1. 整體流程

構(gòu)建 okHttpClient -------->構(gòu)建 Request.Builder ----------->發(fā)送請求秫舌,構(gòu)建Call(RealCall),有同步(execute)和 異步(enqueue)請求 -------------> Dispatcher
---------> InterceptorChain -------------> 獲取 response 再返回璧眠;

2. 攔截器

在看這個(gè)源碼的時(shí)候责静,我主要看的是這個(gè)攔截器部分 灾螃;攔截器得從請求入口那里說起腰鬼;

RealCall.kt

//無論是同步請求還是異步請求,都差不多,這里以同步為例齿税;
override fun execute(): Response {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    transmitter.timeoutEnter()
    transmitter.callStart()
    try {
      client.dispatcher.executed(this) ----------->(1)
      return getResponseWithInterceptorChain() ------------->(2)
    } finally {
      client.dispatcher.finished(this)
    }
  }
  • 注釋(1)處:表示把當(dāng)前的請求(call)添加到隊(duì)列中去乌助,(runningSyncCalls :ArrayDeque<RealCall>() )
  • 注釋(2)處:getResponseWithInterceptorChain() 調(diào)用獲取這個(gè) 攔截器鏈并獲取結(jié)果 返回他托,主要就是這個(gè)方法赏参。

RealCall.kt

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(interceptors, transmitter, null, 0, originalRequest, this,
        client.connectTimeoutMillis, client.readTimeoutMillis, 
client.writeTimeoutMillis) -------->(1)

    var calledNoMoreExchanges = false
    try {
      val response = chain.proceed(originalRequest) ---------->(2)
      if (transmitter.isCanceled) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
        ...
    } finally {
      ....
    }
  }
  • 在代碼的一開始構(gòu)建了攔截器的集合把篓,interceptors ,分別按照順序韧掩,依次添加了 :
    攔截器1:RetryAndFollowUpInterceptor
    攔截器2: BridgeInterceptor
    攔截器3:CacheInterceptor
    攔截器4:ConnecInterceptor
    攔截器5:CallServerInterceptor
    而后就構(gòu)建了 RealInterceptorChain ----->chain 在注釋(1)處 疗锐;
    在注釋(2)處滑臊,調(diào)用 chain.proceed(originalRequest) 返回 response
    關(guān)鍵方法就是:proceed
    RealInterceptorChain.kt
fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
    if (index >= interceptors.size) throw AssertionError()

    calls++
    ...

    // Call the next interceptor in the chain.
    val next = RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout) -------->(1)
    val interceptor = interceptors[index]   ------->(2)

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

    // Confirm that the next interceptor made its required call to chain.proceed().
    check(exchange == null || 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
  }
  • 注釋(1)處鬓椭,我們看到 第四個(gè)參數(shù) :index+1 ,之前的時(shí)候我們傳入的是0 小染,這個(gè)表示取出下一個(gè) RealInterceptorChain ;
  • 在注釋(2)處氧映,取出當(dāng)前的攔截器:interceptor
  • 在注釋(3)處,調(diào)用攔截器的 intercept 攔截方法律姨,并將下一個(gè)攔截器傳入進(jìn)去 择份;
    每一個(gè)攔截器都是 繼承于 Interceptor 并 重寫 interceptor 方法 荣赶;如果要自定義攔截器也是如此拔创;

2.1 第一個(gè)攔截器也就是:RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor.kt

/**關(guān)于該攔截器的介紹
 * This interceptor recovers from failures and follows redirects as necessary. It may throw an
 * [IOException] if the call was canceled.
就是說會(huì)從失敗中恢復(fù)并且在必要的時(shí)候重定向慢逾,如果請求被取消可能會(huì)拋出IOException.
 */
override fun intercept(chain: Interceptor.Chain): Response {
    var request = chain.request()  ------>取出request
    val realChain = chain as RealInterceptorChain  
    val transmitter = realChain.transmitter()
    var followUpCount = 0
    var priorResponse: Response? = null --------->重定向前的 response
    while (true) {  ------> 這里是個(gè)死循環(huán)
      transmitter.prepareToConnect(request)

      if (transmitter.isCanceled) {
        throw IOException("Canceled")
      }

      var response: Response
      var success = false
      try {
        response = realChain.proceed(request, transmitter, null) --------> 這里鏈?zhǔn)秸{(diào)用侣滩,就傳遞到下一個(gè)攔截器君珠,也是調(diào)用下個(gè)攔截器的 interceptor 方法
        success = true
      } catch (e: RouteException) {
        // The attempt to connect via a route failed. The request will not have been sent.
        如果路由失敗材部,請求不會(huì)再次發(fā)送
        if (!recover(e.lastConnectException, transmitter, false, request)) {
          throw e.firstConnectException
        }
        continue
      } catch (e: IOException) {
        // An attempt to communicate with a server failed. The request may have been sent.如果與服務(wù)器交流建立鏈接失敗败富,可能會(huì)重新發(fā)送請求
        val requestSendStarted = e !is ConnectionShutdownException
        if (!recover(e, transmitter, requestSendStarted, request)) throw e
        continue
      } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
          transmitter.exchangeDoneDueToException()
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      這里嘗試獲取之前的response兽叮,如果存在的鹦聪,這樣的response 沒有body,可以看到 body 置為了 null ;
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                .body(null)
                .build())
            .build()
      }

      val exchange = response.exchange
      val route = exchange?.connection()?.route()
      val followUp = followUpRequest(response, route)

      if (followUp == null) {
        if (exchange != null && exchange.isDuplex) {
          transmitter.timeoutEarlyExit()
        }
        return response
      }

      val followUpBody = followUp.body
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response
      }

      response.body?.closeQuietly()
      if (transmitter.hasExchange()) {
        exchange?.detachWithViolence()
      }

      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw ProtocolException("Too many follow-up requests: $followUpCount")
      }

      request = followUp
      priorResponse = response ----->這里對priResponse 賦值
    }
  }

2.2 攔截器2:BridgeInterceptor

BridgeInterceptor.kt

/**就是把用戶請求轉(zhuǎn)化為網(wǎng)絡(luò)(network)請求, 把服務(wù)器響應(yīng)轉(zhuǎn)化為用戶友好的響應(yīng)
 * Bridges from application code to network code. First it builds a network request from a user
 * request. Then it proceeds to call the network. Finally it builds a user response from the network
 * response.
 */
override fun intercept(chain: Interceptor.Chain): Response {
    val userRequest = chain.request()
    val requestBuilder = userRequest.newBuilder()

    val body = userRequest.body
    if (body != null) {
      val contentType = body.contentType()
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString()) ----->(1)
      }

      val contentLength = body.contentLength()
      if (contentLength != -1L) {
        requestBuilder.header("Content-Length", contentLength.toString()) ----->(2)
        requestBuilder.removeHeader("Transfer-Encoding")
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked")
        requestBuilder.removeHeader("Content-Length")
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", userRequest.url.toHostHeader())----->(3)
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive")----->(4)
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    var transparentGzip = false
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true
      requestBuilder.header("Accept-Encoding", "gzip")
    }

    val cookies = cookieJar.loadForRequest(userRequest.url)
    if (cookies.isNotEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies))----->(5)
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", userAgent)----->(6)
    }

    val networkResponse = chain.proceed(requestBuilder.build()) -------->(7)

    cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)

    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())
        val strippedHeaders = networkResponse.headers.newBuilder()
            .removeAll("Content-Encoding")
            .removeAll("Content-Length")
            .build()
        responseBuilder.headers(strippedHeaders)
        val contentType = networkResponse.header("Content-Type")
        responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
      }
    }

    return responseBuilder.build()
  }

這個(gè)每個(gè)不同的攔截器都有自己不同的處理规丽,這里這個(gè)攔截器主要就是對 用戶發(fā)出的request 轉(zhuǎn)化為服務(wù)器需要的 request , 同樣 response 也做一定的處理轉(zhuǎn)化赌莺;

  • 注釋(1)-(6)都是在做這樣的轉(zhuǎn)化艘狭,看名字:userRequest -----> requestBuilder
  • 注釋(7)就又是鏈?zhǔn)秸{(diào)用下一個(gè)攔截器了 得到 networkResponse
  • 下面就是拿到 networkResponse 進(jìn)行轉(zhuǎn)化了巢音,轉(zhuǎn)變?yōu)?-----> responseBuilder

2.3 攔截器3:CacheInterceptor

CacheInterceptor.kt

/** Serves requests from the cache and writes responses to the cache. */
從緩存獲取請求官撼,并且將response寫入緩存 ,這跟你在構(gòu)建 OKHttpClinent 是否設(shè)置緩存有關(guān)
override fun intercept(chain: Interceptor.Chain): Response {
    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)

    if (cacheCandidate != null && cacheResponse == null) { ------->(1)
      // 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) {-------->(2)
      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()
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {----------> (3)
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build()
    }

    var networkResponse: Response? = null
    try {
      networkResponse = chain.proceed(networkRequest)  --------> (4)
    } 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) {--------->(5)
        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() ---------> (6)

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

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

    return response
  }
  • 注釋(1):有緩存但是不允許緩存 ;
  • 注釋(2):networkRequest == null && cacheResponse == null 根據(jù)注釋說明 If we're forbidden from using the network and the cache is insufficient, fail 就是說如果我們禁止從網(wǎng)絡(luò)獲取并且也無緩存斜筐,那就失敗了顷链,構(gòu)建相應(yīng)的 response ; body 我們能看到是 EMPTY_RESPONSE ;
  • 注釋(3):If we don't need the network, we're done 表示有緩存嗤练,如果我們設(shè)置了不從網(wǎng)絡(luò)加載數(shù)據(jù),那么也構(gòu)建相應(yīng)的 response 霜大;
  • 注釋(4):同樣調(diào)用下個(gè)攔截器的 interceptor 方法 战坤;
  • 注釋(5):判斷網(wǎng)絡(luò)得到的 response ,是否修改残拐,是否要更新緩存,如果修改了就更新緩存 囊卜;
  • 注釋(6):構(gòu)建返回的 response

2.4 攔截器4:ConnectInterceptor

ConnectInterceptor.kt

該攔截器主要就負(fù)責(zé)與服務(wù)器建立連接栅组,然后繼續(xù)下個(gè)攔截器
/** Opens a connection to the target server and proceeds to the next interceptor. */
override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val request = realChain.request()
    val transmitter = realChain.transmitter()

    // We need the network to satisfy this request. Possibly for validating a conditional GET.我們需要此次請求是安全的笑窜,可能會(huì)先驗(yàn)證一下 get 請求
    val doExtensiveHealthChecks = request.method != "GET"
    val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)

    return realChain.proceed(request, transmitter, exchange)
  }

這個(gè)建立與服務(wù)器建立連接,在 newExchange 中嫌蚤,一直跟著找能發(fā)現(xiàn)以下的代碼:

internal fun newCodec(client: OkHttpClient, chain: Interceptor.Chain): ExchangeCodec {
    val socket = this.socket!!
    val source = this.source!!
    val sink = this.sink!!
    val http2Connection = this.http2Connection

    return if (http2Connection != null) {
      Http2ExchangeCodec(client, this, chain, http2Connection)
    } else {
      socket.soTimeout = chain.readTimeoutMillis()
      source.timeout().timeout(chain.readTimeoutMillis().toLong(), MILLISECONDS)
      sink.timeout().timeout(chain.writeTimeoutMillis().toLong(), MILLISECONDS)
      Http1ExchangeCodec(client, this, source, sink)
    }
  }

根據(jù)不同的 http 協(xié)議脱吱,分別就是:
Http1ExchangeCodec : http1.1 ;
Http2ExchangeCodechttp2.0 ;
做不同的處理 箱蝠;

2.5 攔截器5:CallServerInterceptor

CallServerInterceptor.kt

這是鏈?zhǔn)秸{(diào)用的最后一個(gè)攔截器,會(huì)向服務(wù)器發(fā)送一個(gè)請求
/** This is the last interceptor in the chain. It makes a network call to the server. */
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange()
    val request = realChain.request()
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()

    exchange.writeRequestHeaders(request)

    var responseHeadersStarted = false
    var responseBuilder: Response.Builder? = null
    if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
        exchange.flushRequest()
        responseHeadersStarted = true
        exchange.responseHeadersStart()
        responseBuilder = exchange.readResponseHeaders(true)
      }
      if (responseBuilder == null) {
        if (requestBody.isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest()
          val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
          requestBody.writeTo(bufferedRequestBody)
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
          requestBody.writeTo(bufferedRequestBody)
          bufferedRequestBody.close()
        }
      } else {
        exchange.noRequestBody()
        if (!exchange.connection()!!.isMultiplexed) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          exchange.noNewExchangesOnConnection()
        }
      }
    } else {
      exchange.noRequestBody()
    }

    if (requestBody == null || !requestBody.isDuplex()) {
      exchange.finishRequest()
    }
    if (!responseHeadersStarted) {
      exchange.responseHeadersStart()
    }
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false)!!
    }
    var response = responseBuilder
        .request(request)
        .handshake(exchange.connection()!!.handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
    var code = response.code
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      response = exchange.readResponseHeaders(false)!!
          .request(request)
          .handshake(exchange.connection()!!.handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
      code = response.code
    }

    exchange.responseHeadersEnd(response)

    response = if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response.newBuilder()
          .body(EMPTY_RESPONSE)
          .build()
    } else {
      response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()
    }
    if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
        "close".equals(response.header("Connection"), ignoreCase = true)) {
      exchange.noNewExchangesOnConnection()
    }
    if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
      throw ProtocolException(
          "HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
    }
    return response
  }

基本就是發(fā)起網(wǎng)絡(luò)請求了以及對各種返回碼的處理矾克,我在網(wǎng)上找到關(guān)于攔截器之間的一個(gè)動(dòng)態(tài)圖胁附,挺形象的:


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末控妻,一起剝皮案震驚了整個(gè)濱河市弓候,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彰居,老刑警劉巖陈惰,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抬闯,死亡現(xiàn)場離奇詭異溶握,居然都是意外死亡睡榆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門宿崭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來才写,“玉大人,你說我怎么就攤上這事讹堤。” “怎么了檀头?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵暑始,是天一觀的道長婴削。 經(jīng)常有香客問我,道長嗤朴,這世上最難降的妖魔是什么雹姊? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任吱雏,我火速辦了婚禮歧杏,結(jié)果婚禮上犬绒,老公的妹妹穿的比我還像新娘兑凿。我一直安慰自己礼华,他們只是感情好卓嫂,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布晨雳。 她就那樣靜靜地躺著餐禁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氧吐。 梳的紋絲不亂的頭發(fā)上筑舅,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天翠拣,我揣著相機(jī)與錄音误墓,去河邊找鬼益缎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛欣范,可吹牛的內(nèi)容都是我干的熙卡。 我是一名探鬼主播驳癌,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼颓鲜,長吁一口氣:“原來是場噩夢啊……” “哼甜滨!你這毒婦竟也來了瘤袖?” 一聲冷哼從身側(cè)響起捂敌,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤既琴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后泡嘴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體甫恩,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年酌予,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磺箕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抛虫,死狀恐怖松靡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情莱褒,我是刑警寧澤击困,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布阅茶,位于F島的核電站,受9級特大地震影響撞蜂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浦旱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一甥捺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谷饿,春花似錦盯蝴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽很魂。三九已至谁榜,卻和暖如春帝蒿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留端朵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓裙戏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親葛作。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • OkHttp解析系列 OkHttp解析(一)從用法看清原理OkHttp解析(二)網(wǎng)絡(luò)連接OkHttp解析(三)關(guān)于...
    Hohohong閱讀 20,979評論 4 58
  • 本文為本人原創(chuàng)途乃,轉(zhuǎn)載請注明作者和出處烫饼。 在上一章我們分析了Okhttp分發(fā)器對同步/異步請求的處理钩骇,本章將和大家一...
    業(yè)松閱讀 977評論 2 8
  • 那么我今天給大家簡單地講一下Okhttp這款網(wǎng)絡(luò)框架及其原理。它是如何請求數(shù)據(jù)拍谐,如何響應(yīng)數(shù)據(jù)的 有什么優(yōu)點(diǎn)?它的應(yīng)...
    卓而不群_0137閱讀 317評論 0 1
  • 前言 用OkHttp很久了,也看了很多人寫的源碼分析崖面,在這里結(jié)合自己的感悟庶香,記錄一下對OkHttp源碼理解的幾點(diǎn)心...
    Java小鋪閱讀 1,522評論 0 13
  • 今天李老師在課堂中講到了一點(diǎn)颈走,就是心理咨詢師锐膜,要去擴(kuò)大自己的知識(shí)面。而要去豐富自己的很大一個(gè)點(diǎn)就是要看閑書。對于這...
    靜靜的葉子閱讀 321評論 0 0