Android Input(四) -InputDispatcher分发事件

原创内容,转载请注明出处,多谢配合。

上一篇分析了InputReader获取事件过程,最终InputReader将input event放到InputDispatcher的mInboundQueue后唤醒InputDispacher。本篇就来分析下InputDispatcher的事件分发过程。

一、InputDispatcher初始化
frameworks/native/services/inputflinger/InputManager.cpp

InputManager::InputManager(
       const sp<EventHubInterface>& eventHub,
       const sp<InputReaderPolicyInterface>& readerPolicy,
       const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
   mDispatcher = new InputDispatcher(dispatcherPolicy);//创建InputDispatcher
...
}

frameworks/native/services/inputflinger/InputDispatcher.cpp

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) ...  : {
   mLooper = new Looper(false);//这里InputDispatcher新建了一个Looper
   ...
}

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

bool InputDispatcherThread::threadLoop() {
   mDispatcher->dispatchOnce();//开始处理分发
   return true;
}
二、InputDispatcher运行
frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::dispatchOnce() {
   nsecs_t nextWakeupTime = LONG_LONG_MAX;register_android_server_InputManager
   ...
       //优先处理Command
       if (!haveCommandsLocked()) {
           //这里处理input event
           dispatchOnceInnerLocked(&nextWakeupTime);
       }
       if (runCommandsLockedInterruptible()) {
           // 处理完Command,立即唤醒loop来处理input event
           nextWakeupTime = LONG_LONG_MIN;
       }    
   ...
   nsecs_t currentTime = now();
   int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
   // 这里进入epoll_wait,
   // InputReader可以用Looper::wake()唤醒InputDispacher
   // 窗口也可以通过回执event唤醒Looper
   mLooper->pollOnce(timeoutMillis);
}

InputDispatcher处理的事件主要分两种:一种是command命令(Command是mPolicy处理的具体事务,这个mPolicy就是NativeInputManager,最终对应的上层是InputManagerService),一种是input event。每次dispatchOnce,会优先执行前者。

这里Command一些列回调命令保存在mCommandQueue中,主要包括:
doPokeUserActivityLockedInterruptible
doNotifyANRLockedInterruptible
doInterceptKeyBeforeDispatchingLockedInterruptible
doDispatchCycleFinishedLockedInterruptible
doNotifyInputChannelBrokenLockedInterruptible
doNotifyConfigurationChangedInterruptible

再看dispatchOnceInnerLocked处理input event部分:

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now(); 
 ...
   //当事件分发的时间点距离该事件加入mInboundQueue的时间超过500ms,则认为app切换过期,即  
//isAppSwitchDue=true;
   bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
  ...
           if (!mPendingEvent) {
                return;
            }
        } else {
           //事件是从InboundQueue头部取的
           mPendingEvent = mInboundQueue.dequeueAtHead();
            traceInboundQueueLengthLocked();
        }
        // Poke user activity for this event.
       if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            pokeUserActivityLocked(mPendingEvent);
        }
        // Get ready to dispatch the event.
       resetANRTimeoutsLocked(); //重置ANR 超时时间
    }
    // Now we have an event to dispatch.
   // All events are eventually dequeued and processed this way, even if we intend to drop them.
   ALOG_ASSERT(mPendingEvent != NULL);
    bool done = false;
    DropReason dropReason = DROP_REASON_NOT_DROPPED;
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
        dropReason = DROP_REASON_POLICY;
    } else if (!mDispatchEnabled) {
        dropReason = DROP_REASON_DISABLED;
    }
    if (mNextUnblockedEvent == mPendingEvent) {
        mNextUnblockedEvent = NULL;
    }
    switch (mPendingEvent->type) {
    …
   //这里有很多类型的EventEntry type 重点看看key
    case EventEntry::TYPE_KEY: {
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
        if (isAppSwitchDue) {
            if (isAppSwitchKeyEventLocked(typedEntry)) {
                resetPendingAppSwitchLocked(true);
                isAppSwitchDue = false;
            } else if (dropReason == DROP_REASON_NOT_DROPPED) {
                dropReason = DROP_REASON_APP_SWITCH;
            }
        }
        if (dropReason == DROP_REASON_NOT_DROPPED
                && isStaleEventLocked(currentTime, typedEntry)) {
            dropReason = DROP_REASON_STALE;
        }

        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
            dropReason = DROP_REASON_BLOCKED;
        }

        //分发事件处理
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }
    ...
    }
    //分发操作完成
    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);//丢弃事件
        }
        mLastDropReason = dropReason;
        releasePendingEventLocked();//释放当前正在处理的事件
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
   }
}

这个过程简单看就是从mInboundQueue头部取出事件交给dispatchKeyLocked执行分发处理,并重置ANR时间,以及执行完之后根据done来做一些收尾操作。

接下来看看dispatchKeyLocked:

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ...
    //寻找焦点窗口inputTargets
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime);
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false;
    }
    setInjectionResultLocked(entry, injectionResult);
    if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
        return true;
    }
    addMonitoringTargetsLocked(inputTargets);
    //只有injectionResult为true才会继续往下执行分发操作
   dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

这个方法核心内容是执行findFocusedWindowTargetsLocked寻找焦点窗口inputTargets,但是在此之前会有若干判断有可能直接return 。

int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
    int32_t injectionResult;
    String8 reason;
  ...
    // Check permissions.
   if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {
        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
        goto Failed;
    }
    // 检测窗口是否为更多的输入操作而准备就绪
   reason = checkWindowReadyForMoreInputLocked(currentTime,
            mFocusedWindowHandle, entry, "focused");
    if (!reason.isEmpty()) { //如果满足,handleTargetsNotReadyLocked会触发anr判断
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
        goto Unresponsive;
    }
    // Success!  Output targets.
   injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
    //添加目标窗口
    addWindowTargetLocked(mFocusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
            inputTargets);
    // Done.
Failed:
Unresponsive:
    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
    updateDispatchStatisticsLocked(currentTime, entry,
            injectionResult, timeSpentWaitingForApplication);
#if DEBUG_FOCUS
    ALOGD("findFocusedWindow finished: injectionResult=%d, "
           "timeSpentWaitingForApplication=%0.1fms",
            injectionResult, timeSpentWaitingForApplication / 1000000.0);
#endif
   return injectionResult;
}

这里主要就是添加目标窗口,与目前窗口建立InputChannel连接

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;
}

将当前聚焦窗口mFocusedWindowHandle的inputChannel传递到inputTargets。

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
#if DEBUG_DISPATCH_CYCLE
    ALOGD("dispatchEventToCurrentInputTargets");
#endif
   ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
   //调用到Java层的PowerManagerService.java中的userActivityFromNative()方法. 这也是PMS中唯一的native call方法
   pokeUserActivityLocked(eventEntry);
    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);
       //根据inputChannel的fd从mConnectionsByFd队列中查询目标connection.
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            //找到目标连接
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        } else {
#if DEBUG_FOCUS
            ALOGD("Dropping event delivery to target with channel '%s' because it "
                   "is no longer registered with the input dispatcher.",
                    inputTarget.inputChannel->getName().string());
#endif
       }
    }
}

该方法主要功能是将eventEntry发送到目标inputTargets。

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
       const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
 ...
   enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
   bool wasEmpty = connection->outboundQueue.isEmpty();
   ...
   enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_DISPATCH_AS_IS);
   ...
   if (wasEmpty && !connection->outboundQueue.isEmpty()) {
       startDispatchCycleLocked(currentTime, connection);
   }
}

void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,  int32_t dispatchMode) {
   int32_t inputTargetFlags = inputTarget->flags;
   if (!(inputTargetFlags & dispatchMode)) {
       return;
   }
   //这里又转了一次数据结构,生成新的事件, 加入connection的outbound队列。
   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;
   }

   //添加到outboundQueue队尾
   connection->outboundQueue.enqueueAtTail(dispatchEntry);
}

执行到这里,其实等于由做了一次搬运的工作,将InputDispatcher中mInboundQueue中的eventEntry事件取出后, 找到目标window后,将eventEntry封装dispatchEntry加入到connection的outbound队列。

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) {
   while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.isEmpty()) {
       DispatchEntry* dispatchEntry = connection->outboundQueue.head;
       dispatchEntry->deliveryTime = currentTime;
       // Publish the event.
       status_t status;
       EventEntry* eventEntry = dispatchEntry->eventEntry;
       switch (eventEntry->type) {
       case EventEntry::TYPE_KEY: {
           KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
           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;
       }
       ...
       }
       // Re-enqueue the event on the wait queue.
       connection->outboundQueue.dequeue(dispatchEntry);
       connection->waitQueue.enqueueAtTail(dispatchEntry);
    }

通过InputPublisher将DispatchEntry发送给窗口,再将DispatchEntry从outboundQueue移到waitQueue里。该通信过程是异步的,当窗口处理完事件后会通过handleReceiveCallback()回调函数通知InputDispatcher。

status_t InputPublisher::publishKeyEvent(...) {
   if (!seq) {
       return BAD_VALUE;
   }
   InputMessage msg;
   msg.header.type = InputMessage::TYPE_KEY;
   msg.body.key.seq = seq;
   msg.body.key.deviceId = deviceId;
   msg.body.key.source = source;
   msg.body.key.action = action;
   msg.body.key.flags = flags;
   msg.body.key.keyCode = keyCode;
   msg.body.key.scanCode = scanCode;
   msg.body.key.metaState = metaState;
   msg.body.key.repeatCount = repeatCount;
   msg.body.key.downTime = downTime;
   msg.body.key.eventTime = eventTime;
   //通过InputChannel来发送消息
   return mChannel->sendMessage(&msg);
}

publishKeyEvent调用InputChanel的SendMessage(),SendMessage()再动用socket的send()函数,将打包好的Message发送给窗口。

最后看看handleReceiveCallback回调:

frameworks/native/services/inputflinger/InputDispatcher.cpp
int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
    InputDispatcher* d = static_cast<InputDispatcher*>(data); 
    ssize_t connectionIndex = d->mConnectionsByFd.indexOfKey(fd); 
    sp<Connection> connection = d->mConnectionsByFd.valueAt(connectionIndex);
    if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
        nsecs_t currentTime = now();
        bool gotOne = false;
        status_t status;
        for (;;) {
            uint32_t seq;
            bool handled;
            //接收窗口处理完成的消息
            status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
            if (status) { // 读完socket即返回WOULD_BLOCK
                break;
            }
            // 这里post一个command
            d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
            gotOne = true;
        }
        if (gotOne) {
            // 这里执行command的handle
            d->runCommandsLockedInterruptible();
            if (status == WOULD_BLOCK) {  // 正常流程走这里
                return 1;
            }
        }
    }
    // 这里是socket链接出现异常的情况
    d->unregisterInputChannelLocked(connection->inputChannel, notify);
    return 0; // remove the callback
}

再看看post command

frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled) {
   ...
   onDispatchCycleFinishedLocked(currentTime, connection, seq, handled);
}
void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled) {
   CommandEntry* commandEntry = postCommandLocked( & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
   commandEntry->connection = connection;
   commandEntry->eventTime = currentTime;
   commandEntry->seq = seq;
   commandEntry->handled = handled;
}

frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) {
   sp<Connection> connection = commandEntry->connection;
   nsecs_t finishTime = commandEntry->eventTime;
   uint32_t seq = commandEntry->seq;
   bool handled = commandEntry->handled;
  // Handle post-event policy actions.
   DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
   if (dispatchEntry) {
       nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
      // 如果一个dispatch周期超过2秒,将打印警告信息
       if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT /* 2sec */) {
           String8 msg;
           msg.appendFormat("Window '%s' spent %0.1fms processing the last input event: ", connection->getWindowName(), eventDuration * 0.000001f);
           dispatchEntry->eventEntry->appendDescription(msg);
           ALOGI("%s", msg.string());
       }
       bool restartEvent;
       if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
           KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
           // 主要是对unhandle key的policy,这里不做分析
           restartEvent = afterKeyEventLockedInterruptible(connection, dispatchEntry, keyEntry, handled);
       } else if (dispatchEntry->eventEntry->type == EventEntry::TYPE_MOTION) {
           MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
           restartEvent = afterMotionEventLockedInterruptible(connection, dispatchEntry, motionEntry, handled);
       } else {
           restartEvent = false;
       }
       if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
           // 从waitQueue中将当前entry移除
           connection->waitQueue.dequeue(dispatchEntry);
           traceWaitQueueLengthLocked(connection);
           if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
               connection->outboundQueue.enqueueAtHead(dispatchEntry);
               traceOutboundQueueLengthLocked(connection);
           } else {
              // 释放内存
               releaseDispatchEntryLocked(dispatchEntry);
           }
       }
      // 如果当前connection的outBoundQueque里还有Event,则继续下一轮的Dispatch周期
       startDispatchCycleLocked(now(), connection);
   }
}

从waitQueue中将当前DispatchEntry移除,如果当前connection的outBoundQueque里还有EventEntry,则继续下一轮的Dispatch周期。

最后放上整体流程图和时序图:

整体流程图
时序图

简单总结下InputDispatcher分发事件流程:

1)InputReader唤醒InputDispacher执行分发处理。

InputReader主要是将EventHub中的input_event转换成具体的EventEntry,并添加到InputDispatcher的mInboundQueue里,然后唤醒InputDispacher线程。InputDispacher线程有自己的Looper,被唤醒后会执行dispatchOnce方法,InputDispatcher处理的事件主要分两种:一种是command命令(Command是mPolicy处理的具体事务,这个mPolicy就是NativeInputManager,最终对应的上层是InputManagerService),一种是input event。每次dispatchOnce,会优先执行前者。

2)InboundQueue队头取出事件,匹配焦点窗口,传递eventEntry。

从mInboundQueue队列头部取出一个事件,寻找与之匹配的焦点窗口的inputTargets,这里先通过InputWindowHandle找到InputWindowInfo,再找到InputChannel,最终找到Connection,每个焦点窗口在InputDispacher里都有一个对应的Connection,通过这个Connection可以跟InputDispacher通信。然后会将eventEntry转为DispatchEntry放入Connection的outboundQueue。通过InputPublisher通过 InputChannel::sendMessage将DispatchEntry发送给窗口,再将dispatchEntry从outboundQueue移到waitQueue里。该通信过程是异步的,也就是说当InputDispatcher将Input事件发送给目标窗口后会立即返回,并不会等待窗口的处理结果。

3)窗口处理完又将处理结果返回给InputDispatcher

当窗口处理完事件后,会通过handleReceiveCallback()回调函数通知InputDispatcher。回调主要是post一个command,从waitQueue中将当前DispatchEntry移除,如果当前connection的outBoundQueque里还有DispatchEntry,则继续下一轮的Dispatch周期。

到这里InputDispatcher分发事件过程就介绍完了。

下一篇文章:
Android Input(五)-InputChannel通信

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

推荐阅读更多精彩内容