OkHttpClient源碼分析(一)—— 同步、異步請(qǐng)求分析和Dispatcher的任務(wù)調(diào)度

OkHttpClient同步請(qǐng)求的執(zhí)行流程和源碼分析

同步請(qǐng)求示例

OkHttpClient okHttpClient = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        Request request = new Request.Builder().url("https://www.baidu.com").get().build();
        Call call = okHttpClient.newCall(request);
        try {
            Response response = call.execute();
            Log.e(TAG,"response: " + response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }

同步請(qǐng)求的步驟

  1. 創(chuàng)建OkHttpClient對(duì)象和Request對(duì)象,均是采用Builder模式創(chuàng)建,構(gòu)建者(Builder)設(shè)計(jì)模式(又叫生成器設(shè)計(jì)模式)
  1. 將Request封裝成Call對(duì)象
  1. 調(diào)用Call的execute()方法發(fā)送同步請(qǐng)求,發(fā)送請(qǐng)求后,就會(huì)進(jìn)入阻塞狀態(tài),直到收到響應(yīng)。

一、(1)OkHttpClient Builder對(duì)象分析

public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
}

??OkHttpClient Builder的構(gòu)造函數(shù),主要是對(duì)一些參數(shù)賦值默認(rèn)值,對(duì)一些對(duì)象進(jìn)行初始化,Dispatcher是OkHttpClient中http請(qǐng)求的分發(fā)器,由它來(lái)決定異步請(qǐng)求是直接處理還是進(jìn)行緩存等待,對(duì)于同步請(qǐng)求,它并沒(méi)有做太多操作,只是把同步請(qǐng)求放到隊(duì)列當(dāng)中去執(zhí)行。ConnectionPool是一個(gè)連接池對(duì)象,用于管理連接對(duì)象,當(dāng)存在同樣的Url請(qǐng)求時(shí),可以復(fù)用,從連接池中找到對(duì)應(yīng)緩存的連接對(duì)象。

(2)Request 對(duì)象分析

 public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
}

Request Builder的構(gòu)造函數(shù),默認(rèn)請(qǐng)求方法為GET,同時(shí)初始化一個(gè)Header對(duì)象。

 public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
}

build()方法是創(chuàng)建Request對(duì)象,將當(dāng)前的builder對(duì)象傳入,接下來(lái)看Request的構(gòu)造函數(shù):

Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
  }

??可以看到,是將傳入的Builder對(duì)象中的屬性賦值給Request的相關(guān)屬性,這樣就創(chuàng)建好了Request對(duì)象。

二、創(chuàng)建Call 對(duì)象分析

/**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

??OkHttpClient 對(duì)象中的newCall()方法,返回值是一個(gè)Call對(duì)象(接口),在這里可以看到實(shí)際上調(diào)用的RealCall.newRealCall()方法創(chuàng)建,RealCall是Call接口的一個(gè)實(shí)現(xiàn)類(lèi),接著查看RealCall類(lèi)中newRealCall()方法:

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

??可以看到newRealCall()方法中創(chuàng)建了Call接口的實(shí)現(xiàn)類(lèi)RealCall對(duì)象并返回該對(duì)象,到此Call對(duì)象的創(chuàng)建便完成了。

三、Call 對(duì)象exexcute()方法分析

??上面有提及到Call對(duì)象是一個(gè)接口,我們點(diǎn)擊查看exexcute()方法時(shí),需要點(diǎn)擊查看該方法的實(shí)現(xiàn),實(shí)際上是進(jìn)入到RealCall對(duì)象的exexcute()方法:

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      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);
    }
  }

??在同步代碼中,先通過(guò)判斷executed標(biāo)識(shí),如果當(dāng)前已經(jīng)有在執(zhí)行,則會(huì)拋出"Already Executed"信息的異常,如果沒(méi)有執(zhí)行過(guò),則更改executed標(biāo)識(shí)為true。

??接著調(diào)用captureCallStackTrace()方法,這個(gè)方法主要用于捕捉一些http請(qǐng)求的異常堆棧信息。

??eventListener.callStart(this)開(kāi)啟事件監(jiān)聽(tīng),通過(guò)查看該方法:

 /**
   * Invoked as soon as a call is enqueued or executed by a client. In case of thread or stream
   * limits, this call may be executed well before processing the request is able to begin.
   *
   * <p>This will be invoked only once for a single {@link Call}. Retries of different routes
   * or redirects will be handled within the boundaries of a single callStart and {@link
   * #callEnd}/{@link #callFailed} pair.
   */
  public void callStart(Call call) {
  }

??通過(guò)閱讀該方法的注釋?zhuān)梢灾涝摲椒〞?huì)在調(diào)用Call對(duì)象的enqueue()或execute()方法的時(shí)候,就會(huì)開(kāi)啟這個(gè)listener。

接下來(lái)分析一下這個(gè)方法中的核心代碼:

client.dispatcher().executed(this);

首先調(diào)用OkHttpClient的dispatcher()方法

public Dispatcher dispatcher() {
    return dispatcher;
}

該方法返回一個(gè)Dispatcher對(duì)象,緊接著調(diào)用該對(duì)象的executed()方法:


  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

??該方法中,runningSyncCalls是一個(gè)存放同步請(qǐng)求的隊(duì)列,這里僅僅只是將RealCall加入到同步請(qǐng)求的隊(duì)列中,Dispatcher對(duì)象中相關(guān)的隊(duì)列有:

 /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  • readyAsyncCalls 是異步請(qǐng)求的就緒隊(duì)列
  • runningAsyncCalls 是異步請(qǐng)求的執(zhí)行隊(duì)列
  • runningSyncCalls 是同步請(qǐng)求的執(zhí)行隊(duì)列

??調(diào)用完Dispatcher的executed()方法后,緊接著調(diào)用getResponseWithInterceptorChain()方法獲取Response對(duì)象,這個(gè)其實(shí)是一個(gè)攔截器鏈的方法,該方法內(nèi)部會(huì)依次調(diào)用攔截器來(lái)進(jìn)行相應(yīng)的操作。

最后看一下finally中:

finally {
      client.dispatcher().finished(this);
}

??通過(guò)調(diào)用Dispatcher的finished()方法,傳入當(dāng)前的RealCall對(duì)象,查看該方法的代碼可以發(fā)現(xiàn):

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }
  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

??該方法繼續(xù)調(diào)用了其他一個(gè)同名的的方法,將正在執(zhí)行的同步請(qǐng)求隊(duì)列傳了進(jìn)來(lái),在同步代碼塊中,移除掉同步請(qǐng)求隊(duì)列中的call對(duì)象,并進(jìn)行了判斷,如果移除出錯(cuò),則會(huì)拋出異常。接著判斷promoteCalls,由于這里傳入的promoteCalls為false,所以不會(huì)走promoteCalls()方法。

??接著,對(duì)runningCallsCount重新賦值,runningCallsCount用于記錄當(dāng)前正在執(zhí)行的請(qǐng)求數(shù),查看該方法的代碼:

public synchronized int runningCallsCount() {
    return runningAsyncCalls.size() + runningSyncCalls.size();
}

該方法很簡(jiǎn)單,即返回正在執(zhí)行的異步請(qǐng)求數(shù)和正在執(zhí)行的同步請(qǐng)求數(shù)的總和。

if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
}

??最后通過(guò)判斷當(dāng)前正在執(zhí)行的請(qǐng)求數(shù),如果當(dāng)前沒(méi)有正在執(zhí)行的請(qǐng)求數(shù)并且有設(shè)置閑置時(shí)的回調(diào),則會(huì)回調(diào)其run()方法。

總結(jié)

??到此,同步請(qǐng)求的執(zhí)行流程就已經(jīng)分析完了,由上述的分析可以知道,在同步請(qǐng)求中,Dispatcher分發(fā)器做的工作非常簡(jiǎn)單,就兩個(gè)操作,保存同步請(qǐng)求和移除同步請(qǐng)求

OkHttpClient異步請(qǐng)求的執(zhí)行流程和源碼分析

異步請(qǐng)求示例

 OkHttpClient okHttpClient = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        Request request = new Request.Builder().url("https://www.baidu.com").get().build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });

異步請(qǐng)求的步驟

  1. 創(chuàng)建OkHttpClient對(duì)象和Request對(duì)象,均是采用Builder模式創(chuàng)建,構(gòu)建者(Builder)設(shè)計(jì)模式(又叫生成器設(shè)計(jì)模式)
  1. 將Request封裝成Call對(duì)象
  1. 調(diào)用Call的enqueue()方法進(jìn)行異步請(qǐng)求

同步和異步的區(qū)別

  1. 發(fā)起請(qǐng)求的方法調(diào)用
  1. 阻塞線程與否

源碼分析

??異步請(qǐng)求的前兩步,和同步請(qǐng)求的一致,都是一些準(zhǔn)備工作,并沒(méi)有發(fā)起請(qǐng)求,這里不再重復(fù)說(shuō)明,最主要的是第三步,調(diào)用Call對(duì)象的enqueue()方法,具體的實(shí)現(xiàn)還是在RealCall類(lèi)中,查看該方法代碼:

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

??前面的操作和同步請(qǐng)求的execute()方法相似,主要是 client.dispatcher().enqueue(new AsyncCall(responseCallback)) 這行代碼,調(diào)用Dispatcher的enqueue()方法,將Callback回調(diào)封裝成AsyncCall對(duì)象作為參數(shù)傳入,通過(guò)查看代碼,了解到AsyncCall對(duì)象繼承自NamedRunnable對(duì)象,而NamedRunnable對(duì)象實(shí)現(xiàn)了Runnable接口,接著繼續(xù)查看Dispatcher的enqueue()方法源碼:

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

??該方法前加了synchronized修飾符,是一個(gè)同步方法,根據(jù)判斷當(dāng)前執(zhí)行的異步請(qǐng)求數(shù)是否小于maxRequests(最大請(qǐng)求數(shù),默認(rèn)為64) 且當(dāng)前執(zhí)行的異步請(qǐng)求隊(duì)列中相同主機(jī)的請(qǐng)求數(shù)小于maxRequestsPerHost(每個(gè)主機(jī)最大請(qǐng)求數(shù),默認(rèn)為5) 來(lái)進(jìn)行處理,如果二者都小于設(shè)置的值,則將該請(qǐng)求添加到runningAsyncCalls(異步請(qǐng)求執(zhí)行隊(duì)列)中,否則則添加到readyAsyncCalls(異步請(qǐng)求準(zhǔn)備隊(duì)列)中。

runningCallsForHost()方法的代碼:

 /** Returns the number of running calls that share a host with {@code call}. */
  private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.get().forWebSocket) continue;
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

??通過(guò)注釋可以知道,該方法返回同一個(gè)主機(jī)的請(qǐng)求數(shù)目,通過(guò)遍歷執(zhí)行中的異步請(qǐng)求隊(duì)列,和傳入的AsyncCall對(duì)象的主機(jī)對(duì)比,如果相同則記錄數(shù)遞增,以此獲得和傳入AsyncCall對(duì)象相同主機(jī)的請(qǐng)求數(shù)。

enqueue()方法中,主要的代碼:

executorService().execute(call);

這里是進(jìn)行異步請(qǐng)求操作的代碼,先看下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;
}

??該方法也是一個(gè)同步方法,主要用于返回 ExecutorService 對(duì)象,在這里僅一次創(chuàng)建了線程池對(duì)象 ThreadPoolExecutor,第二個(gè)參數(shù)傳入了Integer的最大值,即線程池所能容納的最大線程數(shù)為Integer.MAX_VALUE,雖然這里設(shè)置了很大的值,但是實(shí)際情況下并非會(huì)達(dá)到最大值,因?yàn)樯厦鎒nqueue()方法中有做了判斷,主要的還是maxRequests這個(gè)值決定異步請(qǐng)求線程池的最大數(shù)量。

??executorService()方法返回了線程池對(duì)象,接著調(diào)用它的execute()方法,傳入實(shí)現(xiàn)Runnable接口的AsyncCall對(duì)象,上面提及到AsyncCall繼承NamedRunnable,而NamedRunnable對(duì)象實(shí)現(xiàn)了Runnable接口,所以我們想知道該線程池執(zhí)行這個(gè)任務(wù)做了什么,就得看下NamedRunnable對(duì)象的 run() 方法:

@Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
}

該方法中,真正的處理邏輯是在execute()方法中:

 protected abstract void execute();

而execute()方法是一個(gè)抽象方法,所以要回到繼承NamedRunnable對(duì)象的AsyncCall類(lèi)中:

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

??這里才是真正進(jìn)行異步請(qǐng)求操作的邏輯,同樣也是通過(guò)getResponseWithInterceptorChain()方法得到Response對(duì)象,關(guān)于getResponseWithInterceptorChain()方法的分析在下面的文章里將會(huì)介紹,接著通過(guò)判斷retryAndFollowUpInterceptor是否取消回調(diào)CallBack接口的onFailure()或onResponse()方法,最后finally中,和同步請(qǐng)求的處理一樣,調(diào)用了Dispatcher對(duì)象的finished()方法:

 /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

也是調(diào)用了帶三個(gè)參數(shù)的finished()方法,傳入了runningAsyncCalls,call,第三個(gè)參數(shù)傳入了true。

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
}

這里的處理和同步請(qǐng)求結(jié)束后的處理多了一個(gè)promoteCalls()方法的調(diào)用,因?yàn)檫@里promoteCalls傳入了true,所以會(huì)走promoteCalls()方法:

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

??看完這個(gè)方法,會(huì)有一種恍然大悟的感覺(jué),因?yàn)樯厦嬲{(diào)用enqueue()方法的時(shí)候,會(huì)根據(jù)情況將請(qǐng)求添加到runningAsyncCalls(異步請(qǐng)求執(zhí)行隊(duì)列)或readyAsyncCalls(異步請(qǐng)求準(zhǔn)備隊(duì)列)中,而readyAsyncCalls隊(duì)列中的請(qǐng)求什么時(shí)候執(zhí)行呢,相信在看enqueue()方法的時(shí)候會(huì)有這個(gè)疑問(wèn),看了promoteCalls()后疑問(wèn)將會(huì)被解答,為了方便閱讀再次貼上enqueue()方法:

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

??promoteCalls()方法中,首先做了一些判斷,當(dāng)runningAsyncCalls(異步請(qǐng)求執(zhí)行隊(duì)列)已經(jīng)達(dá)到設(shè)置的最大的請(qǐng)求數(shù)或當(dāng)前readyAsyncCalls(異步請(qǐng)求準(zhǔn)備隊(duì)列)中沒(méi)有請(qǐng)求的時(shí)候,則直接返回不做處理,如果滿足條件,則會(huì)遍歷readyAsyncCalls隊(duì)列,將該請(qǐng)求添加到runningAsyncCalls隊(duì)列中,并調(diào)用 executorService().execute(call) 對(duì)該請(qǐng)求進(jìn)行處理。

總結(jié)

??如果異步請(qǐng)求數(shù)超過(guò)最大請(qǐng)求數(shù)或同個(gè)主機(jī)最大請(qǐng)求數(shù)超過(guò)設(shè)置的值的時(shí)候,該請(qǐng)求就會(huì)添加到readyAsyncCalls(異步請(qǐng)求準(zhǔn)備隊(duì)列)中,當(dāng)執(zhí)行完runningAsyncCalls(異步請(qǐng)求執(zhí)行隊(duì)列)的請(qǐng)求后,將會(huì)調(diào)用Dispatcher的finished()三個(gè)參數(shù)的方法,第三個(gè)參數(shù)傳入true,會(huì)調(diào)用promoteCalls()方法,遍歷準(zhǔn)備隊(duì)列readyAsyncCalls,將該隊(duì)列的中的請(qǐng)求添加到執(zhí)行隊(duì)列runningAsyncCalls中,調(diào)用 executorService().execute(call)進(jìn)行處理。

Dispatcher的作用

維護(hù)請(qǐng)求的狀態(tài),并維護(hù)一個(gè)線程池,用于執(zhí)行請(qǐng)求。

異步請(qǐng)求為什么需要兩個(gè)隊(duì)列

異步請(qǐng)求的設(shè)計(jì)可以將其理解成生產(chǎn)者消費(fèi)者模式,其中各個(gè)角色分別為:

  • Dispatcher 生產(chǎn)者
  • ExecutorService 消費(fèi)者池
  • Deque<AsyncCall> readyAsyncCalls 緩存
  • Deque<AsyncCall> runningAsyncCalls 正在運(yùn)行的任務(wù)

當(dāng)同步和異步請(qǐng)求結(jié)束后,會(huì)調(diào)用dispatcher的finished方法,將當(dāng)前的請(qǐng)求從隊(duì)列中移除。

下一篇文章中,將為大家講解一下OkHttp的攔截器鏈,感興趣的朋友可以繼續(xù)閱讀:

OkHttpClient源碼分析(二) —— RetryAndFollowUpInterceptor和BridgeInterceptor

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

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

  • OKHTTP異步和同步請(qǐng)求簡(jiǎn)單分析OKHTTP攔截器緩存策略CacheInterceptor的簡(jiǎn)單分析OKHTTP...
    未見(jiàn)哥哥閱讀 35,422評(píng)論 6 27
  • 最近學(xué)習(xí)了一下okhttp的源碼,發(fā)現(xiàn)okhttp是真滴復(fù)雜。因?yàn)閛khttp是一個(gè)網(wǎng)絡(luò)請(qǐng)求庫(kù),它涉及了網(wǎng)絡(luò)請(qǐng)求的...
    有興不虛昧閱讀 635評(píng)論 0 1
  • 前幾天在在一本書(shū)上看到這樣一句話 如果僅從微觀的視角關(guān)注每一個(gè)單獨(dú)的點(diǎn),可能會(huì)因?yàn)榭床坏秸w而迷失方向。 所以不會(huì)...
    Utte閱讀 697評(píng)論 0 0
  • 本文為本人原創(chuàng),轉(zhuǎn)載請(qǐng)注明作者和出處。 OkHttp可以說(shuō)是目前Android開(kāi)發(fā)中最流行的基礎(chǔ)網(wǎng)絡(luò)框架了。相信你...
    業(yè)松閱讀 625評(píng)論 0 4
  • 君子這個(gè)詞在現(xiàn)在很難聽(tīng)到。本來(lái)一個(gè)這么好的詞,有時(shí)也被用得讓人不知其好壞。君子是個(gè)對(duì)人評(píng)價(jià)很高的詞,難道是現(xiàn)代...
    曼頭閱讀 695評(píng)論 0 0