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阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容