GraphicBuffer管理

Android本地窗口

Android系统定义了一个Native窗口 ANativeWindow,结构定义如下:

struct ANativeWindow
{
#ifdef __cplusplus
    ANativeWindow()
        : flags(0), minSwapInterval(0), maxSwapInterval(0), xdpi(0), ydpi(0)
    {
        common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
        common.version = sizeof(ANativeWindow);
        memset(common.reserved, 0, sizeof(common.reserved));
    }

    /* Implement the methods that sp<ANativeWindow> expects so that it
       can be used to automatically refcount ANativeWindow's. */
    void incStrong(const void* /*id*/) const {
        common.incRef(const_cast<android_native_base_t*>(&common));
    }
    void decStrong(const void* /*id*/) const {
        common.decRef(const_cast<android_native_base_t*>(&common));
    }
#endif

    struct android_native_base_t common;

    /* flags describing some attributes of this surface or its updater */
    const uint32_t flags;

    /* min swap interval supported by this updated */
    const int   minSwapInterval;

    /* max swap interval supported by this updated */
    const int   maxSwapInterval;

    /* horizontal and vertical resolution in DPI */
    const float xdpi;
    const float ydpi;

    /* Some storage reserved for the OEM's driver. */
    intptr_t    oem[4];

    /*
     * Set the swap interval for this surface.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*setSwapInterval)(struct ANativeWindow* window,
                int interval);

    /*
     * Hook called by EGL to acquire a buffer. After this call, the buffer
     * is not locked, so its content cannot be modified. This call may block if
     * no buffers are available.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     *
     * Returns 0 on success or -errno on error.
     *
     * XXX: This function is deprecated.  It will continue to work for some
     * time for binary compatibility, but the new dequeueBuffer function that
     * outputs a fence file descriptor should be used in its place.
     */
    int     (*dequeueBuffer_DEPRECATED)(struct ANativeWindow* window,
                struct ANativeWindowBuffer** buffer);

    /*
     * hook called by EGL to lock a buffer. This MUST be called before modifying
     * the content of a buffer. The buffer must have been acquired with
     * dequeueBuffer first.
     *
     * Returns 0 on success or -errno on error.
     *
     * XXX: This function is deprecated.  It will continue to work for some
     * time for binary compatibility, but it is essentially a no-op, and calls
     * to it should be removed.
     */
    int     (*lockBuffer_DEPRECATED)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer);

    /*
     * Hook called by EGL when modifications to the render buffer are done.
     * This unlocks and post the buffer.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     *
     * Buffers MUST be queued in the same order than they were dequeued.
     *
     * Returns 0 on success or -errno on error.
     *
     * XXX: This function is deprecated.  It will continue to work for some
     * time for binary compatibility, but the new queueBuffer function that
     * takes a fence file descriptor should be used in its place (pass a value
     * of -1 for the fence file descriptor if there is no valid one to pass).
     */
    int     (*queueBuffer_DEPRECATED)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer);

    /*
     * hook used to retrieve information about the native window.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*query)(const struct ANativeWindow* window,
                int what, int* value);

    /*
     * hook used to perform various operations on the surface.
     * (*perform)() is a generic mechanism to add functionality to
     * ANativeWindow while keeping backward binary compatibility.
     *
     * DO NOT CALL THIS HOOK DIRECTLY.  Instead, use the helper functions
     * defined below.
     *
     * (*perform)() returns -ENOENT if the 'what' parameter is not supported
     * by the surface's implementation.
     *
     * See above for a list of valid operations, such as
     * NATIVE_WINDOW_SET_USAGE or NATIVE_WINDOW_CONNECT
     */
    int     (*perform)(struct ANativeWindow* window,
                int operation, ... );

    /*
     * Hook used to cancel a buffer that has been dequeued.
     * No synchronization is performed between dequeue() and cancel(), so
     * either external synchronization is needed, or these functions must be
     * called from the same thread.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     *
     * XXX: This function is deprecated.  It will continue to work for some
     * time for binary compatibility, but the new cancelBuffer function that
     * takes a fence file descriptor should be used in its place (pass a value
     * of -1 for the fence file descriptor if there is no valid one to pass).
     */
    int     (*cancelBuffer_DEPRECATED)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer);

    /*
     * Hook called by EGL to acquire a buffer. This call may block if no
     * buffers are available.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     *
     * The libsync fence file descriptor returned in the int pointed to by the
     * fenceFd argument will refer to the fence that must signal before the
     * dequeued buffer may be written to.  A value of -1 indicates that the
     * caller may access the buffer immediately without waiting on a fence.  If
     * a valid file descriptor is returned (i.e. any value except -1) then the
     * caller is responsible for closing the file descriptor.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*dequeueBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer** buffer, int* fenceFd);

    /*
     * Hook called by EGL when modifications to the render buffer are done.
     * This unlocks and post the buffer.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     *
     * The fenceFd argument specifies a libsync fence file descriptor for a
     * fence that must signal before the buffer can be accessed.  If the buffer
     * can be accessed immediately then a value of -1 should be used.  The
     * caller must not use the file descriptor after it is passed to
     * queueBuffer, and the ANativeWindow implementation is responsible for
     * closing it.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*queueBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer, int fenceFd);

    /*
     * Hook used to cancel a buffer that has been dequeued.
     * No synchronization is performed between dequeue() and cancel(), so
     * either external synchronization is needed, or these functions must be
     * called from the same thread.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     *
     * The fenceFd argument specifies a libsync fence file decsriptor for a
     * fence that must signal before the buffer can be accessed.  If the buffer
     * can be accessed immediately then a value of -1 should be used.
     *
     * Note that if the client has not waited on the fence that was returned
     * from dequeueBuffer, that same fence should be passed to cancelBuffer to
     * ensure that future uses of the buffer are preceded by a wait on that
     * fence.  The caller must not use the file descriptor after it is passed
     * to cancelBuffer, and the ANativeWindow implementation is responsible for
     * closing it.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*cancelBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer, int fenceFd);
};

根据ANativeWindow的结构定义可看出,ANativeWindow描述了本地窗口的基本信息,同时还定义了一系列操作窗口缓冲区的方法,窗口是需要内容显示的,显示的内容自然是存放在窗口缓冲区ANativeWindowBuffer中。
来看下ANativeWindowBuffer窗口缓存的结构定义:


typedef struct ANativeWindowBuffer
{
#ifdef __cplusplus
    ANativeWindowBuffer() {
        common.magic = ANDROID_NATIVE_BUFFER_MAGIC;
        common.version = sizeof(ANativeWindowBuffer);
        memset(common.reserved, 0, sizeof(common.reserved));
    }

    // Implement the methods that sp<ANativeWindowBuffer> expects so that it
    // can be used to automatically refcount ANativeWindowBuffer's.
    void incStrong(const void* /*id*/) const {
        common.incRef(const_cast<android_native_base_t*>(&common));
    }
    void decStrong(const void* /*id*/) const {
        common.decRef(const_cast<android_native_base_t*>(&common));
    }
#endif

    struct android_native_base_t common;

    int width;
    int height;
    int stride;
    int format;
    int usage;

    void* reserved[2];

    buffer_handle_t handle;

    void* reserved_proc[8];
} ANativeWindowBuffer_t;

ANativeWindowBuffer定义了窗口缓冲区的基本信息,包括宽,高, stride(每行像素个数),图像格式等,还定义了handle指针,存放Gralloc模块分配的真正的Buffer的地址。

ANativeWindow代表了一个窗口,提供了窗口管理的基本方法来操作窗口缓冲区的出队和入队。ANativeWindowBuffer则负责描述Window一个图形缓冲区。

Surface实现了ANativeWindow, 负责管理一个窗口,而GraphicBuffer则实现了ANativeWindowBuffer,负责管理一个图形缓冲区。Surface后续在进行分析,我们先看下GraphicBuffer的实现。

GraphicBuffer的实现

class GraphicBuffer
    : public ANativeObjectBase< ANativeWindowBuffer, GraphicBuffer, RefBase >,
      public Flattenable<GraphicBuffer>

GraphicBuffer继承自ANativeWindow

   // creates w * h buffer
    GraphicBuffer(uint32_t inWidth, uint32_t inHeight, PixelFormat inFormat,
            uint32_t inUsage);

    // create a buffer from an existing handle
    GraphicBuffer(uint32_t inWidth, uint32_t inHeight, PixelFormat inFormat,
            uint32_t inUsage, uint32_t inStride, native_handle_t* inHandle,
            bool keepOwnership);

    // create a buffer from an existing ANativeWindowBuffer
    GraphicBuffer(ANativeWindowBuffer* buffer, bool keepOwnership);

GraphicBuffer有三个构造函数
1:创建一个width × height的GraphicBuffer
2:根据已经有的buffer的handle来创建一个GraphicBuffer
3:根据一个ANativeWindowBuffer来创建一个GraphicBuffer
我们只分析第一个看看GraphicBuffer是如何创建的?

GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage)
    : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
      mInitCheck(NO_ERROR), mId(getUniqueId()), mGenerationNumber(0)
{
    width=height=stride=format=usage=0;
    handle = NULL;
    mInitCheck = initSize(inWidth, inHeight, inFormat, inUsage);
}

构造方法先将GraphicBuffer的width,height等属性设置为默认值0,然后调用initSize来创建Buffer.

status_t GraphicBuffer::initSize(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage)
{
    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
    uint32_t outStride = 0;
    status_t err = allocator.alloc(inWidth, inHeight, inFormat, inUsage,
            &handle, &outStride);
    if (err == NO_ERROR) {
        width = static_cast<int>(inWidth);
        height = static_cast<int>(inHeight);
        format = inFormat;
        usage = static_cast<int>(inUsage);
        stride = static_cast<int>(outStride);
    }
    return err;
}

initSize方法中首先获取了GraphicBufferAllocator对象, GraphicBufferAllocator实现是单例模式,所以每个进程中只有一个GraphicBufferAllocator负责Buffer的分配。
调用GraphicBufferAllocator的alloc方法为当前GraphicBuffer分配了一个指定宽高,以及Format的Buffer, 分配成功后将宽高,stride等属性保存到GraphicBuffer对象的属性中,把buffer的handle保存在GraphicBuffer的handle中,GraphicBuffer就可以管理一个真正的图形缓冲区Buffer,这样GraphicBuffer就创建好了。

GraphicBuffer图形缓冲区的分配和释放

GraphicBuffer在构造方法中调用了GraphicBufferAllocator来分配了一个真正的图形缓冲区,具体是怎么分配的呢?

GraphicBufferAllocator::GraphicBufferAllocator()
    : mAllocDev(0)
{
    hw_module_t const* module;
    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
    ALOGE_IF(err, "FATAL: can't find the %s module", GRALLOC_HARDWARE_MODULE_ID);
    if (err == 0) {
        gralloc_open(module, &mAllocDev);
    }
}

在分析Gralloc模块的时候知道,Gralloc设备会在两个地方被打开,一个是HWCompser,另一个就是GraphicBufferAllocator, 此处会加载Gralloc硬件模块抽象库,同时调用gralloc_open来打开GRALLOC_HARDWARE_GPU0 设备,将返回的alloc_device_t保存在mAllocDev中。
接着看负责缓冲区分配的alloc方法。

status_t GraphicBufferAllocator::alloc(uint32_t width, uint32_t height,
        PixelFormat format, uint32_t usage, buffer_handle_t* handle,
        uint32_t* stride)
{
    ATRACE_CALL();

    //如果没有传入Buffer的宽高尺寸,则默认创建一个1*1的Buffer
    if (!width || !height)
        width = height = 1;

    ...

    int outStride = 0;
    err = mAllocDev->alloc(mAllocDev, static_cast<int>(width),
            static_cast<int>(height), format, static_cast<int>(usage), handle,
            &outStride);
    *stride = static_cast<uint32_t>(outStride);
    //如果Buffer分配成功,则根据Buffer的属性创建一个alloc_rec_t 对象,保存在sAllocList中
    if (err == NO_ERROR) {
        Mutex::Autolock _l(sLock);
        KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
        uint32_t bpp = bytesPerPixel(format);
        alloc_rec_t rec;
        rec.width = width;
        rec.height = height;
        rec.stride = *stride;
        rec.format = format;
        rec.usage = usage;
        rec.size = static_cast<size_t>(height * (*stride) * bpp);
        list.add(*handle, rec);
    }

    return err;
}

status_t GraphicBufferAllocator::free(buffer_handle_t handle)
{
    ATRACE_CALL();
    status_t err;

    err = mAllocDev->free(mAllocDev, handle);
    if (err == NO_ERROR) {
        Mutex::Autolock _l(sLock);
        KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
        //从sAllocList列表中移除
        list.removeItem(handle);
    }

    return err;
}

alloc方法会调用打开的alloc_device_t设备的alloc方法,GraphicBufferAllocator中调用 mAllocDev->alloc方法会调用到gralloc模块,来分配图形缓冲区,如果分配成功之后则根据返回的属性创建一个alloc_rec_t对象,并将这个对象添加到sAllocList中。
sAllocList是GraphicBufferAllocator维护的一个列表,一个进程中所有分配的图形缓冲区都会维护在这个列表中,只有调用GraphicBufferAllocator的free方法释放缓冲区后,才会从列表中移除。

Gralloc模块图形缓冲区的分配和释放

int gralloc_device_open(const hw_module_t* module, const char* name,
        hw_device_t** device)
{
    if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
        gralloc_context_t *dev;
        ...
        dev->device.common.close = gralloc_close;

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

    } else {
        ...
    }
}

alloc_device_t设备在打开的过程中会注册操作图形缓冲区的方法,gralloc_alloc和gralloc_free,两个方法分别 用于分配和释放图形缓冲区。

gralloc_alloc

static int gralloc_alloc(alloc_device_t* dev,
        int width, int height, int format, int usage,
        buffer_handle_t* pHandle, int* pStride)
{
    if (!pHandle || !pStride)
        return -EINVAL;
    //根据formate来计算每个像素占几个字节
    int bytesPerPixel = 0;
    switch (format) {
        case HAL_PIXEL_FORMAT_RGBA_8888:
        case HAL_PIXEL_FORMAT_RGBX_8888:
        case HAL_PIXEL_FORMAT_BGRA_8888:
            bytesPerPixel = 4;
            break;
        case HAL_PIXEL_FORMAT_RGB_888:
            bytesPerPixel = 3;
            break;
        case HAL_PIXEL_FORMAT_RGB_565:
        case HAL_PIXEL_FORMAT_RAW16:
            bytesPerPixel = 2;
            break;
        default:
            return -EINVAL;
    }

    const size_t tileWidth = 2;
    const size_t tileHeight = 2;
    //计算stride和size
    size_t stride = align(width, tileWidth);
    size_t size = align(height, tileHeight) * stride * bytesPerPixel + 4;

    //因我们是操作图形缓冲区,而非FB显存,所以直接看gralloc_alloc_buffer方法

    int err;
    if (usage & GRALLOC_USAGE_HW_FB) {
       ...
    } else {
        err = gralloc_alloc_buffer(dev, size, usage, pHandle);
    }

    *pStride = stride;
    return 0;
}

该方法首先根据传入的图像格式计算每个像素占用的字节数,根据每个像素的字节数计算每行像素的个数stride 以及 图形缓冲区所需要的内存大小size。
最后由这些结果作为参数调用gralloc_alloc_buffer来分配缓冲区内存。

static int gralloc_alloc_buffer(alloc_device_t* dev,
        size_t size, int /*usage*/, buffer_handle_t* pHandle)
{
    int err = 0;
    int fd = -1;
    //将缓冲区大小页对齐
    size = roundUpToPageSize(size);
    //创建一个匿名的共享内存,大小为size
    fd = ashmem_create_region("gralloc-buffer", size);
    if (fd < 0) {
        ALOGE("couldn't create ashmem (%s)", strerror(-errno));
        err = -errno;
    }
    //然后根据共享内存的文件描述符fd
    if (err == 0) {
        private_handle_t* hnd = new private_handle_t(fd, size, 0);
        gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
                dev->common.module);
        err = mapBuffer(module, hnd);
        if (err == 0) {
            *pHandle = hnd;
        }
    }

    ALOGE_IF(err, "gralloc failed err=%s", strerror(-err));

    return err;
}

int mapBuffer(gralloc_module_t const* module,
        private_handle_t* hnd)
{
    void* vaddr;
    return gralloc_map(module, hnd, &vaddr);
}

static int gralloc_map(gralloc_module_t const* /*module*/,
        buffer_handle_t handle,
        void** vaddr)
{
    private_handle_t* hnd = (private_handle_t*)handle;
    if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
        size_t size = hnd->size;
        //将共享内存映射到当前进程
        void* mappedAddress = mmap(0, size,
                PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
        //将首地址存放到private_handle_t的base变量中,返回给调用者
        hnd->base = uintptr_t(mappedAddress) + hnd->offset;
   
    }
    *vaddr = (void*)hnd->base;
    return 0;
}

gralloc_alloc_buffer分配缓冲区内存做了什么操作呢?
1:根据参数传入的缓冲区大小 创建了一个匿名共享内存,共享内存fd存放到private_handle_t的fd中
2:调用mapBuffer,将共享内存映射到当前进程,并吧内存首地址保存到private_handle_t的base中
3:将private_handle_t返回给调用者,这个就是图形缓冲区的handle, 用于唯一区别一个缓冲区。

至此,Gralloc模块分配缓冲区的逻辑就分析完了

gralloc_free

static int gralloc_free(alloc_device_t* dev,
        buffer_handle_t handle)
{
    if (private_handle_t::validate(handle) < 0)
        return -EINVAL;

    private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(handle);
    if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
        ...
    } else {
        gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
                dev->common.module);
        terminateBuffer(module, const_cast<private_handle_t*>(hnd));
    }

    close(hnd->fd);
    delete hnd;
    return 0;
}

根据传入的图形缓冲区的private_handle_t标识符,调用terminateBuffer来释放buffer, 因我们分析GraphicBuffer的分配与释放,FB设备的相关逻辑暂时不关注。
直接看terminateBuffer是如何释放缓冲区的?

int terminateBuffer(gralloc_module_t const* module,
        private_handle_t* hnd)
{
    if (hnd->base) {
        //hnd的base中保存的是映射内存的首地址,如果这个属性不为0,说明地址已经映射过了,需要取消映射
        gralloc_unmap(module, hnd);
    }

    return 0;
}

     
static int gralloc_unmap(gralloc_module_t const* /*module*/,
        buffer_handle_t handle)
{
    private_handle_t* hnd = (private_handle_t*)handle;
    //如果不是FB设备,则直接调用munmap方法,取消映射
    if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
        void* base = (void*)hnd->base;
        size_t size = hnd->size;
        //ALOGD("unmapping from %p, size=%d", base, size);
        if (munmap(base, size) < 0) {
            ALOGE("Could not unmap %s", strerror(errno));
        }
    }
    hnd->base = 0;
    return 0;
}

terminateBuffer 释放图形缓冲区很简单,根据private_handle_t的base属性判断内存是否已经映射过了,如果已经映射,则调用munmap方法取消映射即可。

总结

GraphicBuffer实现了ANativeWindowBuffer,用来管理图像窗口的图形缓冲区,是ANativeWindow的显示内容和操作对象,GraphicBuffer的handle指针指向的才是真正的缓冲区内存。
GraphicBufferAllocator负责缓冲区内存的分配,会调用到gralloc模块的gralloc设备进行分配。每个图形缓冲区都是一个匿名共享内存空间,分配的时候创建一个匿名共享内存,然后映射到当前进程,释放的时候将匿名共享内存从当前进程取消映射。


GraphicBuffer管理
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 【读经】 王下20章 【金句】 “你回去告诉我民的君希西家 (列王纪下 20:5 和合本) 【感动】 耶和华称呼希...
    chanor阅读 1,121评论 0 0
  • 你觉得自己表达不好,努力学习各种表达技巧,可是仍然没有该善。为什么呢?很可能是你找错了方向。你的表达不能打动人,也...
    惜惜阅读 546评论 0 15
  • 每个人都有优点与缺点,任意放大缺点会变成自卑,所以我们都应该正视缺点关注优点,好好爱自己。自卑是不爱自己的...
    兰泽_ed50阅读 362评论 3 0
  • 美术作业,模仿姐姐的画,画
    纶纶妈阅读 159评论 0 0
  • 2019做真的自己,向内寻求,不向外比。 最近在读一些国外的心理学的书,发现和中国的哲学大同小异,中国的孔孟,王阳...
    蕙心读书阅读 513评论 3 2