OkHttp使用及源碼學(xué)習(xí)

本文僅為學(xué)習(xí)筆記辟宗;不是原創(chuàng)文章碗硬;

使用教程
源碼分析參考1
源碼分析參考2

一:OkHttp連接池復(fù)用

1.1 持久連接

HTTP非持久連接

HTTP持久連接

持久連接(HTTP keep-alive):允許HTTP設(shè)備在事務(wù)處理結(jié)束之后將TCP連接保持在打開狀態(tài),以便未來的HTTP請求重用現(xiàn)在的連接绵咱;在事務(wù)處理結(jié)束之后仍然保持在打開狀態(tài)的TCP連接叫做持久連接;非持久連接會在事件處理結(jié)束之后關(guān)閉碘饼,持久連接會在不同的事務(wù)之間保持打開狀態(tài);
持久連接的優(yōu)點:降低時延和連接建立的開銷;將連接保持在已經(jīng)調(diào)諧的狀態(tài)艾恼;減少了打開連接的潛在數(shù)量住涉;
持久連接的缺點:如果存在大量空閑的keepalive connections(我們可以稱作僵尸連接或者泄漏連接),其它客戶端們的正常連接速度也會受到影響

1.2 連接池的使用與分析

Call: 對HTTP的Request的封裝
Connection: 對socket連接的包裝钠绍;
**StreamAllocation: **表示Connection被引用的次數(shù)
**ConnectionPool: ** Socket連接池舆声,對連接緩存進行回收與管理
**Deque: ** Deque也就是雙端隊列,雙端隊列同時具有隊列和棧性質(zhì)柳爽;

1.3 Connection自動回收機制

ConnectionPool.java
ConnectionPool內(nèi)部用一個線程池來執(zhí)行連接池的自動回收和管理任務(wù)媳握,

 private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

  /** The maximum number of idle connections for each address. */
  private final int maxIdleConnections;//每個connection的空閑socket連接數(shù)目;
  private final long keepAliveDurationNs;//每個空閑socket連接的keep-alive時長泻拦;

ConnectionPool()
keepAliveDurationNs默認為5分鐘毙芜;maxIdleConnections默認為5個空閑連接忽媒;

  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }
 public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }

當用戶socket連接成功争拐,向連接池中put新的socket時,回收函數(shù)會被主動調(diào)用晦雨,線程池就會執(zhí)行cleanupRunnable;
ConnectionPool.put()

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }

cleanupRunnable.java
通過一個無限循環(huán)來執(zhí)行cleanup()方法來執(zhí)行connection的連接自動回收架曹,并返回下一次回收的時間;

//Socket清理的Runnable闹瞧,每當put操作時绑雄,就會被調(diào)用
//put操作是在網(wǎng)絡(luò)線程
//Socket清理是在 ConnectionPool線程池中調(diào)用
private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      while (true) {
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return;
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
  };

ConnectionPool.Cleanup()

/**
   * Performs maintenance on this pool, evicting the connection that has been idle the longest if
   * either it has exceeded the keep alive limit or the idle connections limit.
   *
   * <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
   * -1 if no further cleanups are required.
   */
//清理超過空閑連接次數(shù)和空閑時間限制的連接,返回下次執(zhí)行清理需要等待時長奥邮;如果不需要清理万牺,返回-1;
  long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    // Find either a connection to evict, or the time that the next eviction is due.
   //遍歷Deque中所有的RealConnection洽腺,標記泄漏的連接
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        // If the connection is in use, keep searching.
       //如果連接在使用脚粟,繼續(xù)搜索需要清理的connection;
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }
       //空閑連接數(shù)目+1;
        idleConnectionCount++;

        // If the connection is ready to be evicted, we're done.
        //連接已經(jīng)空閑的時間
        //找到空閑時間最長的連接蘸朋;
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }
      //如果最長空閑時間的連接所包含的socket空閑連接超過最大空閑連接限制或者超過最長空閑時間核无;那么此連接為待清理的連接
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        // We've found a connection to evict. Remove it from the list, then close it below (outside
        // of the synchronized block).
        //清理連接
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
        //返回剩余可空閑的時間
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // All connections are in use. It'll be at least the keep alive duration 'til we run again.
       //返回 keepAlive時長;
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        //無連接藕坯;
        cleanupRunning = false;
        return -1;
      }
    }

    closeQuietly(longestIdleConnection.socket());

    // Cleanup again immediately.
    return 0;
  }

ConnectionPool.pruneAndGetAllocationCount()
主要用來判斷一個連接是不是活躍的連接

/**
   * Prunes any leaked allocations and then returns the number of remaining live allocations on
   * {@code connection}. Allocations are leaked if the connection is tracking them but the
   * application code has abandoned them. Leak detection is imprecise and relies on garbage
   * collection.
   */
 返回一個connection剩余的活躍的 allocation數(shù)量团南;
  private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    List<Reference<StreamAllocation>> references = connection.allocations;
    for (int i = 0; i < references.size(); ) {
      Reference<StreamAllocation> reference = references.get(i);

      if (reference.get() != null) {
        i++;
        continue;
      }

      // We've discovered a leaked allocation. This is an application bug.
      Internal.logger.warning("A connection to " + connection.route().address().url()
          + " was leaked. Did you forget to close a response body?");
    //移除一個Allocation;
      references.remove(i);
      connection.noNewStreams = true;

      // If this was the last allocation, the connection is eligible for immediate eviction.
   //如果這個 connection所有的allocation都沒有被引用,那么這個連接應(yīng)該馬上被清理炼彪,設(shè)置該connection的已經(jīng)空閑 了keepAliveDurationNs時間吐根;
      if (references.isEmpty()) {
        connection.idleAtNanos = now - keepAliveDurationNs;
        return 0;
      }
    }

    return references.size();
  }
}

1:遍歷RealConnection連接中的StreamAllocationList,它維護著一個弱應(yīng)用列表
2 :查看此StreamAllocation是否為空(它是在線程池的put/remove手動控制的)辐马,如果為空拷橘,說明已經(jīng)沒有代碼引用這個對象了,需要在List中刪除
3 :遍歷結(jié)束,如果List中維護的StreamAllocation刪空了膜楷,就返回0旭咽,表示這個連接已經(jīng)沒有代碼引用了,是泄漏的連接;否則返回非0的值赌厅,表示這個仍然被引用穷绵,是活躍的連接。

二:OkHttp緩存策略

OkHttp用CacheStrategy很好的實現(xiàn)了符合HTTP規(guī)范的HTTP緩存策略特愿;

HTTP緩存流程

CacheStrategy()構(gòu)造
networkRequest:網(wǎng)絡(luò)請求仲墨;
cacheResponse:緩存響應(yīng);

 /** The request to send on the network, or null if this call doesn't use the network. */
  public final Request networkRequest;
   
  /** The cached response to return or validate; or null if this call doesn't use a cache. */
  public final Response cacheResponse;

  private CacheStrategy(Request networkRequest, Response cacheResponse) {
    this.networkRequest = networkRequest;
    this.cacheResponse = cacheResponse;
  }

isCacheable():根據(jù)返回的狀態(tài)碼揍障,主要用來判斷一個Reponse是否可以緩存目养;如果不能緩存,那么Request就需要走網(wǎng)絡(luò)請求毒嫡;不支持緩存部分內(nèi)容癌蚁;如果是302響應(yīng)(暫時性重定向,需要進一步判斷兜畸?)努释;如果有Reponse和Request都有noStore(),那么代表不能緩存咬摇;

/** Returns true if {@code response} can be stored to later serve another request. */
  public static boolean isCacheable(Response response, Request request) {
    // Always go to network for uncacheable response codes (RFC 7231 section 6.1),
    // This implementation doesn't support caching partial content.
    switch (response.code()) {
      case HTTP_OK:
      case HTTP_NOT_AUTHORITATIVE:
      case HTTP_NO_CONTENT:
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_NOT_FOUND:
      case HTTP_BAD_METHOD:
      case HTTP_GONE:
      case HTTP_REQ_TOO_LONG:
      case HTTP_NOT_IMPLEMENTED:
      case StatusLine.HTTP_PERM_REDIRECT:
        // These codes can be cached unless headers forbid it.
        break;

      case HTTP_MOVED_TEMP:
      case StatusLine.HTTP_TEMP_REDIRECT:
        // These codes can only be cached with the right response headers.
        // http://tools.ietf.org/html/rfc7234#section-3
        // s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
        if (response.header("Expires") != null
            || response.cacheControl().maxAgeSeconds() != -1
            || response.cacheControl().isPublic()
            || response.cacheControl().isPrivate()) {
          break;
        }
        // Fall-through.

      default:
        // All other codes cannot be cached.
        return false;
    }
    // A 'no-store' directive on request or response prevents the response from being cached.

      return !response.cacheControl().noStore() && !request.cacheControl().noStore();

Factory.java:是CacheStrage的一個內(nèi)部類伐蒂;

    final long nowMillis;
    final Request request;
    final Response cacheResponse;

    /** The server's time when the cached response was served, if known. **     
   //服務(wù)器創(chuàng)建響應(yīng)的時間
    private Date servedDate;
    private String servedDateString;
   //緩存文檔最后修改時間
    /** The last modified date of the cached response, if known. */
    private Date lastModified;
    private String lastModifiedString;
   
    /**
     * The expiration date of the cached response, if known. If both this field and the max age are
     * set, the max age is preferred.
     */
    //緩存文檔過期時間
    private Date expires;

    /**
     * Extension header set by OkHttp specifying the timestamp when the cached HTTP request was
     * first initiated.
     */
   // 第一次發(fā)送請求的時間戳
    private long sentRequestMillis;
  //第一次接收到緩存響應(yīng)的時間戳
    /**
     * Extension header set by OkHttp specifying the timestamp when the cached HTTP response was
     * first received.
     */
    private long receivedResponseMillis;
   //實體標簽
    /** Etag of the cached response. */
    private String etag;
    //緩存響應(yīng)的年齡
    /** Age of the cached response. */
    private int ageSeconds = -1;

Factory構(gòu)造函數(shù):根據(jù)緩存響應(yīng)來初始化各個參數(shù)值瓦堵;

public Factory(long nowMillis, Request request, Response cacheResponse) {
      //當前時間
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if (cacheResponse != null) {
           //請求發(fā)送時間存和;初始發(fā)送
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        //響應(yīng)產(chǎn)生時間
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            //緩存響應(yīng)的年齡
            ageSeconds = HeaderParser.parseSeconds(value, -1);
          }
        }
      }
    }

Factory.getCandidate():返回一個CacheStragey();

 /** Returns a strategy to use assuming the request can use the network. */
    private CacheStrategy getCandidate() {
      // No cached response.
       //響應(yīng)為空, 走網(wǎng)絡(luò)枣耀;
      if (cacheResponse == null) {
       
        return new CacheStrategy(request, null);
      }

      // Drop the cached response if it's missing a required handshake.
        //是HTTPS請求且TLS握手失敗在扰,走網(wǎng)絡(luò)缕减;
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      // If this response shouldn't have been stored, it should never be used
      // as a response source. This check should be redundant as long as the
      // persistence store is well-behaved and the rules are constant.
        //如果不能緩存,走網(wǎng)絡(luò)健田;
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
       //請求不允許緩存烛卧,或者是條件請求,走網(wǎng)絡(luò)
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }
      //初始化緩存響應(yīng)的年齡和緩存新鮮時間
      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();
      //如果
      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }
      //如果
      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }

      long maxStaleMillis = 0;
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }

      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
          //緩存不新鮮妓局;添加相關(guān)響應(yīng)首部总放;
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
          //緩存過期;添加相關(guān)響應(yīng)首部好爬;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());
      }
     //分別構(gòu)造If-None-Match局雄;If-Modified-Since請求首部;
      Request.Builder conditionalRequestBuilder = request.newBuilder();

      if (etag != null) {
        conditionalRequestBuilder.header("If-None-Match", etag);
      } else if (lastModified != null) {
        conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);
      } else if (servedDate != null) {
        conditionalRequestBuilder.header("If-Modified-Since", servedDateString);
      }

      Request conditionalRequest = conditionalRequestBuilder.build();
     //如果允許條件請求存炮,則進行條件請求炬搭,驗證新鮮度蜈漓;不允許就發(fā)起新的網(wǎng)絡(luò)請求;
      return hasConditions(conditionalRequest)
          ? new CacheStrategy(conditionalRequest, cacheResponse)
          : new CacheStrategy(conditionalRequest, null);
    }

Factory.computeFreshnessLifetime():計算緩存維持在新鮮(不過期)狀態(tài)還有多長

/**  Returns the number of milliseconds that the response was 
fresh for, starting from the served date. **/
private long computeFreshnessLifetime() {
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.maxAgeSeconds() != -1) {
         //返回reponse的 max-age
        return SECONDS.toMillis(responseCaching.maxAgeSeconds());
      } else if (expires != null) {
       //返回expires-servedDate;取差值宫盔;消除服務(wù)器時鐘偏差融虽;
        long servedMillis = servedDate != null
            ? servedDate.getTime()
            : receivedResponseMillis;
        long delta = expires.getTime() - servedMillis;
        return delta > 0 ? delta : 0;
      } else if (lastModified != null
          && cacheResponse.request().url().query() == null) {
        // As recommended by the HTTP RFC and implemented in Firefox, the
        // max age of a document should be defaulted to 10% of the
        // document's age at the time it was served. Default expiration
        // dates aren't used for URIs containing a query.
        long servedMillis = servedDate != null
            ? servedDate.getTime()
            : sentRequestMillis;
        //返回expires-lastModified;作為緩存能維持在新鮮狀態(tài)的時長灼芭;取差值有额;消除服務(wù)器時鐘偏差;
        long delta = servedMillis - lastModified.getTime();
        return delta > 0 ? (delta / 10) : 0;
      }
      return 0;
    }

Factory:cacheResponseAge():返回 response的年齡彼绷;

/**  Returns the current age of the response, in milliseconds.
 The calculation is specified by RFC 2616, 13.2.3 Age Calculations. **/
 private long cacheResponseAge() {
      //客戶端初始接收某響應(yīng)時間-服務(wù)器響應(yīng)產(chǎn)生時間巍佑;
      long apparentReceivedAge = servedDate != null
          ? Math.max(0, receivedResponseMillis - servedDate.getTime())
          : 0;
      long receivedAge = ageSeconds != -1
          ? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
          : apparentReceivedAge;
      //客戶端初始接收某響應(yīng)時間-客戶端初始發(fā)送某請求時間
      long responseDuration = receivedResponseMillis - sentRequestMillis;
    //當前時間-客戶端初始接收某響應(yīng)時間
      long residentDuration = nowMillis - receivedResponseMillis;
     當前時間-第一次發(fā)送請求時間+
      return receivedAge + responseDuration + residentDuration;
    }

okhttp緩存實現(xiàn)

LinkedHashMap;
文件
OkHttp通過對文件進行了多次封裝,實現(xiàn)了非常簡單的I/O操作
OkHttp通過對請求url進行md5實現(xiàn)了與文件的映射寄悯,實現(xiàn)寫入萤衰,刪除等操作
OkHttp內(nèi)部維護著清理線程池,實現(xiàn)對緩存文件的自動清理

okhttp任務(wù)調(diào)度

Dispacher

okhttp 任務(wù)調(diào)度
   // 最大并發(fā)請求數(shù)為64
   private int maxRequests = 64;
  // 每個主機最大請求數(shù)為5
   private int maxRequestsPerHost = 5;
   //線程池
  /** Executes calls. Created lazily. */
  private ExecutorService executorService;
 //緩存隊列
  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  
  //正在運行的任務(wù);包括已經(jīng)取消但是還沒結(jié)束的任務(wù)猜旬;
  /** 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<>();

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

當執(zhí)行任務(wù)脆栋,將任務(wù)加入任務(wù)隊列

 OkHttpClient client = new OkHttpClient.Builder().build();
 Request request = new Request.Builder()
    .url("http://qq.com").get().build();
  client.newCall(request).enqueue(new Callback() {
  @Override public void onFailure(Call call, IOException e) {

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

  }
});

enqueue: 添加任務(wù)實際上是入隊

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        //如果滿足最大并發(fā)請求數(shù)為64, 每個主機最大請求數(shù)為5昔馋;
      //添加正在運行的請求
    runningAsyncCalls.add(call);
       //線程池執(zhí)行請求
    executorService().execute(call);
  } else {
      //添加到緩存隊列
    readyAsyncCalls.add(call);
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末筹吐,一起剝皮案震驚了整個濱河市糖耸,隨后出現(xiàn)的幾起案子秘遏,更是在濱河造成了極大的恐慌,老刑警劉巖嘉竟,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邦危,死亡現(xiàn)場離奇詭異,居然都是意外死亡舍扰,警方通過查閱死者的電腦和手機倦蚪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來边苹,“玉大人陵且,你說我怎么就攤上這事「鍪” “怎么了慕购?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茬底。 經(jīng)常有香客問我沪悲,道長,這世上最難降的妖魔是什么阱表? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任殿如,我火速辦了婚禮贡珊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涉馁。我一直安慰自己门岔,他們只是感情好,可當我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布烤送。 她就那樣靜靜地躺著固歪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胯努。 梳的紋絲不亂的頭發(fā)上牢裳,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天,我揣著相機與錄音叶沛,去河邊找鬼蒲讯。 笑死,一個胖子當著我的面吹牛灰署,可吹牛的內(nèi)容都是我干的判帮。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼溉箕,長吁一口氣:“原來是場噩夢啊……” “哼晦墙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肴茄,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤晌畅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后寡痰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抗楔,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年拦坠,在試婚紗的時候發(fā)現(xiàn)自己被綠了连躏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡贞滨,死狀恐怖入热,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晓铆,我是刑警寧澤勺良,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站尤蒿,受9級特大地震影響郑气,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腰池,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一尾组、第九天 我趴在偏房一處隱蔽的房頂上張望忙芒。 院中可真熱鬧,春花似錦讳侨、人聲如沸呵萨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潮峦。三九已至,卻和暖如春勇婴,著一層夾襖步出監(jiān)牢的瞬間忱嘹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工耕渴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拘悦,地道東北人。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓橱脸,卻偏偏與公主長得像础米,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子添诉,可洞房花燭夜當晚...
    茶點故事閱讀 43,666評論 2 350

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