Glide 源碼分析解讀-基于最新版Glide 4.9.0

項目編譯


項目基于最新版 Glide 4.9.0 源碼分析,通過如下方式獲取并編譯代碼:

git clone https://github.com/bumptech/glide.git
cd glide
./gradlew jar

代碼量(使用 cloc 統計):

代碼量

這么多的代碼,直接看肯定讓人頭疼,理不清頭緒,但好在 Glide 分包結構明確,我們可以先根據不同的模塊來逐個擊破。

我在分析 Glide 源碼前將 Glide 的項目 clone 到了本地,閱讀時添加了很多注釋以及自己的理解等等,現在已經推到了 Github 上,有興趣的同學可以看看:
https://github.com/0xZhangKe/Glide-note

總覽


有一點需要說明的是,Glide 源碼很復雜, 涉及到的東西也很多,這里不能面面俱到,只是把我認為重要的東西介紹了一下,可能還有一些疏漏。

首先我將 Glide 分成了幾個模塊,讓大家有個整體的印象,自頂向下的分析源碼,從而實現降維打擊。

按照邏輯功能劃分,可以把 Glide 框架大概的分成如下幾個部分:


模塊劃分

Glide 大體上可以分為如上幾個模塊。

下面通過一個常用案例來分析整個流程。
一般來說,我們使用如下代碼加載一張網絡圖片:

Glide.with(this)
        .load(url)
        .into(imgView);

假設這是我們的 APP 第一次使用 Glide 加載一張圖片,那么流程如下:


加載流程

上面的流程是簡化版,省去了一部分東西,可以通過這張圖更直觀的了解到 Glide 的加載流程以及機制。
看到上面這個流程圖大家先別慌,下面我來慢慢介紹。

模塊介紹


根據模塊學習事半功倍,先看看 Glide 的分包結構:

包結構

我在分析 Glide 源碼前將 Glide 的項目 clone 到了本地,閱讀時添加了很多注釋以及自己的理解等等,現在已經推到了 Github 上,有興趣的同學可以看看:
https://github.com/0xZhangKe/Glide-note

先看看最外層的幾個類。

Glide


Glide 是單例類,通過 Glide#get(Context) 方法可以獲取到實例。

Glide 類算是個全局的配置類,Encoder、Decoder、ModelLoader、Pool 等等都在這里設置,此外還提供了創建 RequestManager 的接口(Glide#with() 方法)。

使用 Glide 時會最先調用 Glide#with() 方法創建 RequestManager,Glide 中的 with() 方法有五個重載:

RequestManager with(Context context)
RequestManager with(android.app.Activity)
RequestManager with(android.app.Fragment)
RequestManager with(android.support.v4.app.Fragment)
RequestManager with(android.support.v4.app.FragmentActivity)

Glide#with() 方法會將 RequestManager 的創建委托給 RequestManagerRetriever,RequestManagerRetriever 為單例類,調用 get(Context) 創建 RequestManager。

GlideBuilder


GlideBuilder 是用來創建 Glide 實例的類,其中包含了很多個 get/set 方法,例如設置 BitmapPool、MemoryCache、ArrayPool 等等,最終通過這些設置調用 build 方法構建 Glide,可以截取 build 方法中的一段代碼來看一下:

if (bitmapPool == null) {
    //創建 Bitmap 池
    int size = memorySizeCalculator.getBitmapPoolSize();
    if (size > 0) {
        bitmapPool = new LruBitmapPool(size);
    } else {
        bitmapPool = new BitmapPoolAdapter();
    }
}
?
//創建數組池
if (arrayPool == null) {
    arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}
?
//創建內存緩存
if (memoryCache == null) {
    memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
?
//創建磁盤緩存
if (diskCacheFactory == null) {
    diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}

上面截取的幾行代碼很具有代表性,這些數組池、緩存實現等等最終都會當做 Glide 構造器的參數創建 Glide 實例。

RequestManagerRetriever


上面說的 5 個重載的 Glide#with() 方法對應 RequestManagerRetriever 中的 5 個重載的 get() 方法。
由于這個比較重要,而且跟我們使用息息相關,所以仔細的說一下~

創建 RequestManager 邏輯如下:

  1. 如果 with 方法的參數為 Activity 或者 Fragment ,則最終調用 RequestManagerRetriever 中的 fragmentGet(Context, android.app.FragmentManager) 方法創建 RequestManager;
  2. 如果 with 方法的參數為 android.support.v4.app.Fragment 或者android.support.v4.app.FragmentActivity,則最終調用 supportFragmentGet(Context, android.support.v4.app.FragmentManager) 方法創建 RequestManager;
  3. 如果 with 方法的參數為 Context,則會判斷其來源是否屬于 FragmentActivity 及 Activity,是則按照上面的邏輯進行處理,否則最終調用 getApplicationManager(Context) 方法創建 RequestManager。

上面說的情況有個條件都是在主線程調用 Glide#with() 方法, 如果子線程調用 Glide#with() 或者系統版本小于 17,則最終會調用 getApplicationManager(Context) 方法創建 RequestManager 。

也就是說,無論使用什么參數,最終都會進入如下三個方法創建 RequestManager:

RequestManager fragmentGet(Context context, android.app.FragmentManager fm);
RequestManager supportFragmentGet(Context context, android.support.v4.app.FragmentManager fm);
RequestManager getApplicationManager(Context context);

可以看到這三個方法作用都是用來創建 RequestManager,前兩個方法主要是用來兼容 support 包中的 FragmentActivity、Fragment。

至于為什么需要傳入一個 FragmentManager 參數留在后面說。

此外還有一種情況,即在子線程調用 Glide#with() 方法或傳入 Context 對象為 ApplicationContext,此時會創建一個全局唯一的 RequestManager,生命周期與 APP 周期保持一致。

根據上述規則可以得出以下幾個結論:

  1. 同一個 Activity 對應一個 FragmentManager,一個 FragmentManager 對應一個 RequestManagerFragment,一個 RequestManagerFragment 對應一個 RequestManager,所以一個 Activity 對應 一個 RequestManager
  2. 同一個 Fragment 同樣可得出上述結論;
  3. 但如果 Fragment 屬于 Activity,或者 Fragment 屬于 Fragment,在 Activity、Framgnent 中分別創建 Glide 請求是并不會只創建一個 RequestManager;
  4. 子線程發起 Glide 請求或傳入對象為 ApplicationContext,則使用全局單例的 RequestManager。

RequestManager


RequestManager 主要由兩個作用:

  1. 創建 RequestBuilder ;
  2. 通過生命周期管理請求的啟動結束等。

我們都知道使用 Glide 加載圖片時,如果當前頁面被銷毀或者不可見時會停止加載圖片,但我們使用 Glide 加載圖片時并沒有顯示的去設置 Glide 與當前頁面的生命周期關聯起來,只是傳了個 Context 對象,那么 Glide 是如何通過一個上下文對象就能獲取到頁面生命周期的呢?

通過上面 RequestManagerRetriever 章節的介紹我們知道創建 RequestManager 時需要一個 FragmentManager 參數(全局 RequestManager 除外),那么再創建 RequestManager 時會先創建一個不可見的 Fragment ,通過 FM 加入到當前頁面,用這個不可見的 Fragment 即可檢測頁面的生命周期。代碼中保證了每個 Activity/Fragment 中只包含一個 RequestManagerFragment 與 一個 RequestManager。

創建 RequestBuilder 的 load 方法有很多:

RequestBuilder<Drawable> load(@Nullable Bitmap bitmap);
RequestBuilder<Drawable> load(@Nullable Drawable drawable);
RequestBuilder<Drawable> load(@Nullable String string);
RequestBuilder<Drawable> load(@Nullable Uri uri);
RequestBuilder<Drawable> load(@Nullable File file);
RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId);
RequestBuilder<Drawable> load(@Nullable URL url);
RequestBuilder<Drawable> load(@Nullable byte[] model);
RequestBuilder<Drawable> load(@Nullable Object model);

看看有這么多重載方法,沒一個都代表不同的加載源。
除此之外還有兩個特殊的方法:

RequestBuilder<File> downloadOnly();
RequestBuilder<File> download(@Nullable Object model);

這兩個聽名字就知道是用來下載圖片的。

RequestBuilder


RequestBuilder 用來構建請求,例如設置 RequestOption、縮略圖、加載失敗占位圖等等。
上面說到的 RequestManager 中諸多的 load 重載方法,同樣也對應 RequestBuilder 中的重載 load 方法,一般來說 load 方法之后就是調用 into 方法設置 ImageView 或者 Target,into 方法中最后會創建 Request,并啟動,這個后面會詳細介紹。

Request


顧名思義, request 包下面的是封裝的請求,里面有一個 Request 接口,估計所有的請求都是基于這個接口的,看一下:


Request

接口定義了對請求的開始、結束、狀態獲取、回收等操作,所以請求中不僅包含基本的信息,還負責管理請求。
Request 主要的實現類有三個:

  1. SingleRequest
  2. ThumbnailRequestCoordinator
  3. ErrorRequestCoordinator

一個個看。

SingleRequest


這個類負責執行請求并將結果反映到 Target 上。
當我們使用 Glide 加載圖片時,會先根據 Target 類型創建不同的 Target,然后 RequestBuilder 將這個 target 當做參數創建 Request 對象,Request 與 Target 就是這樣關聯起來的。

這里就會先創建一個包含 Target 的 SingleRequest 對象。考慮到性能問題,可能會連續創建很多個 SingleRequest 對象,所以使用了對象池來做緩存。
再來說說 SingleRequest 的請求發起流程。

我們經常在 Activity#onCreate 方法中直接使用 Glide 方法,但此時的圖片大小還未確定,所以調用 Request#begin 時并不會直接發起請求,而是等待 ImageView 初始化完成,對于 ViewTarget 以及其子類來說,會注冊View 的 OnPreDrawListener 事件,等待 View 初始化完成后就調用 SingleRequest#onSizeReady 方法,這個方法里就會開始加載圖片了。

onSizeReady 方法并不會去直接加載圖片,而是調用了 Engine#load 方法加載,這個方法差不多有二十個參數,所以 onSizeReady 方法算是用來構建參數列表并且調用 Engine#load 方法的。

clear 方法用于停止并清除請求,主要就是從 Engine 中移除掉這個任務以及回調接口。
另外,SingleRequest 實現了 ResourceCallback 接口,這個接口就連個方法:

void onResourceReady(Resource<?> resource, DataSource dataSource);
void onLoadFailed(GlideException e);

即資源加載完成和加載失敗的兩個回調方法,剛剛說的 Engine#load 方法中有差不多二十個參數,其中有一個參數就是這個接口。那再來說這兩個方法在 SingleRequest 中的實現。
其實很簡單,重點就是調用 Target#onResourceReady 方法以及構建圖片加載完成的動畫,另外還要通知 ThumbnailRequestCoordinator 圖片加載完成。
onLoadFailed 方法流程大體上也類似 onResourceReady。
那 SingleRequest 就差不多這樣了。

ThumbnailRequestCoordinator


這個類是用來協調兩個請求,因為有的請求需要同時加載原圖和縮略圖,比如啟動這兩個請求、原圖加載完成后縮略圖其實就不需要加載了等等,這些控制都由這個類來操作。
RequestBuilder 中會將縮略圖和原圖的兩個 SingleRequest 都交給它,后面再對其操作時都由這個類來同一控制。
所以這個類其實沒什么太多的功能,就是對兩個對象的一個統一個管理協調包裝。

ErrorRequestCoordinator


RequestBuilder 的父類 BaseRequestOptions 中有幾個 error 的重載方法:

T error(@Nullable Drawable drawable);
T error(@DrawableRes int resourceId);

一般地,我們會使用這個方法設置一個加載失敗時的填充圖,大部分情況下都是一個通過 resource 資源文件中獲取到的圖片 ID 或者 Drawable。
但 RequestBuilder 中還提供了另一個 error 方法:

RequestBuilder<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> errorBuilder);

考慮這樣的一個場景,當我們加載失敗時我可能希望繼續去通過網絡或者別的什么加載另一張圖片,例如:

Glide.with(context)
    .load((Object) null)
    .error(
        Glide.with(context)
            .load(errorModel)
            .listener(requestListener))
    .submit();

當我們這樣使用 error 時最終就會創建一個 ErrorRequestCoordinator 對象,這個類的功能類似 ThumbnailRequestCoordinator,其中也沒多少代碼,主要用來協調 ThumbnailRequestCoordinator 以及 error 中的 Request。

通過上面的介紹就已經對 Request 的作用以及子類有一定的了解了,上面多次提到過 Target 是另一個很重要的概念,下面接著看一下這個類。

Target


Target 代表一個可被 Glide 加載并且具有生命周期的資源
當我們調用 RequestBuilder#into 方法時會根據傳入參數創建對應類型的 Target 實現類。

那么 Target 在 Glide 的整個加載流程中到底扮演者什么樣的角色呢?Target 的中文意思為:目標,實際上就是指加載完成后的圖片應該放在哪, Target 默認提供了很多很有用的實現類,當然我們也可以自定義 Target。

Glide 默認提供了用于放在 ImageView 上的 ImageViewTarget(以及其各種子類)、放在 AppWidget 上的 AppWidgetTarget、用于同步加載圖片的 FutureTarget(只有一個實現類:RequestFutureTarget)等等,下面分別來看一下。

CustomViewTarget


這個是抽象類,負責加載 Bitmap、Drawable 并且放到 View 上。

上文提到過,如果在 View 還未初始化完成時就調用了 Glide 加載圖片會等待加載完成再去執行 onSizeReady 方法,那如何監聽 View 初始化完成呢?
CustomViewTarget 就針對這個問題給出了解決方案,其中會調用 View#addOnAttachStateChangeListener 方法添加一個監聽器,這個監聽器可以監聽到 View 被添加到 Widow 以及移除 Window 時的事件,從而更好的管理 Request 生命周期。

另外,構建好的 Request 會通過 View#setTag 方法存入 View 中,后面再通過 View#getTag 方法獲取。

但這個抽象類并沒有實現類,也沒有被使用過,View 相關的 Target 都是繼承 ViewTarget 抽象基類,但這個類已經被標記為過期類了,推薦將 ViewTarget 替換成 CustomViewTarget 使用。

ViewTarget


這個類又繼承了抽象類 BaseTarget,這個基類里只是實現了 Target 接口的 setRequest 以及 getRequest 方法。
ViewTarget 基本上類似 CustomViewTarget ,只是具體的實現上有點不同。

ImageViewTarget


聽名字就知道,這是加載到 ImageView 上的 Target,繼承了 ViewTarget,同樣也是個抽象類

構造器中限定了必須傳入 ImageView 或者其子類,圖片數據加載完成后會回調其中的 onResourceReady 方法,第一步是將圖片設置給 ImageView,第二部是判斷是否需要使用動畫,需要的話就執行動畫。

ImageViewTarget 的實現類比較多,總共有 5 個,但內容都很簡單,主要用于區分加載的資源時 Bitmap 類型還是 Drawable 類型,這個在構建請求時確定,默認的加載請求最終都是 Drawable 類型,但如果構建請求時調用了 asBitmap 方法那就資源就會被轉成 Bitmap 類型,另外一個就是資源使用縮略圖展示。

RequestFutureTarget


這是用來同步加載圖片的 Target,調用 RequestBuilder#submit 將會返回一個 FutureTarget,調用 get 方法即可獲取到加載的資源對象。

AppWidgetTarget


用于將下載的 Bitmap 設置到 RemoteView 上。

NotificationTarget


與 AppWidgetTarget 類似,不同的是這是用來將 Bitmap 設置到 Notification 中的 RemoteView 上。

module


module 包下面的 GlideModel 比較重要,需要詳細說一下。

這是用來延遲設置 Glide 相關參數的,我們可以通過這個接口使 Glide 在初始化時應用我們的設置,因為 Glide 是單例類,通過這個設置可以保證在 Glide 單例類初始時,所有請求發起之前應用到 Glide。

GlideModel 是個接口,所以代碼很簡單:

@Deprecated
public interface GlideModule extends RegistersComponents, AppliesOptions { }

可以看到該接口被標識已過期,Glide 推薦使用 AppGlideModule 替代,不用管他。

GlideModel 接口本身沒有代碼內容,但其繼承了 RegistersComponents 與 AppliesOptions 接口,先分別看一下這兩個接口。

RegistersComponents


這是用來注冊 Glide 中一些組件的,這個接口只有一個方法:

void registerComponents(@NonNull Context context, @NonNull Glide glide,
      @NonNull Registry registry);

這個方法中提供了一個 Registry 對象,這是用來管理注冊 ModelLoader、Encoder、Decoder 等等,具體可以看看 Registry 提供的公開方法。

例如我們可以在這里注冊自己的 ModelLoader,比如我們的網絡請求使用的 OkHttp,Glide 默認使用的是HttpURLConnection,我們想改成 OkHttp 就可以在這里設置,具體的使用方式點此查看使用案例。

AppliesOptions


這是用來管理一些 Glide 的參數設置項,同樣只有一個方法。

void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder);

這個方法提供了一個 GlideBuilder 參數,這是用來構建 Glide 的,我們可以使用 GlideBuilder 對象提供的公開方法做一些設置,例如設置線程池、設置 BitmapPool/ArrayPoll 等等。

那么說完這兩個接口,在回過頭來看看 GlideModel ,通過上面的描述已經明白 GlideModel 中兩個方法的作用了,再來看看如何使用。

Glide 在實例化時會解析 manifest 文件并從中獲取 value 為 GlideModule 的 meta-data 配置信息,我們定義好自己的 GlideModule 之后需要在 manifest 文件中進行配置,配置方式如下:

<meta-data
        android:name="com.zhangke.glide.samples.OkHttpGlideModule"
        android:value="GlideModule"/>

其中 OkHttpGlideModule 必須實現 GlideModel 接口。
具體的配置方式點此查看

此外,Glide 默認提供了很多 ModelLoader,基本上可以滿足所有場景的使用。
ModelLoader 的具體作用與機制后面會詳細介紹。

load


load

load 包下面是加載資源的核心,里面的東西很多,也很復雜,所以我先把其中兩個比較重要的接口介紹完了在介紹別的。

ModelLoader


類路徑:

com.bumptech.glide.load.model.ModelLoader

工廠接口,用于將任意復雜的數據模型轉換為可由 DataFetcher 用于獲取模型所代表的資源的數據的具體數據類型。叫他加載器比較合適,用來加載資源的。

除此之外,還允許將圖片按照 ImageView 大小按需加載。防止浪費內存。

Glide 初始化時會注冊很多個 ModelLoader ,除了Glide 默認提供的之外還會注冊用戶在 manifest 中配置的 ModelLoader,也就是上面 GlideModel 章節介紹的內容。

ModelLoader 中有兩個方法以及一個內部類:LoadData,下來看看這兩個方法:

@Nullable
LoadData<Data> buildLoadData(@NonNull Model model, int width, int height,
                                 @NonNull Options options);
boolean handles(@NonNull Model model);

buildLoadData 方法除了包含 Model 之外還有寬高以及 Option,所以光看參數列表應該能猜到,加載圖片時可以根據需要的寬高以及其他設置做到按需加載。
返回的是 LoadData 實例,這個類待會再說。所以這個方法的意義就是通過參數構建一個 LoadData 實例。

handles 方法比較簡單,就是用來判斷給定模型是不是此加載器可能加載的已識別類型。

至于內部類 LoadData 呢,主要作用就是裝了三個東西:

  1. 用于識別資源唯一性的 Key;
  2. 緩存相關的備用 Key 列表
  3. DataFetcher

其中 DataFetcher最重要,為什么說它是最重要的呢,因為加載資源的根源就在這里(找了半天終于找到了),例如發起網絡請求等等,都在這個里面。
那既然說到了 DataFetcher 就在說說它。

DataFetcher


類路徑:

com.bumptech.glide.load.data.DataFetcher

DataFetcher 也是個接口,其中最重要的一個方法就是 loadData,聽名字就很重要是吧:加載數據

內部實現就是通過 HttpUrlConnect 發起網絡請求,或者打開一個文件,或者使用 AssetManager 打開一個資源等等。。。

加載完成后通過 DataFetcher$DataCallback 接口回調。

DataCallback 中包含兩個方法:

void onDataReady(@Nullable T data);
void onLoadFailed(@NonNull Exception e);

分別代表數據加載成功或者加載失敗回調。

Encoder


Encoder 是個接口,在 Glide 中也是個很重要的概念,用來將給定的數據寫入持久性存儲介質中(文件)。

其中只有一個方法:

public interface Encoder<T> {
  /**
   * Writes the given data to the given output stream and returns True if the write completed
   * successfully and should be committed.
   *
   * @param data The data to write.
   * @param file The File to write the data to.
   * @param options The put of options to apply when encoding.
   */
  boolean encode(@NonNull T data, 
                 @NonNull File file, 
                 @NonNull Options options);
}

比較簡單,注釋寫的很清楚了,就是把 data 存入文件中。

數據加載完成之后會先使用 Encoder 將數據存入本地磁盤緩存文件中。
同樣,Encoder 對應的實現類都是在 Glide 初始化時注冊進去的。

ResourceDecoder


與 Encoder 對應,數據解碼器,用來將原始數據解碼成相應的數據類型,針對不同的請求實現類都不同,例如通過網絡請求最終獲取到的是一個 InputStream,經過 ByteBufferBitmapDecoder 解碼后再生成一個 Bitmap。

需要指出的是,這里解碼時會根絕 option 以及圖片大小(如果有的話)按需加載 Bitmap,防止內存的浪費。

與 Encoder 一樣,Glide 初始化時會注冊很多個類型的 ResourceDecoder 實現類,圖片數據獲取到之后會根據不同的類型使用對應的解碼器對其解碼。

Engine


上面的 Request 中也講到了 Engine 這個類,可理解為執行引擎,算是整個 Glide 的核心發動機。

Engine 負責管理請求以及活動資源、緩存等。主要關注 load 方法,這個方法主要做了如下幾件事:

  1. 通過請求構建 Key;
  2. 從活動資源中獲取資源(詳見緩存章節),獲取到則返回;
  3. 從緩存中獲取資源,獲取到則直接返回;
  4. 判斷當前請求是否正在執行,是則直接返回;
  5. 構建 EngineJob 與 DecodeJob 并執行。

關于緩存相關的都在緩存章節。下面說說 EngineJob 與 DecodeJob。

EngineJob


這個主要用來執行 DecodeJob 以及管理加載完成的回調,各種監聽器,沒有太多其他的東西。

DecodeJob


負責從緩存或數據源中加載原始數據并通過解碼器轉換為相應的資源類型(Resource)。DecodeJob 實現了 Runnable 接口,由 EngineJob 將其運行在指定線程池中。

首次加載一張圖片資源時,資源加載完成后會先存入到本地緩存文件中,然后再從文件中獲取。

上面已經說過,圖片的加載最終是通過 DataFetcher 來實現,但是此處并沒有直接這么調用,考慮到緩存文件,這里面使用的是 DataFetcherGenerator,其有三個實現類,對應不同的加載方式,這里就不多做介紹了,只需要知道它會根據資源類型去 Glide 中獲取已注冊的 DataFetcher ,然后通過 DataFetcher#loadData 方法獲取原始數據,獲取完成后使用 Encoder 將數據存入磁盤緩存文件中,同時使用對應的解碼器將原始數據轉換為相應的資源文件,這樣整個流程就差不多結束了。

結語


至此,Glide 源碼分析就結束了,對于像 Glide 這種優秀的源碼看一遍能學到很多東西,受益匪淺,而且源碼是越看越熟,看得多了,再去看別的框架就很簡單,俗話說,閱讀一個優秀的框架源碼,就好比再跟一個偉大的人談話。后面我還會繼續出一篇關于閱讀源碼技巧的博客。
另外,緩存模塊比較重要復雜,所以又單獨寫了篇文章,可以看我下一篇博客

如果覺得還不錯的話,歡迎關注我的公眾號,我會不定期發一些干貨~

公眾號

也可以加我微信:

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