Android Input子系统

本文转载自:

1.Android输入系统

  说到屏幕点击事件,大部分同学最早想到的就是自定义中点击事件的处理过程,屏幕滑动Down事件Up事件等。自定义View的事件处理其实在整个Android输入系统中只能算是最上层的。 输入系统如果按结构分层可以分为:

input01.PNG
  • 输入系统部分

  包含输入子系统以及InputManagerService,用于事件的捕获以及分发给上一级。

  • WindowManagerService处理部分

  输入系统部分将事件分发给对应的Window,而Window正是由WMS来管理的。

  • View处理部分

  就是Activity->ViewGroup->View层面的事件分发的逻辑。

  这里再提供一张更加详细的Android输入系统模型:

input02.PNG

  输入系统说白了就是捕获事件,并将事件分发给WMS进行处理。关键字:事件捕获,事件分发。

1.1 输入子系统

  Android中的输入设备有很多种,如:键盘屏幕鼠标等,开发中最常见的就是屏幕和按键(如Home键等属于键盘)了。

  这些设备对于核心处理器来说就是一个“即插即用”外设,和我们电脑插入鼠标或手机后,在“设备管理器”里面会新增一个输入设备节点一样(前提是已经安装好对应的驱动) 。

input03.png

  Android处理器在接入这些“外设”后,比如滑动屏幕,设备驱动层就会接受到原始事件最终将事件传递到用户空间的设备节点(dev/input/)中。Android提供了一些api可以让开发者在设备节点(dev/input/)中读取内核写入的事件。

1.2 InputManagerService

  InputManagerService(输入管理服务)简称IMS,在安卓系统中负责它管理整个系统的输入部分,包括键盘、鼠标、触摸屏等等,它与WindowManager密切相关。它读取设备节点(dev/input/)中的输入事件,并对数据进行二次甚至三次加工后分发给合适的Window进行处理。IMS整体启动过程和重要方法如下图所示。

input04.png

2.事件读取机制

  我们以IMS为入口。在设备开机过程中,启动SystemServer部分初始了很多系统服务,这里面就包括了IMS的创建和启动过程。 注:文章使用的源码版本为8.0。以上就是整个输入事件读取的过程:

input05.png

2.1 IMS的创建与启动

  在SystemServer的run方法中调用了startOtherServices。

//frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
    ...
    inputManager = new InputManagerService(context);//1.创建IMS
    ...
    wm = WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore, new PhoneWindowManager());//2.IMS传入WMS
    ...
    inputManager.start();//3.启动IMS
}

注释1:调用了IMS的构造方法;
注释2:将IMS作为参数传递给WMS;
注释3:调用了IMS的启动方法start。

  我们来具体分析下注释1和注释3:注释1

public InputManagerService(Context context) {
    ...
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
    ...
}

重点看nativeInit方法,这是一个native方法。

//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* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);//返回NativeInputManager指针到java framework层
}
//------------------------------------
NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();

    mContextObj = env->NewGlobalRef(contextObj);//1.将java层的传下来的Context上下文保存在mContextObj中
    mServiceObj = env->NewGlobalRef(serviceObj);//2.将java层传递下来的InputManagerService对象保存在mServiceObj中
    ...
    sp<EventHub> eventHub = new EventHub();//3.创建EventHub
    mInputManager = new InputManager(eventHub, this, this);//4.创建InputManager
}

nativeInit方法中创建了一个NativeInputManager对象,并将该对象指针返回给了java framework层。 这就是为了打通java和native层,下次需要使用native层的NativeInputManager对象的时候,直接传递这个指针就可以访问了。

  继续看NativeInputManager构造方法:

  • 注释1:将java层的传下来的Context上下文保存在mContextObj中;
  • 注释2:将java层传递下来的InputManagerService对象保存在mServiceObj中。

  如果你对源码比较熟悉,可以知道大部分native层和java层的交互都是通过这个模式,调用模型图如下:

input06.png

NativeInputManager构造方法注释3处:创建一个EventHub,EventHub通过Linux内核的INotify和epoll机制监听设备节点,使用它的getEvent函数就可以读取设备节点的原始事件以及设备节点增删事件。 这里说明下原始事件和设备增删事件:

  • 原始事件:比如键盘节点的输入事件或者屏幕的触屏DOWN或者UP事件等,都属于原始事件。

  • 设备增删事件:值键盘或者屏幕等节点的增删,EventHub在获取这类事件后,会在native层创建对应的节点处理Mapper对象。

NativeInputManager构造方法注释4处:创建一个InputManager对象并将注释3处的EventHub对象作为参数传入。 进入InputManager构造方法:

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);//1
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);//2
    initialize();//3
}
//----------------------------------------
void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

InputManager构造方法中:

  1. 创建InputDispatcher类对象mDispatcher,InputDispatcher类主要用来对原始事件进行分发,传递给WMS;

  2. 创建InputReader类对象mReader,并传入1中mDispatcher对象以及eventHub对象, 为什么要传入这2个对象呢?因为InputReader机制就是:eventHub对象用来读取事件数据,mDispatcher对象用来将读取到事件数据分发;

  3. initialize方法中创建了InputReaderThread对象和InputDispatcherThread对象 因为事件读取机制是一个耗时过程,不能在主线程中进行,所以使用InputReaderThread线程来读取事件,用InputDispatcherThread线程来分发事件。

  关于IMS的构造方法就讲了这么多,先来小结下:

  1. IMS构造方法中:创建了一个NativeInputManager的native对象,并将java层的Context上下文保存在native层的mContextObj,将java层的IMS对象保存在native层的mServiceObj中 创建InputManager对象并传入一个新建的EventHub对象,用于读取设备节点事件。

  2. InputManager构造方法中:创建了一个InputDispatcher和InputReader对象,以及用于读取事件的InputReaderThread线程和分发事件的InputDispatcherThread线程。

  下面我们继续看IMS的启动方法,在startOtherServices方法的调用inputManager.start。

//frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
    ...
    inputManager = new InputManagerService(context);//1.创建IMS
    ...
    wm = WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore, new PhoneWindowManager());//2.IMS传入WMS
    ...
    inputManager.start();//3.启动IMS
}

start方法中主要调用了nativeStart方法,参数为初始化时,native返回的NativeInputManager对象地址。

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }

}

nativeStart方法调用了NativeInputManager的InputManager的start方法。

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    ...
    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputReader thread due to error %d.", result);

        mDispatcherThread->requestExit();
        return result;
    }

    return OK;

}

start方法主要作用就是启动初始化时创建的两个线程:mDispatcherThread和mReaderThread,这里注意先后顺序先启动事件分发线程,再启动事件读取线程。这是为了在事件读取后可以立即对事件进行分发。

  IMS启动时序图如下:

input07.png

2.2 线程的创建与启动

  在分析两个线程启动过程之前,我们先来讲解下Thread的run方法。

//system\core\libutils\Threads.h
status_t Thread::run(const char* name, int32_t priority, size_t stack)
{
    ...
    res = createThreadEtc(_threadLoop,
                this, name, priority, stack, &mThread);
    ...
}

Thread的run方法中调用了createThreadEtc,这个方法第一个参数_threadLoop是一个方法指针,第二个参数是自己,最终会调用到_threadLoop方法并传入this指针。

int Thread::_threadLoop(void* user)
    Thread* const self = static_cast<Thread*>(user);
    ...
    do {
        bool result;
        result = self->threadLoop();
        ...
        if (result == false || self->mExitPending) {
            ...
            break;
        }
        ...
    } while(strong != 0);
}

_threadLoop方法内部会调用self->threadLoop(),这个self就是当前Thread的this指针,除了result返回false或者调用了requestExit才会退出。 不然会一直循环调用self->threadLoop()函数,threadLoop在Thread中是一个纯虚函数,在其子类中实现。

2.2.1 InputDispatcher/InputDispatcherThread

  所以后面只要分析子线程的threadLoop方法即可,下面我们先来分析InputDispatcherThread:

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

这里的mDispatcher是InputDispatcher类对象。

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        ..
        if (!haveCommandsLocked()) {//1.判断有没有命令需要执行
            dispatchOnceInnerLocked(&nextWakeupTime);//2.分发事件
        }

        if (runCommandsLockedInterruptible()) {//3.执行命令
            nextWakeupTime = LONG_LONG_MIN;//4.设置下次唤醒的时间
        }
    } // release lock
    ...
    mLooper->pollOnce(timeoutMillis);//5.让线程进入休眠

}

注释1:判断有没有命令需要执行,如果没有命令,则调用注释2的dispatchOnceInnerLocked分发事件;如果有命令,则在注释3处执行完所有命令后,将nextWakeupTime置为LONG_LONG_MIN,这样就可以让下一次线程可以被立即唤醒。 注释5处调用mLooper->pollOnce,让线程进入休眠。第一次启动的时候是直接进入休眠,等待事件的到来。

2.2.2 InputReader/InputReaderThread

  下面再来分析事件读取线程InputReaderThread:

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

这里mReader是InputReader类对象:

void InputReader::loopOnce() {
    ...
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);//1.获取事件

    { // acquire lock
        ...
        if (count) {
            processEventsLocked(mEventBuffer, count);//2.处理事件
        }
        ...
    } // release lock

    ...
    mQueuedListener->flush();//3

}

2.3 事件的获取与赋值

  • 注释1处调用EventHub的getEvents方法读取设备节点中的输入事件,注意这个方法内部在没有输入事件的时候也是一个休眠的过程,并不是死循环耗时操作。

  • 在注释2处如果count不为0,说明有事件,调用processEventsLocked方法。

2.4 事件的处理与加工

  进入processEventsLocked看看:

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.原始事件类型
            ...
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {//2.设备节点事件
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);//添加设备
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);//删除设备
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);//设备配置改变
                break;
            default:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        count -= batchSize;
    }
}

processEventsLocked方法主要实现了:根据事件的type类型进行不同处理。

  • 原始事件:调用processEventsForDeviceLocked处理。

  • 设备节点事件:调用节点的添加和删除等操作。

  我们先来看原始事件:processEventsForDeviceLocked

void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);//获取设备在mDevices中的索引
    ...
    InputDevice* device = mDevices.valueAt(deviceIndex);//根据索引获取InputDevice对象
    ...
    device->process(rawEvents, count);//调用InputDevice的process方法继续处理
}

根据deviceId去获取设备在mDevices中的索引,根据索引获取InputDevice对象。然后调用InputDevice的process方法继续处理。

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    ...
    size_t numMappers = mMappers.size();
    for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
        ...
            ...
            for (size_t i = 0; i < numMappers; i++) {
                InputMapper* mapper = mMappers[i];
                mapper->process(rawEvent);
            }
            ...
        ... 
    }   
}

process方法最终是使用不同的InputMapper进行处理, 那这个InputMapper在哪里设置成员呢。

  我们回到前面processEventsLocked方法:如果是节点处理事件,如添加则调用addDeviceLocked方法, addDeviceLocked方法中又调用了createDeviceLocked方法,并将返回的InputDevice放入到mDevices列表中。

void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
    ...
    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
    ...
    mDevices.add(deviceId, device);
    ...
}
//--------------------------------------
InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
        const InputDeviceIdentifier& identifier, uint32_t classes) {
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
            controllerNumber, identifier, classes);

    ...
    // Switch-like devices.
    if (classes & INPUT_DEVICE_CLASS_SWITCH) {
        device->addMapper(new SwitchInputMapper(device));
    }

    // Scroll wheel-like devices.
    if (classes & INPUT_DEVICE_CLASS_ROTARY_ENCODER) {
        device->addMapper(new RotaryEncoderInputMapper(device));
    }
    ...
    // Keyboard-like devices.
    ...
    if (keyboardSource != 0) {
        device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
    }
    ...
    // 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));
    }
    ...
    return device;

}

可以看到createDeviceLocked根据不同输入类型给Device设备添加了不同的InputMapper。

  回到前面InputDevice::process方法中:这里用KeyboardInputMapper来做例子。 这个方法调用了KeyboardInputMapper的process方法。

void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
        case EV_KEY: {
            ..
            if (isKeyboardOrGamepadKey(scanCode)) {
                processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
            }
            break;
        }
    }
}
//--------------------------------------
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
        int32_t usageCode) {

    ...
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
    getListener()->notifyKey(&args);

}

最终在processKey方法中将原始事件封装为一个新的NotifyKeyArgs对象,并调用 getListener()->notifyKey方法作为参数传入,这里getListener是在InputReader构造方法中初始化。

InputReader::InputReader(const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& policy,
        const sp<InputListenerInterface>& listener) :
        mContext(this), mEventHub(eventHub), mPolicy(policy),
        mGlobalMetaState(0), mGeneration(1),
        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
        mConfigurationChangesToRefresh(0) {
    mQueuedListener = new QueuedInputListener(listener);
    ...
}

就是这个mQueuedListener,而这个listener是外部传入的InputDispatcher对象。

  那进入QueuedInputListener的notifyKey看看:

void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
    mArgsQueue.push(new NotifyKeyArgs(*args));
}

这里只是将NotifyKeyArgs事件对象存储到mArgsQueue队列中,并没有真正对事件进行处理,那什么时候处理呢? 观察仔细的同学应该看到在InputReader的loopOnce中调用了mQueuedListener->flush()。

void InputReader::loopOnce() {
    ...
    mQueuedListener->flush();//3
}
//---------------------------------
void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}

这个flush方法就是循环调用列表中的事件,并依次执行NotifyArgs的notify方法传入的参数mInnerListener为初始化时的InputDispatcher对象。

2.5 事件的分发与通知

  继续进入NotifyArgs的notify方法:

struct NotifyArgs {
    virtual void notify(const sp<InputListenerInterface>& listener) const = 0;
}

notify方法是一个纯虚函数,由其子类实现。这里子类就是NotifyKeyArgs对象。

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

这里listener是InputDispatcher的对象。

2.5.1 InputDispatcher/InputDispatcherThread

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    ...
    bool needWake;
    { // acquire lock
        ...
        KeyEntry* newEntry = new KeyEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);//1.封装成KeyEntry

        needWake = enqueueInboundEventLocked(newEntry); //2.将KeyEntry对象压入mInboundQueue中
    } // release lock

    if (needWake) {
        mLooper->wake();//3.唤醒InputDispatcherThread线程进行处理
    }

}
  • 注释1处:将NotifyKeyArgs事件重新封装为一个KeyEntry对象。

  • 注释2处:enqueueInboundEventLocked将KeyEntry对象压入mInboundQueue中。

  • 注释3处:唤醒InputDispatcherThread线程进行处理。

  到这里InputReader的输入事件读取流程已经全部走完。

  事件读取时序图如下:

input08.png

2.6 事件获取流程小结

  1. SystemServer创建并启动InputManagerService;

  2. InputManagerService在native层创建一个NativeInputManager对象;

  3. NativeInputManager内部创建一个InputManager对象;

  4. InputManager启动InputReaderThread和InputDispatcherThread;

  5. 在InputReaderThread线程中调用EventHub的getEvents获取设备节点中的输入事件;

  6. 并将输入事件封装为NotifyKeyArgs对象放入队列中;

  7. 之后再调用flush,依次将事件传递给InputDispatcher;

  8. InputDispatcher在收到事件后,会重新封装为一个KeyEntry对象,并压入mInboundQueue列表中;

  9. 最后唤醒InputDispatcherThread线程。

3.事件分发机制

  为了让大家不会迷失在源码中,笔者先抛出几个问题,然后带着问题去看源码。

  • 问题1:窗口什么时候传入IMS输入系统的?IMS是如何找到对应的Window窗口的?
  • 问题2:IMS和WMS是通过什么方式通讯将事件分发出去的?

  这里再提前放一张图让大家对输入事件模型有个概念,然后再结合源码去分析。

input09.png

3.1 事件分发入口确定

  前面讲到事件读取流程将事件放入封装为KeyEntry放入到mInboundQueue队列的尾部tail,并唤醒InputDispatcherThread线程,就以这里为入口。

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    ...
    bool needWake;
    { // acquire lock
        ...
        KeyEntry* newEntry = new KeyEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);//1.封装成KeyEntry

        needWake = enqueueInboundEventLocked(newEntry); //2.将KeyEntry对象压入mInboundQueue中
    } // release lock

    if (needWake) {
        mLooper->wake();//3.唤醒InputDispatcherThread线程进行处理
    }

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

3.2 事件分发线程唤醒

  进入InputDispatcherThread的threadLoop方法: 关于Thread的threadLoop方法解释已经在捕捉输入事件时讲过,这里不再重复。

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();//mDispatcher 是InputDispatcher类型对象
    return true;
}
//-----------------------------------------
void InputDispatcher::dispatchOnce() {  
    ...
    if (!haveCommandsLocked()) {//1.判断是否有指令需要执行
        dispatchOnceInnerLocked(&nextWakeupTime);//处理输入事件
    }
    ...
    if (runCommandsLockedInterruptible()) {//2.执行命令
        nextWakeupTime = LONG_LONG_MIN;
    }
    mLooper->pollOnce(timeoutMillis);
}

注释1处判断是否有指令需要执行,如果没有,则调用dispatchOnceInnerLocked去处理输入事件,如果有,则优先调用runCommandsLockedInterruptible处理事件, 为了让线程可以立即进入事件处理,将nextWakeupTime 设置为LONG_LONG_MIN,这样线程在指令执行完毕后可以立即被唤醒去处理输入事件。

  从这里可以看出dispatchOnce主要是做了两个功能:1.执行指令 2.处理输入事件,且指令执行优先级高于输入事件处理。 这里的指令例如:对waitQueue中的事件进行出栈,后面会讲到。

  进入注释2输入事件处理:dispatchOnceInnerLocked

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    switch (mPendingEvent->type) {
    ...
    case EventEntry::TYPE_KEY: {// 按键事件类型
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
        ...
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }

    case EventEntry::TYPE_MOTION: {// 移动事件类型
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
        ...
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }
    if (done) {
        ...
        releasePendingEventLocked(); // 事件处理分发完毕后,用于释放对应的资源
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }

}

dispatchOnceInnerLocked方法主要是根据事件的类型调用不同的处理方法。 我们拿TYPE_KEY 按键事件来作为主线,关于TYPE_MOTION触摸事件处理逻辑都是差不过的,感兴趣的同学可以自行阅读源码。 在事件处理分发完毕后会调用releasePendingEventLocked里面会释放对应的内存资源

  mPendingEvent代表当前需要处理的输入事件,传递给dispatchKeyLocked去处理。

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ...
    Vector<InputTarget> inputTargets;
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime); //1.找到焦点窗口
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false;
    }
    ...
    addMonitoringTargetsLocked(inputTargets);//2.放入一个监听input通道

    // Dispatch the key.
    dispatchEventLocked(currentTime, entry, inputTargets);  //3.实际处理事件 处

}

dispatchKeyLocked在注释1处获取按键事件对应的Window窗口,在注释2处放入一个监听input通道,注释3处实际处理事件处。

  在讲解注释1处获取窗口逻辑前我们先来看下我们窗口是如何传入到InputDispatcher对象中的以及InputChannel概念。

3.3 事件分发通道注册

  我们Window是在ViewRootImpl的setView方法中传入WMS的。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    mInputChannel = new InputChannel();//创建通道
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
    if (mInputChannel != null) {
        mInputEventReceiver = new       WindowInputEventReceiver(mInputChannel,Looper.myLooper());
    }
}

mWindowSession是IWindowSession在app端的代理对象。实际执行的是Session类:

//frameworks\base\services\core\java\com\android\server\wm\Session.java
public int addToDisplay(IWindow window,...InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

这里的mService是WMS对象,重点记住最后一个参数outInputChannel。

//WMS:
public int addWindow(Session session, IWindow client...InputChannel outInputChannel) {
    ...
    final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
    ...
    win.openInputChannel(outInputChannel);  //1.打开InputChannel通道
    if (focusChanged) {
        mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);//2
    }
}

addWindow的注释1处调用WindowState打开InputChannel通道,什么是InputChannel通道呢?

  进入WindowState的openInputChannel看看:

void openInputChannel(InputChannel outInputChannel) {
    ...
    String name = getName();
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//1
    mInputChannel = inputChannels[0];//socket服务端的InputChannel
    mClientChannel = inputChannels[1];//socket客户端的InputChannel
    mInputWindowHandle.inputChannel = inputChannels[0];
    if (outInputChannel != null) {
        mClientChannel.transferTo(outInputChannel);//将
        mClientChannel.dispose();
        mClientChannel = null;
    }
    ...
    mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);//2
}
//--------------------------------------------------
//InputChannel.java:
public static InputChannel[] openInputChannelPair(String name) {
    ...
    return nativeOpenInputChannelPair(name);
}
//--------------------------------------------------
//android_view_InputChannel.cpp:
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env...) {
    ...
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);

    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            std::make_unique<NativeInputChannel>(serverChannel));
    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            std::make_unique<NativeInputChannel>(clientChannel));
    ...
    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    return channelPair;

}
//--------------------------------------------------
status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        ...
        return result;
    }
    ...
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

通过以上代码可以看出InputChannel使用的是sockets通讯,且WindowState的openInputChannel中注释1处:InputChannel[] inputChannels = InputChannel.openInputChannelPair(name),返回的inputChannels是一个服务端和客户端的输入通道数组,其中: 下标0:表示服务端的InputChannel;下标1:表示客户端的InputChannel。

  有了这些基础我们再来细细品味下这段代码:为什么registerInputChannel传递的是mInputChannel(socket服务端)而不是mClientChannel(socket客户端)。

void openInputChannel(InputChannel outInputChannel) {
    ...
    String name = getName();
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//1
    mInputChannel = inputChannels[0];//socket服务端的InputChannel
    mClientChannel = inputChannels[1];//socket客户端的InputChannel
    mInputWindowHandle.inputChannel = inputChannels[0];
    if (outInputChannel != null) {
        // app的outInputChannel和socket的客户端mClientChannel关联
        mClientChannel.transferTo(outInputChannel);//outInputChannel:app层传递进来的InputChannel
        mClientChannel.dispose();
        mClientChannel = null;
    }
    ...
    mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);//2
}

通过前面分析知:

  • mInputChannel:socket服务端的InputChannel;

  • mClientChannel:socket客户端的InputChannel;

  • outInputChannel:app层传递进来的InputChannel。

3.3.1 InputChannel创建流程

3.3.2 Channel模型

  1. 调用mService.mInputManager.registerInputChannel

  将wms在socket服务端的InputChannel(mInputChannel)注册到IMS中。这样在IMS输入系统就可以给socket服务端的InputChannel(mInputChannel)写入数据,在WMS的socket客户端InputChannel(mClientChannel)就可以读取数据。

  1. 调用mClientChannel.transferTo(outInputChannel)

  将app端的InputChannel(outInputChannel)和wms的socket客户端InputChannel(mClientChannel)关联,这样就可以向socket客户端InputChannel(mClientChannel)中写入数据然后通知app端的InputChannel(outInputChannel),实际传递给ViewRootImpl对象处理,接着就是View层面的处理了。

  这里我们继续看注释2处:mService.mInputManager.registerInputChannel 最终会进入native层:

//frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    ...
    status_t status = im->registerInputChannel(
            env, inputChannel, inputWindowHandle, monitor);//1.socket的服务端

}
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
        const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
    return mInputManager->getDispatcher()->registerInputChannel(
            inputChannel, inputWindowHandle, monitor);
}
//----------------------------------------------
//frameworks/native/services/inputflinger/InputDispatcher.cpp
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
        ...
        sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);

        int fd = inputChannel->getFd();
        mConnectionsByFd.add(fd, connection);
        ...

        // registerInputChannel里面传入的monitor是false --> nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
        // 所以这个流程不会将窗口的channel放到mMonitoringChannels里面
        if (monitor) {
            mMonitoringChannels.push(inputChannel);
        }
        ...

}

im->registerInputChannel参数说明:

  • inputChannel:WMS在socket服务端InputChannel(mInputChannel);

  • inputWindowHandle:WMS内的一个包含Window所有信息的实例;

  • monitor:值为false,表示不加入监控。

  InputChannel时序图如下:

input10.png

  最后阶段在InputDispatcher中创建一个Connection并加入到mConnectionsByFd队列中,key为当前inputChannel的fd。获取的时候也是通过inputChannel的fd去获取。

input11.png

通过上面的分析我们知道了,和输入系统InputDispatcher使用的socket通讯,在View端,WMS端以及IMS端都有一个InputChannel。

  • View端:outInputChannel(和mClientChannel绑定一起);

  • WMS端:mClientChannel(socket客户端);

  • IMS端:mInputChannel(socket服务端)。

InputDispatcher -> mInputChannel -> mClientChannel -> outInputChannel -> view

  哪个部分有数据需要分发就是将数据写入通道中。 那么接下来我们看看IMS是如何选取对应的通道的。

3.4 事件分发窗口确认

  回到InputDispatcher::dispatchKeyLocked方法。

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ..
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime); //1.找到焦点窗口

    dispatchEventLocked(currentTime, entry, inputTargets);  //2.处理分发事件

}

进入findFocusedWindowTargetsLocked:这个方法就是用来确认当前需要传递事件的窗口。 重点看inputTargets的赋值操作。

int32_t InputDispatcher::findFocusedWindowTargetsLocked(...inputTargets){
    ...
    addWindowTargetLocked(mFocusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
            inputTargets);
    ...
}
//----------------------------------------------
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
        int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
    inputTargets.push();

    const InputWindowInfo* windowInfo = windowHandle->getInfo();
    InputTarget& target = inputTargets.editTop();
    target.inputChannel = windowInfo->inputChannel;
    target.flags = targetFlags;
    target.xOffset = - windowInfo->frameLeft;
    target.yOffset = - windowInfo->frameTop;
    target.scaleFactor = windowInfo->scaleFactor;
    target.pointerIds = pointerIds;

}

这里可以看出findFocusedWindowTargetsLocked方法中对inputTargets的头部数据进行了赋值,其中将windowInfo->inputChannel通道赋值给了target.inputChannel。 那么这个windowInfo是个什么?怎么获取?

  windowInfo是InputWindowHandle的属性,而InputWindowHandle传入的是一个mFocusedWindowHandle对象。 从名字也可以大概看出这是一个包含焦点Window信息的对象。

  那么这个焦点Window是在哪里赋值的呢?

3.5 事件分发窗口注册

  我们回到WMS的addWindow步骤。

// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
    ...
    if (focusChanged) {
        mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
    }
    mInputMonitor.updateInputWindowsLw(false /*force*/);
    ...
}

在焦点发生改变的时候会调用setInputFocusLw方法和updateInputWindowsLw,updateInputWindowsLw经过层层调用最终会走到InputDispatcher::setInputWindows中。

// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
    ...
    mWindowHandles = inputWindowHandles;

    sp<InputWindowHandle> newFocusedWindowHandle;
    ...
    for (size_t i = 0; i < mWindowHandles.size(); i++) {
        const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
        ...
        if (windowHandle->getInfo()->hasFocus) {
            newFocusedWindowHandle = windowHandle;
        }
        ...
        mFocusedWindowHandle = newFocusedWindowHandle;
    }
    ...

}

看到了这里对mWindowHandles和mFocusedWindowHandle做了赋值。

  • mWindowHandles:代表所有Window的Handler对象;
  • mFocusedWindowHandle:表示焦点Window的Handler对象,通过这些代码就让我们IMS中获取到了需要处理的焦点Window。

  window窗口赋值时序图如下:

input12.png

3.6 事件分发最终处理

  继续回到回到InputDispatcher::dispatchKeyLocked方法的注释2。

dispatchEventLocked(currentTime, entry, inputTargets); //2
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    ...
    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);

        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        }
        ...
    }

}
//-----------------------------------------------------
ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
    ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd());
    if (connectionIndex >= 0) {
        sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
        if (connection->inputChannel.get() == inputChannel.get()) {
            return connectionIndex;
        }
    }

    return -1;

}

dispatchEventLocked主要作用:轮询inputTargets,根据inputTarget.inputChannel获取其在mConnectionsByFd中的索引,根据索引获取Connection对象,并调用prepareDispatchCycleLocked进行处理。

  prepareDispatchCycleLocked方法内部调用了enqueueDispatchEntriesLocked方法:

void InputDispatcher::enqueueDispatchEntriesLocked(connection,..){
    // Enqueue dispatch entries for the requested modes.
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,...);//1
    ...
    // If the outbound queue was previously empty, start the dispatch cycle going.
    if (wasEmpty && !connection->outboundQueue.isEmpty()) {//2
        startDispatchCycleLocked(currentTime, connection);//3
    }
}
//--------------------------------------------
void InputDispatcher::enqueueDispatchEntryLocked(
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
        int32_t dispatchMode) {
    ...
    DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry,
            inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
            inputTarget->scaleFactor);

    switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
            KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
            dispatchEntry->resolvedAction = keyEntry->action;
            dispatchEntry->resolvedFlags = keyEntry->flags;
            ...
            break;
        }
        ...
    }
    ...
    connection->outboundQueue.enqueueAtTail(dispatchEntry);
    ...

}

在注释1处enqueueDispatchEntryLocked方法中会将输入事件重新封装为一个DispatchEntry并压入connection的outboundQueue队列中。 然后在注释2处判断如果事件不为空,则调用startDispatchCycleLocked循环发送输入事件。

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
    while (connection->status == Connection::STATUS_NORMAL
            && !connection->outboundQueue.isEmpty()) {
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;
        ...
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
            KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);

            // Publish the key event.
            status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
                    keyEntry->deviceId, keyEntry->source,
                    dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
                    keyEntry->keyCode, keyEntry->scanCode,
                    keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                    keyEntry->eventTime);
            break;
        }
        ...
        connection->outboundQueue.dequeue(dispatchEntry);
        connection->waitQueue.enqueueAtTail(dispatchEntry)
    }   
    ...

}

startDispatchCycleLocked方法中调用publishKeyEvent,其内部会将事件写入到WMS传递下来的InputChannel通道中。这样WMS端的InputChannel就可以通过socket获取到事件信息。在发送完毕后会将事件移出connection->outboundQueue队列,并放入到waitQueue等待队列中,等待事件处理完毕后再移出。

  waitQueue用来监听当前分发给WMS的输入事件是否已经被处理完毕。什么时候知道事件处理完毕呢?

  在InputDispatcher::registerInputChannel方法里面注册了handleReceiveCallback回调:

status_t InputDispatcher::registerInputChannel(...) {
        ...
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
        ...
}

当app层的事件处理完毕之后就会回调handleReceiveCallback:

int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
    InputDispatcher* d = static_cast<InputDispatcher*>(data);
    ...
    d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
    ...
    d->runCommandsLockedInterruptible();
    ...
}

这里会先调用InputDispatcher::finishDispatchCycleLocked去往mCommandQueue里面加入一个执行InputDispatcher:: doDispatchCycleFinishedLockedInterruptible的Command:


void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
        CommandEntry* commandEntry) {
    sp<Connection> connection = commandEntry->connection;
    ...
    DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
    ...
    if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
        connection->waitQueue.dequeue(dispatchEntry);
        ...
    }
}

doDispatchCycleFinishedLockedInterruptible中会将connection->waitQueue出栈,这样整个输入系统的分发过程就闭环了。

3.7 事件分发流程小结

  1. ViewRootImpl在setView方法会创建一个InputChannel通道,并在将Window添加给WMS的过程时,以参数传递给WMS。

  2. WMS在添加Window的过程中会调用updateInputWindows,这个方法最终会调用到InputDispatcher::setInputWindows中, 并给InputDispatcher的Window队列以及焦点Window赋值,这样IMS就可以找到对应的Window了。

  3. 在WMS在添加Window的过程中还会创建一个socketpair通道的InputChannel,其中客户端的socket与app层的InputChannel关联,用于WMS与app通讯;服务端的socket传递给IMS,用于IMS和WMS通讯。

  4. 客户端在接收到输入事件后,会根据当前焦点Window的的InputChannel找到对应的Connection连接,这个Connection用于与WMS进行通讯,内部其实就是使用前面的socket通讯。

  5. 事件分发后将输入事件放入到waitQueue中,等待事件处理完毕后,将事件移出waitQueue

3.8 问题复盘

  那么关于开头的两个问题:

  • 问题1:窗口什么时候传入IMS输入系统的?

  IMS又是如何找到对应的Window窗口的? ViewRootImpl在setView方法中,调用addToDisplay将Window传递给WMS的时候,这个时候会调用InputMonitor.updateInputWindowsLw方法,最终会调用到InputDispatcher::setInputWindows,这里面会对IMS的Window属性进行赋值。IMS根据前面赋值的Window属性,就可以找到对应的焦点Window。

  • 问题2:IMS和WMS是通过什么方式通讯将事件分发出去的?

  IMS和WMS是通过InputChannel通道进行通讯的,WMS在Window添加过程中会创建一个socket通道,将server端通道传递给IMS,而client端通道用于WMS中接收server端事件,server端根据对应的Window,找到对应的Connection,然后使用Connection进行通讯,而Connection内部就是通过socket进行通讯的。

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

推荐阅读更多精彩内容