Android | InputManagerService与输入事件采集

前言

  • 事件分发机制是Android中的基础而重要的知识,一般认为Activity#dispatchKeyEvent()或者Activity#dispatchTouchEvent()是分发的起点。那么问题来了,是谁调用了Activity的方法呢?输入事件是如何产生的?
  • Android系统有一整套从Linux内核应用框架层应用层的事件处理机制
  • 本文将以InputManagerService为线索,分析输入事件的产生-采集-分发流程,希望能帮上忙
思维导图
输入事件 示意图

1. 启动服务

Android系统启动后,系统进程SystemServer.java将依次启动各个系统服务,我们搜索下InputManagerService,不会匹配到太多东西,梳理一下有关的代码:

关于系统服务的更多介绍参考:[Android | 系统启动过程]

// /frameworks/base/services/java/com/android/server/SystemServer.java
private void startBootstrapServices() {
    InputManagerService inputManager = null;
    WindowManagerService wm = null;
        
    // ...
        
    // 实例化InputManagerService
    inputManager = new InputManagerService(context);
    // 实例化WindowManagerService
    wm = WindowManagerService.main(context,inputManager,...);
    // 添加到ServiceManager统一管理
    ServiceManager.addService(Context.WINDOW_SERVICE, wm, ...);
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager,...);
        
    // ...
        
    inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
    // 启动InputManager服务
    inputManager.start();
}
IMS与WMS UML类图

可以看出,系统进程分别实例化了InputManagerServiceWindowManagerService,前者的实例直接传入后者,而后者又传递了一个InputMonitor对象给前者,这是为什么呢?暂时跳过,继续往下看InputManagerService.java

// /frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

private static native long nativeInit(InputManagerService service,Context context, MessageQueue messageQueue);

private static native void nativeStart(long ptr);

public InputManagerService(Context context) {
    // ...
    // 调用了native层
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
    // ...
}

public void start() {
    // 调用了native层
    nativeStart(mPtr);
    // ...
}

这里只是调用到native层的两个静态方法,继续往下看com_android_server_input_InputManagerService.cpp

// /frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static jlong nativeInit(JNIEnv* env, jclass /* clazz */, jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    // ...
    // 实例化NativeInputManager对象
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    // 调用到InputManager#start()
    status_t result = im->getInputManager()->start();
}

NativeInputManager::NativeInputManager(jobject contextObj, 
        jobject serviceObj, const sp<Looper>& looper) : 
    mLooper(looper), mInteractive(true) {
    // ...
    // 实例化EventHub对象
    sp<EventHub> eventHub = new EventHub();
    // 实例化InputManager对象
    mInputManager = new InputManager(eventHub, this, this);
}

public: 
inline sp<InputManager> getInputManager() const { return mInputManager; }

可以看出,在native层实例化了NativeInputManager,其构造方法里还实例化了InputManagerEventHub对象,后者是干什么的呢?暂时跳过,继续往下看InputManager.cpp

// /frameworks/native/services/inputflinger/InputManager.cpp

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    // 实例化InputDispatcher与InputReader
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    // InputDispatcher实例与EventHub实例传递给InputReader
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

void InputManager::initialize() {
    // 实例化两个线程
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

status_t InputManager::start() {
    // 运行两个线程
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    // ...
}

可以看出,InputManager的构造方法中实例化了InputDispatcherInputReader,随后start()中启动了InputDispatcherThread线程与InputReaderThread线程(继承于Thread.cpp)。

Timethreads图 - 01

这两个线程分别做什么事呢?继续往下看InputReder.cppInputDispatcher.cpp

// /frameworks/native/services/inputflinger/InputReader.cpp

InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) : 
// 继承于Thread
Thread(/*canCallJava*/ true), mReader(reader) {
}

/**
 * true:循环执行threadLoop()直到调用requireExit()退出循环
 **/
bool InputReaderThread::threadLoop() {
    // 读取/采集一次事件
    mReader->loopOnce();
    return true;
}
// /frameworks/native/services/inputflinger/InputDispatcher.cpp

InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) : Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
}

bool InputDispatcherThread::threadLoop() {
    // 分发一次事件
    mDispatcher->dispatchOnce();
    return true;
}

可以看到,到这里InputManagerService的启动就完成了,提炼出关键点:

  • InputManagerServiceWindowManagerService运行在系统进程SystemServer
  • InputManagerService启动了InputReaderThread线程与InputDispatcherThread线程,分别死循环调用InputReader#loopOnce()InputDispatcher#dispatchOnce()
Timethreads图 - 02

2. 采集事件

上一节讲到,InputReaderThread线程死循环执行InputReader#loopOnce(),用于采集事件,简化代码如下:

// /frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::loopOnce() {
    // ...
    // 从EventHub中读取事件,存储在指针mEventBuffer中
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    if (count) {
        // 处理读取到的事件
        processEventsLocked(mEventBuffer, count);
    }
    // ...
    // 这一行是干什么的?稍后介绍
    mQueuedListener->flush();
}

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    // 遍历每个事件
    for (const RawEvent* rawEvent = rawEvents; count;) {
        // ...
        int32_t type = rawEvent->type;
        if(type <  EventHubInterface::FIRST_SYNTHETIC_EVENT){
            // 分支1:输入事件
            // ...
                        
            // 处理每一个输入事件
            int32_t deviceId = rawEvent->deviceId;
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        }else{
            // 分支2:设备事件
            switch(rawEvent->type){
                    case EventHubInterface::DEVICE_ADDED:
                        addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                        break;
                       // ...
            }
        }
    }
    // ...
}

可以看到,loopOnce()EventHub实例中获取到原始事件,并依次处理每个事件,分为两种:

  • 输入事件(分支1)
  • 设备事件(分支2)

我们先看分支1的processEventsForDeviceLocked(),简化代码如下:

// /frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    // ...
    InputDevice* device = mDevices.valueAt(deviceIndex);
    // ...
    device->process(rawEvents, count);
}

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    size_t numMappers = mMappers.size();
    // 遍历每个事件
    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
            // 依次交给每个InputMapper处理
            for (size_t i = 0; i < numMappers; i++) {
                    InputMapper* mapper = mMappers[i];
                    mapper->process(rawEvent);
            }
    }
}

可以看到,输入事件交给了InputDevice处理,在内部实际上是执行了多次InputMapper#process(),这里的InputDeviceInputMapper是什么呢,还记得分支2的addDeviceLocked()吗?简化代码如下:

// /frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
    // ...
    // 创建InputDevice实例
    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
    // ...
    // 添加到mDevices列表 
    // InputReader.h:KeyedVector<int32_t, InputDevice*> mDevices;
    mDevices.add(deviceId, device);
    // ...
}

InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber, const InputDeviceIdentifier& identifier, uint32_t classes) {
    // 创建InputDevice实例
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(), controllerNumber, identifier, classes);

    // ...
        
    // Scroll wheel-like devices. - 滚轮式
    if (classes & INPUT_DEVICE_CLASS_ROTARY_ENCODER) {
        device->addMapper(new RotaryEncoderInputMapper(device));
    }
        
    // ...
        
    // Touchscreens and touchpad devices. - 触屏式
    if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
        // 多点触控设备
        device->addMapper(new MultiTouchInputMapper(device));
    } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
        // 单点触控设备
        device->addMapper(new SingleTouchInputMapper(device));
    }
        
    // Joystick-like devices. - 操纵杆式
    if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
        device->addMapper(new JoystickInputMapper(device));
    }
        // ...
}

可以看到分支2里,先创建了InputDevice实例,随后根据不同的设备事件的类型,又添加了不同InputMapper,比如滚轮式,触屏式,操纵杆式,这也就说明了,一个Android设备上是支持同时接入多个输入设备的,当然触屏设备才是本文分析的重点。

让我们回到分支1的InputMapper#process(),因为我们的重点是触屏设备,所以我们只关心MultiTouchInputMapperSingleTouchInputMapper,代码如下:

// /frameworks/native/services/inputflinger/InputReader.cpp

// 多点触控设备
void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
    TouchInputMapper::process(rawEvent);
    // ...
}

// 单点触控设备
void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
    TouchInputMapper::process(rawEvent);
    // ...
}

void TouchInputMapper::process(const RawEvent* rawEvent) {
    // ...
    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
        sync(rawEvent->when);
    }
}

void TouchInputMapper::sync(nsecs_t when) {
    // ...
    processRawTouches(false /*timeout*/);
}

void TouchInputMapper::processRawTouches(bool timeout) {
    // ...
    cookAndDispatch(mCurrentRawState.when);
}

void TouchInputMapper::cookAndDispatch(nsecs_t when) {
    // ...
        
    // 按键事件
    if (consumeRawTouches(when, policyFlags)) {
        mCurrentRawState.rawPointerData.clear();
    }
    // ...
    // 触点事件
    dispatchPointerUsage(when, policyFlags, pointerUsage);
    // ...
}

从简化的代码可以看出,不论是MultiTouchInputMapper还是SingleTouchInputMapper,最终都会走到cookAndDispatch()这个方法,分别处理了两类事件:

  • 按键事件
  • 触点事件
// /frameworks/native/services/inputflinger/InputReader.cpp

bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) {
    // ...
    // 处理虚拟按键事件
    dispatchVirtualKey(...)
    // ...
}

void TouchInputMapper::dispatchVirtualKey(...) {
    // ...
    // 实例化一个NotifyKeyArgs
    NotifyKeyArgs args(...);
    // 回调
    getListener()->notifyKey(&args);
}
// /frameworks/native/services/inputflinger/InputReader.cpp

void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags, PointerUsage pointerUsage) {
    // ...
    // 处理触点事件
    dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
    // ...
}

void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout) {
    // ...
    // 处理触点事件
    dispatchMotion(...)
    // ...
}

void TouchInputMapper::dispatchMotion(...){
    // ...
    // 实例化一个NotifyMotionArgs
    NotifyMotionArgs args(...);
    // 回调
    getListener()->notifyMotion(&args);
}

从简化代码可以看出,对于按键事件和触点事件,分别实例化了NotifyKeyArgsNotifyMotionArgs,随后分别调用了getListener()->notifyKey()getListener()->notifyMotion()

事件采集 UML时序图 - 01

getListener()->notifyKey()看起来很像是告知监听器已经采集到事件了,是不是这样呢?找一下getListener()相关代码:

// /frameworks/native/services/inputflinger/InputReader.cpp

InputListenerInterface* InputReader::ContextImpl::getListener() {
    // 弱指针的用法,简单理解为返回了mQueuedListener就好
    return mReader->mQueuedListener.get();
}

// --- InputReader ---

InputReader::InputReader(const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& policy, const sp<InputListenerInterface>& listener) {
    // 实例化QueuedInputListener
    mQueuedListener = new QueuedInputListener(listener);
}

可以看出,getListener()的返回值是mQueuedListener,返回值类型是InputListenerInterface,它在InputReader的构造方法中创建,并包装了InputReader构造方法的第三个参数listener,这个listener又是什么呢?让我们回去找到实例化InputReader的地方:

// /frameworks/native/services/inputflinger/InputManager.cpp

InputManager::InputManager(...) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    // InputDispatcher实例与EventHub实例传递给InputReader
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

原来如此,InputDispatcher实例就是这第三个参数listener,我们查看InputDispatcherInputListenerInterface的定义:InputDispatcher.hInputListener.h

// /frameworks/native/services/inputflinger/InputDispatcher.h

class InputDispatcher : public InputDispatcherInterface {
    // ...
}

class InputDispatcherInterface : public virtual RefBase, public InputListenerInterface {
    //...
}
// /frameworks/native/services/inputflinger/InputListener.h

/**
 * 基类
 **/
struct NotifyArgs {
    virtual ~NotifyArgs() { }
    virtual void notify(const sp<InputListenerInterface>& listener) const = 0;
};

/**
 * 触点事件
 **/
struct NotifyMotionArgs : public NotifyArgs {
    // ...
}

/**
 * 按键事件
 **/
struct NotifyKeyArgs : public NotifyArgs {
    // ...
}

class InputListenerInterface : public virtual RefBase {/**弱指针**/
    // ...
public:
    virtual void notifyKey(const NotifyKeyArgs* args) = 0;
    virtual void notifyMotion(const NotifyMotionArgs* args) = 0;
        
    // ...  
};

class QueuedInputListener : public InputListenerInterface {
    // ...
public:
    explicit QueuedInputListener(const sp<InputListenerInterface>& innerListener);
        
    virtual void notifyKey(const NotifyKeyArgs* args);
    virtual void notifyMotion(const NotifyMotionArgs* args);
        
    void flush();
        
    // ...

private:
    sp<InputListenerInterface> mInnerListener;
    Vector<NotifyArgs*> mArgsQueue;
};

可以看到,QueuedInputListenerInputDispatcher都是实现了InputListenerInterface,前者其实就是后者的包装类。

InputDispatcher UML类图

我们看看包装类QueuedInputListener都做了什么,简化代码如下InputListener.cpp

// /frameworks/native/services/inputflinger/InputListener.cpp

void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
    // push到队列中
    mArgsQueue.push(new NotifyKeyArgs(*args));
}

void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
    // push到队列中
    mArgsQueue.push(new NotifyMotionArgs(*args));
}

void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyMotion(this);
}

void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyKey(this);
}

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    // 遍历每个NotifyArgs
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        // mInnerListener就是InputDispatcher
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}

可以看到,之前的getListener()->notifyMotion()或者getListener()->notifyKey()都是把事件pushmArgsQueue队列中。那么队列中的这些事件到底什么时候才被处理呢?还记得InputReader#loopOnce()的最后一步吗?

// /frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::loopOnce() {
    // ...
    // 从EventHub中读取事件,存储在指针mEventBuffer中
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    if (count) {
        // 处理读取到的事件
        processEventsLocked(mEventBuffer, count);
    }
    // ...
    mQueuedListener->flush();
}

原来如此,所有事件被pushmArgsQueue队列之后,会调用mQueuedListener->flush(),最终会调用到InputDispatcher#notifyKey()InputDispatcher#notifyMotion(),归纳时序图如下:

事件采集 UML时序图 - 02

继续查看InputDispatcher#notifyKey()InputDispatcher#notifyMotion()的简化代码:

// /frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    // ...
        
    // 实例化KeyEntry
    KeyEntry* newEntry = new KeyEntry(...);
    // 加入事件队列
    enqueueInboundEventLocked(newEntry);
    // 唤醒在mLooper上等待的线程
    mLooper->wake();
}

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
    // ...
        
    // 实例化MotionEntry
    MotionEntry* newEntry = new MotionEntry(...);
    // 加入事件队列
    enqueueInboundEventLocked(newEntry);
    // 唤醒在mLooper上等待的线程
    mLooper->wake();
}

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    // 加入队列尾部
    mInboundQueue.enqueueAtTail(entry);
    // ...
}

可以看到,InputDispatcher#notifyKey()InputDispatcher#notifyMotion()基本类似,都是将事件KeyEntryMotionEntry加入队列mInboundQueue尾部,随后唤醒在mLooperLooper.h)上等待的线程。

Timethreads图 - 03

记得我们熟知的生产者-消费者模型吗?

到这里,我们就完整地在InputReaderThread线程中执行了一次loopOnce(),总结一下关键点:

  • InputReaderEventHub中读取事件,其中一部分是输入事件,我们关心的是按键事件触点事件
  • 事件最终被加入mInboundQueue队列,随即唤醒在mLooper上等待的线程

那么,唤醒的是哪个线程呢?会不会就是InputDispatcherThread线程呢?相关代码如下:

// /frameworks/native/services/inputflinger/InputDispatcher.cpp

// --- InputDispatcherThread ---

InputDispatcherThread::InputDispatcherThread(...) {
    bool InputDispatcherThread::threadLoop() {
        mDispatcher->dispatchOnce();
        return true;
    }
}
// --- InputDispatcher ---

InputDispatcher::InputDispatcher(...){
    mLooper = new Looper(false);
}

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    // ...
    // 分发一次事件
    dispatchOnceInnerLocked(&nextWakeupTime);
    // ...
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    // 在mLooper上等待一段时间
    mLooper->pollOnce(timeoutMillis);
}

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // ...
    // 获取mInboundQueue队列头部的事件
    mPendingEvent = mInboundQueue.dequeueAtHead();
        
    //...
        
    switch (mPendingEvent->type) {
        // ...
        case EventEntry::TYPE_KEY: {
                // ...
                // 分发按键事件
                done = dispatchKeyLocked(...);
                break;
        }
        case EventEntry::TYPE_MOTION: {
                // ...
                // 分发触点事件
                done = dispatchMotionLocked(...);
                break;
        }
    }
    // ...
}

可以看出,InputDispatcherThread线程中死循环运行dispatchOnce(),每一次都从mInboundQueue队列中获取头部的事件,并执行一次事件分发。每分发一次事件后都会调用mLooper->pollOnce()等待一段时间。等待的过程中,可能被InputReaderThread线程中执行的mLooper.wake()提前唤醒,从而触发下一次事件分发。


到这里,我们就完整分析了InputManagerService的事件采集流程,下一篇文章我们将讨论事件分发过程,欢迎关注彭旭锐的简书!


延伸阅读

  • [Android | 系统启动过程]
  • [Android | InputManagerService与输入事件分发]

推荐阅读


感谢喜欢!你的点赞是对我最大的鼓励!欢迎关注彭旭锐的简书!

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

推荐阅读更多精彩内容