Okhttp源碼分析之責任鏈模式

創建Okhttp對象

// 使用默認的設置創建OkHttpClient
public final OkHttpClient client = new OkHttpClient();

//自定義設置
public final OkHttpClient client = new OkHttpClient.Builder()
        //添加日志攔截器
       .addInterceptor(new HttpLoggingInterceptor())
       .cache(new Cache(cacheDir, cacheSize))
       .build();

接著看構造函數中是如何實現對象的創建的。

  public OkHttpClient() {
    this(new Builder());
  }
  
 OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;  //異步請求分發器
    this.proxy = builder.proxy; //代理設置
    this.protocols = builder.protocols; //Okhttp實現的協議
    this.connectionSpecs = builder.connectionSpecs; //連接配置
    this.interceptors = Util.immutableList(builder.interceptors); // 攔截器
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors); //網絡攔截器
    this.eventListenerFactory = builder.eventListenerFactory; //事件監聽器工廠類,用于創建事件監聽器以監聽Http請求的數量、大小和持續時間。
    this.proxySelector = builder.proxySelector; //代理選擇器
    this.cookieJar = builder.cookieJar; //提供Cookie的策略和持久化
    this.cache = builder.cache; //緩存Http和Https響應到文件系統,以便重用他們
    this.internalCache = builder.internalCache; 
    this.socketFactory = builder.socketFactory; //創建Socket

    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }

    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool; //連接池,用于連接的復用
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout; //連接超時時間
    this.readTimeout = builder.readTimeout; //讀取超時時間
    this.writeTimeout = builder.writeTimeout; // 寫入超時時間
    this.pingInterval = builder.pingInterval;

    if (interceptors.contains(null)) {
      throw new IllegalStateException("Null interceptor: " + interceptors);
    }
    if (networkInterceptors.contains(null)) {
      throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
    }
  }

創建完OkhttpClient,接下來就開始通過client對象創建請求

 @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
 }

實際上調用的是RealCall.newRealCall方法,接著看該方法做了什么

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    // 創建RealCall對象
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

創建完請求之后,就是執行請求,這里分為同步和異步兩種方法,我們先來看同步

  @Override public Response execute() throws IOException {
    //使用同步代碼塊檢測是否已經執行
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    //開始執行
    eventListener.callStart(this);
    try {
      //使用OkHttpClient的Dispatcher將該請求添加到runningSyncCalls隊列中
      client.dispatcher().executed(this);
      //構建一個攔截器鏈
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

接著重點看攔截器鏈這一部分

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors()); //此處是自定義的攔截器,例如日志攔截器
    interceptors.add(retryAndFollowUpInterceptor); //失敗重試和重定向的攔截器
    interceptors.add(new BridgeInterceptor(client.cookieJar())); //用來構建網絡請求
    interceptors.add(new CacheInterceptor(client.internalCache())); //緩存攔截器
    interceptors.add(new ConnectInterceptor(client)); //連接攔截器,打開與網絡服務器的連接
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors()); //網絡攔截器,用于觀察單個網絡請求和響應
    }
    interceptors.add(new CallServerInterceptor(forWebSocket)); //鏈中的最后一個攔截器,用于向服務器進行網絡調用

    //創建一個攔截器鏈,然后按順序的執行攔上面的所有攔截器
    //參數0 代表 當前執行的是攔截器的索引
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

這里執行攔截器使用的是責任鏈模式,不熟悉的可以查找看一下。接著開始看proceed方法:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    //首先判斷當前攔截器索引是否大于等于攔截器列表的長度
    if (index >= interceptors.size()) throw new AssertionError();
    
    calls++;

    //以下兩個方法用來確定
    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //此處開始指定索引為index的攔截器,index從0開始
    //同時會創建索引為index+1的新的攔截器鏈傳遞給將要執行的攔截器,方便在該攔截器執行的合適時候去執行下一個攔截器
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

此處借用Okhttp源碼解析的一幅畫,來說明一下攔截器的具體執行順序。

image.png

BridgeInterceptor

現在我們先放棄我們的自定義攔截器,然后從BridgeInterceptor攔截器開始,先看一下它的Process方法。

@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        //如果存在body,設置Content-Length header
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        //移除傳輸編碼
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        //如果沒有設置body,那么就添加傳輸編碼
        requestBuilder.header("Transfer-Encoding", "chunked");
        //移除內容編碼
        requestBuilder.removeHeader("Content-Length");
      }
    }

    //設置主機
    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    //設置連接類型
    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    //設置接受的編碼列表
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    // 如果存在Cookies,就設置Cookies到header中
    //但是在初始化OkhttpClient的時候,我們使用了NO_COOKIES,所以此處cookies是空的
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    //設置身份標識字符串
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    // 此處開始執行第二個攔截器,也就是CacheInterceptor
    Response networkResponse = chain.proceed(requestBuilder.build());

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

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    //如果請求頭中有gzip,那么就壓縮數據,然后返回構建響應頭,并返回響應信息
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
}

BridgeInterceptor攔截器相當于是給請求添加了一些header,然后執行下一個攔截器,最后等到從所有攔截器執行完再次回到這里的時候對響應數據進行處理。

CacheInterceptor 緩存攔截器

接著分析一下CacheInterceptor,直接從proceed方法開始。

  @Override public Response intercept(Chain chain) throws IOException {
    //判斷是否設置了緩存, 如果設置就從緩存中通過request拿response信息
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //根據請求和緩存數據創建緩存策略
    //如果緩存存在,給緩存更新一下響應頭
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    
    if (cache != null) {
      //此處根據緩存策略來更新:請求數、網絡請求數、緩存命中數
      cache.trackResponse(strategy);
    }

    //緩存不適用的話,就關閉該緩存
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    // 如果禁止使用網絡并且緩存不足的時候,就直接返回失敗的請求
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    // 如果我們不需要網絡的話,到這就結束了,直接返回緩存的響應信息
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      //執行下一個攔截器,傳入網絡請求,獲取網絡響應信息,下一個攔截器為ConnectInterceptor
      //此處的response為從下個攔截器中返回回來的響應信息
      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) {
        //如果緩存存在,不要忘記關閉
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    //如果我們既存在緩存,又獲取到了網絡響應信息
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response 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 {
        closeQuietly(cacheResponse.body());
      }
    }

    //如果沒有網絡信息,就構建網絡響應信息
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        //添加響應到緩存
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

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

    return response;
  }

從文件系統中獲取緩存的方法:

  @Nullable Response get(Request request) {
    //根據url生成一個緩存的key
    //ByteString.encodeUtf8(url.toString()).md5().hex();
    String key = key(request.url());
    DiskLruCache.Snapshot snapshot;
    Entry entry;
    try {
      //根據key返回該條目的快照,如果不存在返回null,如果存在,則返回,并且將該條目移到LRU隊列的頭部
      //下面的都是從快照拿到數據的一些方法,待會分析
      snapshot = cache.get(key);
      if (snapshot == null) {
        return null;
      }
    } catch (IOException e) {
      // Give up because the cache cannot be read.
      return null;
    }

    try {
      entry = new Entry(snapshot.getSource(ENTRY_METADATA));
    } catch (IOException e) {
      Util.closeQuietly(snapshot);
      return null;
    }

    Response response = entry.response(snapshot);

    if (!entry.matches(request, response)) {
      Util.closeQuietly(response.body());
      return null;
    }

    return response;
  }

ConnectInterceptor 連接攔截器

接著分析ConnectInterceptor

  @Override public Response intercept(Chain chain) throws IOException {
    //獲取攔截器鏈
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //從攔截器中獲取請求信息
    Request request = realChain.request();
    //StreamAllocation是用來協調Connections、Streams、Calls三者之間的關系
    // Connections: 到遠程服務器的物理連接,建立比較慢,需要可以取消
    // Streams: 定義了連接可以攜帶多少并發流,Http/1.x是一個,Http/2是2個
    // Calls: 流的邏輯序列
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //此處返回一個HttpCodec,他是用來對請求進行編碼,并且對響應進行解碼的一個類。
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();
    
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

CallServerInterceptor

這是最后一個攔截器。

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    realChain.eventListener().requestHeadersStart(realChain.call());
    //整理請求頭并寫入
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != 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".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      //寫入請求體
      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!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.
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();
    //得到響應頭
    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }
    //構造response
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int 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
      //如果code為100,需要繼續讀取真正的響應
      responseBuilder = httpCodec.readResponseHeaders(false);

      response = responseBuilder
              .request(request)
              .handshake(streamAllocation.connection().handshake())
              .sentRequestAtMillis(sentRequestMillis)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();

      code = response.code();
    }

    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), response);

    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
    //讀取body
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

異步請求

同步請求使用的是execute方法,異步請求使用的是enqueue,接下來我們就看一下RealCall中的異步請求方法

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      //這里也是用來判斷是否執行過
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //此處將回調函數封裝成了一個AsyncCall,并且將該請求添加到了一個runningAsyncCalls隊列中
    //AsyncCall實際上就是一個Runnable
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

往runningAsyncCalls隊列中添加的邏輯如下

  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  //如果隊列中的請求數小于最大請求數或者對于每個host的請求數小于5才會被添加進去
  //否則將被添加到準備隊列中
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      //此處使用了線程池來進行操作
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

接著我們看一下executorService方法

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      //此處創建了一個線程池
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;

接著看一下AsyncCallexecute方法,會發現跟同步請求是一樣的

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
       //此處就是我們在上面分析的責任鏈模式
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

總結

畫了一個簡易流程圖,然后配合上面的文字說明看會更好理解一點,接下來也會分模塊的進行分析。

image.png
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • 版本號:3.13.1 一.基本使用 Call可以理解為Request和Response之間的橋梁,Http請求過程...
    慕涵盛華閱讀 1,028評論 0 8
  • 主目錄見:Android高級進階知識(這是總目錄索引)?OkHttp的知識點實在是不少,優秀的思想也有很多,這里只...
    ZJ_Rocky閱讀 2,314評論 2 6
  • 一、引言 在我們日常開發中,OkHttp可謂是最常用的開源庫之一,目前就連Android API中的網絡請求接口都...
    horseLai閱讀 1,641評論 4 11
  • 本文為本人原創,轉載請注明作者和出處。 在上一章我們分析了Okhttp分發器對同步/異步請求的處理,本章將和大家一...
    業松閱讀 982評論 2 8
  • 管理的實質是出于自己的價值觀做思考后進行選擇。領導水平的高低也決定了其價值觀的高低。所以高水平的領導往往從很高的高...
    簡述塔母閱讀 244評論 1 0