Android GUI SurfaceFlinger

本文涉及的源代碼基于 Android-7.1.1r。

一、Android GUI 框架

SurfaceFlinger 是 Android GUI 的核心,但是從 OpenGL_ES 的角度來看,它也只是個“應用程序”。Android 的顯示系統大致框架圖下圖所示:

GUI_STRUCT.png

下面就“由下向上”來逐一分析該框架。

(1) 顯示驅動

Linux 內核提供了統一的 framebuffer 顯示驅動。設備節點是 /dev/graphics/fb* 或 /dev/fb*,而 fb0 表示第一個 Monitor,當前系統實現中只用到了一個顯示屏。

(2) HAL 層

Android 的 HAL 層提供了 Gralloc,包括 fb 和 gralloc 兩個設備。

  • fb 負責打開內核中的 framebuffer,初始化配置,并提供 post,setSwapIntervel 等操作接口;
  • gralloc 用于管理幀緩沖區的分配和釋放。

HAL 層還包含另一個重要模塊 —— “Composer”,它為廠商自定制“UI合成”提供了接口。Composer 的直接使用者是 SurfaceFlinger 中的 HWComposer,HWComposer 除了負責管理 Composer 的 HAL 模塊外,還負責 VSync 信號(軟件、硬件)的產生和控制。

(3) FramebufferNativeWindow

FramebufferNativeWindow 是負責 OpenGL ES(通用函數庫) 在 Android 平臺上本地化的中介之一,它將 Android 的窗口系統與 OpenGL ES 產生聯系,為 OpenGL ES 配置本地窗口的是 EGL。

(4) EGL

EGL 負責為 OpenGL ES 配合本地窗口。OpenGL ES 更多的只是一個接口協議,具體實現即可以采用軟件,也可以采用硬件實現,而 EGL 會去讀取 egl.cfg,并根據用戶的設置來動態加載 libagl(軟件實現)或是 libhgl(硬件實現)。

(5) DisplayDevice

SurfaceFlinger 中持有一個成員數組 mDisplays 用來描述系統中支持的各種"顯示設備",具體有那些 Display 是由 SurfaceFlinger 在 readyToRun 中進行判斷并賦值的。DisplayDevice 在初始化的時候會調用 eglGetDisplay,eglCreateWindowSurface 等接口,并利用 EGL 來完成 OpenGL ES 環境的搭建。

(6) OpenGL ES 模塊

很多模塊都可以調用 OpenGL ES 提供的 API,其中就包括 SurfaceFLinger 和 DisplayerDevice。

與 OpenGL ES 的相關的模塊分為以下幾類:

  • 配置類:幫助 OpenGL ES 完成配置,包括 EGL,DisplayHardware 都屬這一類。
  • 依賴類:OpenGL ES 要運行的起來所依賴的“本地化”的東西,在上圖中指的就是 FramebufferNativeWindow。
  • 使用類:使用 OpenGL ES 的用戶,如 DisplayDevice 即扮演了使用者,又扮演了構建 OpenGL ES 的配置者。

二、HAL

HAL 是子系統(顯示系統、音頻系統)與 Linux 內核驅動之間的統一接口。

HAL 需要解決以下問題:

  • 硬件的抽象;
  • 接口的穩定;
  • 靈活的使用。

(1) 硬件抽象

HAL 多數使用 C 語言編寫,而 C 語言不是面向對象的,所以具體的“繼承”關系就沒有像 C++ 或是 Java 這類的面向對象的語言表現的那么直接。在 C 語言中要實現類似的“繼承”關系,只需要讓子類的第一個成員變量是父類結構即可。以 Gralloc 為例,它就是 hw_module_t 的子類,代碼如下:

typedef struct gralloc_module_t {
    struct hw_module_t common; // 父類

    // 結構體中定義函數指針(結構體中不能定義函數)
    int (*registerBuffer)(struct gralloc_module_t const* module,
            buffer_handle_t handle);

    int (*unregisterBuffer)(struct gralloc_module_t const* module,
            buffer_handle_t handle);

    int (*lock)(struct gralloc_module_t const* module,
            buffer_handle_t handle, int usage,
            int l, int t, int w, int h,
            void** vaddr);

    int (*unlock)(struct gralloc_module_t const* module,
            buffer_handle_t handle);

    int (*perform)(struct gralloc_module_t const* module,
            int operation, ... );

    int (*lock_ycbcr)(struct gralloc_module_t const* module,
            buffer_handle_t handle, int usage,
            int l, int t, int w, int h,
            struct android_ycbcr *ycbcr);

    int (*lockAsync)(struct gralloc_module_t const* module,
            buffer_handle_t handle, int usage,
            int l, int t, int w, int h,
            void** vaddr, int fenceFd);

    int (*unlockAsync)(struct gralloc_module_t const* module,
            buffer_handle_t handle, int* fenceFd);

    int (*lockAsync_ycbcr)(struct gralloc_module_t const* module,
            buffer_handle_t handle, int usage,
            int l, int t, int w, int h,
            struct android_ycbcr *ycbcr, int fenceFd);

    void* reserved_proc[3];
} gralloc_module_t;

(2) 接口的穩定

HAL 中的接口必須是穩定不變的,Android 系統中已經預定好了這些接口,如下圖所示(源碼位置:hardware/libhardware/include/hardware):

HAL接口.png

(3) 靈活的使用

硬件生產商只要按照 Android 提供的硬件要求來實現 HAL 接口,手機開發商只需要移植硬件生產商提供的 HAL 庫就可以了。

三、Android 終端顯示設備 ———— Gralloc 和 Framebuffer

Framebuffer 是 Linux 內核提供的圖形硬件的抽象描述,它占用了系統內存的一部分,是一塊包含屏幕顯示信息的緩沖區。在 Android 中,Framebuffer 提供的設備文件節點是 /dev/graphics/fb*。這里以 sony Xperia 為例,它的 fb 節點如下圖所示:

Sony_Xperia_fb.png

Android 的子系統不會直接使用內核驅動,而是由 HAL 層來間接引用底層框架。顯示系統也是一樣,它通過 HAL 層來做操作幀緩沖區,而完成這一中介任務的就是 Gralloc

3.1、Gralloc 模塊的加載

Gralloc 對應的模塊是在 FramebufferNativeWindow(GUI 結構圖中位于 Grlloc 上方) 的構造函數中加載的(在 Androdi-7.1.1 中是通過 Gralloc1.cpp 進行加載的),即:

int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module); //

hw_get_module 是上層使用(FramebufferNativeWindow)者加載 HAL 庫的入口。lib 庫有以下幾種形式;

gralloc.[ro.hardware].so
gralloc.[ro.product.board].so
gralloc.[ro.board.platform].so
gralloc.[ro.arch].so

當以上文件都不存在時,就使用默認的:

gralloc.default.so

源碼位置:hardware/libhardware/modules/gralloc/,由 gralloc.cpp,framebuffer.cpp 和 mapper.cpp 三個主要文件編譯而成。

3.2、Gralloc 提供的接口

Gralloc 是 hw_module_t 的子類,hw_module_t 代碼如下:

typedef struct hw_module_t {
    uint32_t tag;
    uint16_t module_api_version;
#define version_major module_api_version
    uint16_t hal_api_version;
#define version_minor hal_api_version
    const char *id;
    const char *name;
    const char *author;
    struct hw_module_methods_t* methods; // hw_module_t 中必須提供
    void* dso;
#ifdef __LP64__
    uint64_t reserved[32-7];
#else
    uint32_t reserved[32-7];
#endif
} hw_module_t;

typedef struct hw_module_methods_t {
    // 函數指針,用于打開設備
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);

} hw_module_methods_t;

任何硬件設備的 HAL 庫都必須實現 hw_module_methods_t,該結構體中只有一個函數指針變量,也就是 open,由于函數體內不能定義函數,所以這里使用了函數指針。當上層使用者調用 hw_get_module 時,系統首先會在指定目錄下加載正確的 HAL 庫,然后通過 open 函數打開指定的設備。這里 open 方法對應的實現是 gralloc_device_open()@gralloc.cpp。open接口可以可以幫助上層使用者打開兩種設備:

  • define GRALLOC_HARDWARE_FB0:主屏
  • define GRALLOC_HARDWARE_GPU0:負責圖形緩沖區的分配和釋放

define 前有“#”,由于格式問題,這里沒有打出。

下面看 gralloc_device_open() 的實現:

// gralloc.cpp
int gralloc_device_open(const hw_module_t* module, const char* name,
        hw_device_t** device)
{
    int status = -EINVAL;
    // 打開 gralloc 設備或是打開 fb 設備。
    if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
        gralloc_context_t *dev;
        dev = (gralloc_context_t*)malloc(sizeof(*dev));

        /* initialize our state here */
        memset(dev, 0, sizeof(*dev));

        /* initialize the procs */
        dev->device.common.tag = HARDWARE_DEVICE_TAG;
        dev->device.common.version = 0;
        dev->device.common.module = const_cast<hw_module_t*>(module);
        dev->device.common.close = gralloc_close;

        dev->device.alloc   = gralloc_alloc;
        dev->device.free    = gralloc_free;

        *device = &dev->device.common;
        status = 0;
    } else {
        status = fb_device_open(module, name, device); // 打開 Framebuffer
    }
    return status;
}

下面看 framebuffer 設備的打開過程:

int fb_device_open(hw_module_t const* module, const char* name,
        hw_device_t** device)
{
    int status = -EINVAL;
    if (!strcmp(name, GRALLOC_HARDWARE_FB0)) {
        /* initialize our state here */
        fb_context_t *dev = (fb_context_t*)malloc(sizeof(*dev)); // 分配 hw_device_t 空間,這只是一個“殼”
        memset(dev, 0, sizeof(*dev)); // 初始化

        /* initialize the procs */
        dev->device.common.tag = HARDWARE_DEVICE_TAG;
        dev->device.common.version = 0;
        dev->device.common.module = const_cast<hw_module_t*>(module);
        // 核心接口
        dev->device.common.close = fb_close;
        dev->device.setSwapInterval = fb_setSwapInterval;
        dev->device.post            = fb_post;

        dev->device.setUpdateRect = 0;

        private_module_t* m = (private_module_t*)module;

        status = mapFrameBuffer(m); // 內存映射 mmap
        if (status >= 0) {
            int stride = m->finfo.line_length / (m->info.bits_per_pixel >> 3);
            int format = (m->info.bits_per_pixel == 32)
                         ? (m->info.red.offset ? HAL_PIXEL_FORMAT_BGRA_8888 : HAL_PIXEL_FORMAT_RGBX_8888)
                         : HAL_PIXEL_FORMAT_RGB_565;
            const_cast<uint32_t&>(dev->device.flags) = 0;
            const_cast<uint32_t&>(dev->device.width) = m->info.xres;
            const_cast<uint32_t&>(dev->device.height) = m->info.yres;
            const_cast<int&>(dev->device.stride) = stride;
            const_cast<int&>(dev->device.format) = format;
            const_cast<float&>(dev->device.xdpi) = m->xdpi;
            const_cast<float&>(dev->device.ydpi) = m->ydpi;
            const_cast<float&>(dev->device.fps) = m->fps;
            const_cast<int&>(dev->device.minSwapInterval) = 1;
            const_cast<int&>(dev->device.maxSwapInterval) = 1;
            *device = &dev->device.common; // 核心
        }
    }
    return status;
}

其中 fb_context_t 是 framebuffer 內部使用的一個類,它包含了眾多信息,而最終返回的 device 只是其內部的 device.common。這種“通用和差異”并存的編碼風格在 HAL 層非常常見。

fb_context_t 唯一的成員就是 framebuffer_device_t,這是對 frambuffer 設備的統一描述

struct fb_context_t {
    framebuffer_device_t  device;
};

一個標準的 fb 設備通常要提供如下的函數實現:

  • int(post)(struct framebuffer_device_t dev, buffer_handle_t buffer);
    將 buffer 數據 post 到顯示屏上。要求 buffer 必須與屏幕尺寸一致,并且沒有被 locked。這樣的話
    buffer 內容將在下一次 VSYNC 中被顯示出來。

  • int(setSwapInterval)(struct framebuffer_device_t window, int interval);
    設置兩個緩沖區交換的時間間隔

  • int(setUpdateRect)(struct framebuffer_device_t window, int left, int top, int width, int height);
    設置刷新區域,需要 framebuffer 驅動支持“update-on-demand”。也就是說在這個區域外的數據很可能
    被認為無效。

framebuffer_device_t 中的重要成員變量:

typedef struct framebuffer_device_t {
    struct hw_device_t common;
    const uint32_t  flags; // 用來記錄系統幀緩沖區的標志
    const uint32_t  width; // 用來描述設備顯示屏的寬度
    const uint32_t  height; // 用來描述設備顯示屏的高度
    const int       stride; // 用來描述設備顯示屏的一行有多少個像素點
    const int       format; // 用來描述系統幀緩沖區的像素格式
    const float     xdpi; // 用來描述設備顯示屏在寬度上的密度
    const float     ydpi; // 用來描述設備顯示屏在高度上的密度
    const float     fps; // 用來描述設備顯示屏的刷新頻率
    const int       minSwapInterval; // 用來描述幀緩沖區交換前后兩個圖形緩沖區的最小時間間隔
    const int       maxSwapInterval; // 用來描述幀緩沖區交換前后兩個圖形緩沖區的最大時間間隔
    int reserved[8];//保留
    // 用來設置幀緩沖區交換前后兩個圖形緩沖區的最小和最大時間間隔
    int (*setSwapInterval)(struct framebuffer_device_t* window,int interval);
    // 用來設置幀緩沖區的更新區域
    int (*setUpdateRect)(struct framebuffer_device_t* window,int left, int top, int width, int height);
    // 用來將圖形緩沖區buffer的內容渲染到幀緩沖區中去
    int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);
    // 用來通知 fb 設備,圖形緩沖區的組合工作已經完成
    int (*compositionComplete)(struct framebuffer_device_t* dev);
    void (*dump)(struct framebuffer_device_t* dev, char *buff, int buff_len);
    int (*enableScreen)(struct framebuffer_device_t* dev, int enable);
    // 保留
    void* reserved_proc[6];
} framebuffer_device_t;
變 量 描 述
uint32_t flags 標志位,指示framebuffer 的屬性配置
uint32_t width; uint32_t height; framebuffer 的寬和高,以像素為單位
int format framebuffer 的像素格式,比如:HAL_PIXEL_FORMAT_RGBA_8888,HAL_PIXEL_FORMAT_RGBX_8888,HAL_PIXEL_FORMAT_RGB_888,HAL_PIXEL_FORMAT_RGB_565 等等
float xdpi;float ydpi; x和y軸的密度(pixel per inch)
float fps 屏幕的每秒刷新頻率,假如無法正常從設備獲取的話,默認設置為 60Hz
int minSwapInterval;int maxSwapInterval; 該 framebuffer 支持的最小和最大緩沖交換時間

我們以下面簡圖來小結對 Gralloc 的分析:

[圖片上傳失敗...(image-f944da-1542183518291)]

四、Android 本地窗口

Native Window為OpenGL與本地窗口系統之間搭建了橋梁。整個GGUI系統至少需要兩種本地窗口:

  1. 面向管理者(SurfaceFlinger)
    SurfaceFlinger 是系統中所有 UI 界面的管理者,需要直接或間接的持有“本地窗口”,此本地窗口是
    FramebufferNativeWindow(4.2+ 后被廢棄)。
  2. 面向應用程序
    這類本地窗口是 Surface。

正常情況按照 SDK 向導生成 APK 應用程序,是采用 Skia 等第三方圖形庫,而對于希望使用 OpenGL ES 來完成復雜界面渲染的應用開發者來說,Android 也提供封裝的 GLSurfaceView(或其他方式)來實現圖形顯示。

4.1、FramebufferNativeWindow

EGL 需要根據本地窗口來為 OpenGL/OpenGL ES 創造環境,但是不論哪一類本地窗口都需要和“本地窗口類型”保持一致。

// /frameworks/native/opengl/include/EGL/eglplatform.h
...
typedef HWND    EGLNativeWindowType;

#elif defined(__WINSCW__) || defined(__SYMBIAN32__)  /* Symbian */

typedef int   EGLNativeDisplayType;
typedef void *EGLNativeWindowType;
typedef void *EGLNativePixmapType;

#elif defined(__ANDROID__) || defined(ANDROID) // Android 系統

struct ANativeWindow;
struct egl_native_pixmap_t;

typedef struct ANativeWindow*           EGLNativeWindowType;
...
#elif defined(__unix__) // unix 系統
...
typedef Window   EGLNativeWindowType;

#else
#error "Platform not recognized"
#endif
...

EGLNativeWindowType 在不同系統中對應不同的數據類型,而在 Android 中對應的是 ANativeWindow 指針。

// /system/core/include/system/window.h
struct ANativeWindow
{
    ...
    const uint32_t flags;        // 與 Surface 或 update 有關的屬性
    const int   minSwapInterval; // 最小交換時間間隔
    const int   maxSwapInterval; // 最大交換時間間隔
    const float xdpi;            // 水平方向密度 dpi
    const float ydpi;            // 垂直方向密度 dpi
    intptr_t    oem[4];
    ...
    // 設置交換時間
    int     (*setSwapInterval)(struct ANativeWindow* window,
                int interval);
    // 向本地窗口查詢相關信息
    int     (*query)(const struct ANativeWindow* window,
                int what, int* value);
    // 用于執行本地窗口的相關操作
    int     (*perform)(struct ANativeWindow* window,
                int operation, ... );
    int     (*cancelBuffer_DEPRECATED)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer);
    // EGL 通過該接口來申請 buffer
    int     (*dequeueBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer** buffer, int* fenceFd);
    // EGL 對 buffer 渲染完成后就調用該接口,來 unlock 和 post buffer
    int     (*queueBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer, int fenceFd);
    // 取消一個已經 dequeue 的 buffer
    int     (*cancelBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer, int fenceFd);
};

ANativeWindow 更像一份“協議”,規定了本地窗口的形態和功能。下面來分析 FramebufferNativeWindow 是如何履行“協議”的。

(1) FramebufferNativeWindow 構造函數

FramebufferNativeWindow 構造函數的功能包括:

  • 加載 Gralloc 模塊(GRALLOC_HARDWARE_MODULE_ID)。
  • 打開 fb 和 gralloc(gpu0) 設備,打開后由 fbDev 和 grDev 管理。
  • 根據設備屬性為 FramebufferNativeWindow 賦初值。
  • 根據 FramebufferNativeWindow 的實現來填充 ANativeWindow 中的“協議”。
  • 其他必要的初始化。

所有申請到的緩沖區都由 FramebufferNativeWindow 中的 buffers[] 來記錄,每個元素是一個 NativeBuffer,該類繼承了 ANativeWindowBuffer, 該類的聲明如下:

// /system/core/include/system/window.h
typedef struct ANativeWindowBuffer
{
    ...
    int width;
    int height;
    int stride;
    int format;
    int usage;

    void* reserved[2];

    buffer_handle_t handle; // 代表內存塊的句柄

    void* reserved_proc[8];
} ANativeWindowBuffer_t;

(2) dequeueBuffer

int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window,
        ANativeWindowBuffer** buffer)
{
    FramebufferNativeWindow* self = getSelf(window);
    Mutex::Autolock _l(self->mutex);
    // 從 FramebufferNativeWindow 對象中取出 fb 設備描述符,在構造 FramebufferNativeWindow 對象時,已經打開了 fb 設備
    framebuffer_device_t* fb = self->fbDev;
    // 計算當前申請的圖形緩沖區在 buffers 數組中的索引,同時將下一個申請的 buffe r的索引保存到 mBufferHead 中
    int index = self->mBufferHead++;
    // 如果申請的下一個 buffer 的索引大于或等于 buffer 總數,則將下一個申請的 buffer 索引設置為 0,這樣就實現了對 buffer 數組的循環管理
    if (self->mBufferHead >= self->mNumBuffers)
        self->mBufferHead = 0;
    // 如果當前沒有空閑的 buffer,即 mNumFreeBuffers = 0,則線程睡眠等待 buffer 的釋放
    while (!self->mNumFreeBuffers) {
        self->mCondition.wait(self->mutex);
    }
    // 存在了空閑 buffer,線程被喚醒繼續執行,由于此時要申請一塊 buffer,因此空閑 buffer 的個數又需要減 1
    self->mNumFreeBuffers--;
    // 保存當前申請的 buffer 在緩沖區數組中的索引位置
    self->mCurrentBufferIndex = index;
    // 得到 buffer 數組中的 NativeBuffer 對象指針
    *buffer = self->buffers[index].get();
    return 0;
}

dequeueBuffer 函數就是從 FramebufferNativeWindow 創建的包含 2 個圖形緩沖區的緩沖區隊列 buffers 中取出一塊空閑可用的圖形 buffer,如果當前緩沖區隊列中沒有空閑的 buffer,則當前申請 buffer 線程阻塞等待,等待其他線程釋放圖形緩沖區。mNumFreeBuffers 用來描述可用的空閑圖形 buffer 個數,index 記錄當前申請 buffer 在圖形緩沖區隊列中的索引位置,mBufferHead 指向下一次申請的圖形 buffer 的位置,由于我們是循環利用兩個緩沖區的,所以如果這個變量的值超過 mNumBuffers,就需要置 0。也就是說 mBufferHead 的值永遠只能是 0或者 1。

4.2、SurfaceView

Surface 也繼承了 ANativeWindow:

class Surface: public ANativeObjectBase<ANativeWindow, Surface, RefBase>{ ... }

Surface 是面向 Android 系統中所有 UI 應用程序的,即它承擔著應用進程中的 UI 顯示需求。

Surface 需要面向上層實現(主要是 Java 層)提供繪制圖像的畫板。SurfaceFlinger 需要收集系統中所有應用程序繪制的圖像數據,然后集中顯示到物理屏幕上。Surface 需要扮演相應角色,本質上還是由 SurfaceFlinger 服務統一管理的,涉及到很多跨進程的通信細節。

下面來看 Surface 中的關鍵成員變量:

成員變量 說明
sp<IGraphicsBufferProducer> mGraphicsBufferProducer Surface 核心變量
BufferSlot mSlots[32] Surface 內部存儲 buffer 的地方,BufferSlot 內不包括:GraphicsBuffer 和 dirtyRegion,當用戶 dequeue 時將申請內存

Surface 將通過 mGraphicBufferProducer 來獲取 buffer,這些緩沖區會被記錄在 mSlots 中數據中。mGraphicBufferProducer 這一核心成員的初始化流程如下:

  • ViewRootImpl 持有一個 Java 層的 Surface 對象(mSurface)。
  • ViewRootImpl 向 WindowManagerService 發起 relayout 請求,此時 mSurface 被賦予真正的有效值,
    將輾轉生成的 SurfaceControl 通過S urface.copyFrom() 函數復制到 mSurface 中。

由此,Surface 由 SurfaceControl 管理,SurfaceControl 由 SurfaceComposerClient 創建。SurfaceComposerClient 獲得的匿名 Binder 是 ISurfaceComposer,其服務端實現是 SurfaceFlinger。而 Surface 依賴的 IGraphicBufferProducer 對象在 Service 端的實現是 BufferQueue。

class SurfaceFlinger :
  public BinderService<SurfaceFlinger>, // 在 ServiceManager 中注冊為 SurfaceFlinger
  public BnSurfaceComposer, // 實現的接口卻叫 ISurfaceComposer

Buffer,Consumer,Producer 是“生產者-消費者”模型中的 3 個參與對象,如何協調好它們的工作是應用程序能否正常顯示UI的關鍵。Buffer 是 BufferQueue,Producer 是應用程序,Consumer 是 SurfaceFlinger。

五、BufferQueue

To be continued ....

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

推薦閱讀更多精彩內容