那天有人问我,Android 的事件到底是怎么来的?

不要问我从哪里来.png

本文主要针对的是 我们认为的事件分发 之前的流程,也就是说事件是怎么来的?一路又是怎么走下去的?

那为什么要研究事件的来龙去脉呢?其实我写本文之前是知道事件是以下的这种走向传递的。

Avtivity->PhoneWindow->DecorView->ViewGroup->View

直到有一天,有人问我那Activity是怎么接到事件的?我当时是一脸懵逼,然后就没有然后了,所以至于是谁问的我,估计你们也知道了。

本文争取以极简的方式来描述,如有问题欢迎指正。


我们开始表演
首先整个过程中会涉及以下几个类

  1. Activity
  2. PhoneWindow extends Window
  3. WindowManager extends ViewManager
  4. WindowManagerImpl implements WindowManager
  5. WindowManagerGlobal
  6. ViewRootImpl implements ViewParent
  7. WindowManagerService extends IWindowManager.Stub
  8. DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
  9. InputChannel,InputQueue,WindowInputEventReceiver extends InputEventReceiver
  10. ViewRootHandler extends Handler

OK,我们先上结论,后展开说明,以下部分纯干货,如之前看过部分源码可直接看这部分,对源码不是很熟悉的也没有关系,下一个环节就是展开说明。

先上张图理解一下,图片内容较多,可点击原图欣赏无码大图 。


事件来源示例图.png
    1. ActivityThread.performLaunchActivity() 中调用 Activity.attach(),创建PhoneWindow,PhoneWindow创建DecorView
    1. ActivityThread.handleResumeActivity()中调用Activity.makeVisible(),初始化WindowManager并调用WindowManager.addView()
    1. 因为WindowManager是接口,找到其实现类并调用WindowManagerImpl.addView()
    1. 调用WindowManagerGlobal.addView(View view, ..., Window parentWindow, ...)
      此时需要注意view为DecorView对象,该对象是通过ActivityThread.handleResumeActivity()中的PhoneWindow.getDecorView()获取,并直接赋值给Activity变量的。
    1. WindowManagerGlobal.addView()创建了ViewRootImpl,并调用了ViewRootImpl.setView(view,...,...)
    1. 连带创建InputChannel,InputQueue以及WindowInputEventReceiver对象并传入InputChannel和Looper
    1. 其实Android事件的源头来自于用户输入行为,由硬件进行捕获,一般会保存在dev/input节点下,后续组装成KeyEvent/MotionEvent对象,经Native进入Java的InputEventReceiver.dispatchInputEvent()中。
    1. ViewRootImpl.WindowInputEventReceiver extends InputEventReceiver,连带调用了enqueueInputEvent()->doProcessInputEvents-> deliverInputEvent(q),方法中获取到mFirstPostImeInputStage对象其实为ViewPostImeInputStage
    1. ViewPostImeInputStage extends InputStage 因此执行onProcess(),判断如果是触摸事件,调用processPointerEvent(),然后内部调用mView.dispatchPointerEvent(),此时的mView为DecorView,辗转调用到了DecorView.dispatchTouchEvent()
    1. 通过 mWindow.getCallback()获取Window.Callback然后调用Window.Callback.dispatchTouchEvent(),这个Callback就是PhoneWindow里的mCallback,而mCallback则是Activity的attach()赋值的,此处也就自然调用到了Activity中,后续就是我们都知道的事件分发了,一个完整的闭环就结束了。

  1. ActivityThread.performLaunchActivity()
  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          ...
            if (activity != null) {
               ...
              //可以看到此处调用了Activity的attach()方法
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
            ...
            }
}
  • 1.1 Activity.attach()中进行了PhoneWindow初始化,并设置CallBack对象。
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //PhoneWindow初始化
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);

2.ActivityThread.handleResumeActivity() 通过PhoneWindow获取DecorView,然后调用activity.makeVisible()

 public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
       ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }

          } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
       ...
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        }
 ...
    }
  • 2.1 PhoneWindow创建DecorView的过程
   @Override
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            //组装DecorView
            installDecor();
        }
        return mDecor;
    }

   private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //获取DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
    }
      //返回值就是DecorView
    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
  • 2.2. activity.makeVisible()过程
  void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            //此时调用的其实事WindowManager.addView()
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
  1. WindowManager是接口,找到其实现类并调用WindowManagerImpl.addView()
@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
       //此时调用的是WindowManagerGlobal.addView()
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
  1. WindowManagerGlobal.addView() 初始化ViewRootImpl,然后调用root.setView()
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
      ...
         synchronized (mLock) {
           ...
           //初始化ViewRootImpl
           root = new ViewRootImpl(view.getContext(), display);
           view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
  1. ViewRootImpl.setView(view,...,...) 创建InputChannel, InputQueue, WindowInputEventReceiver
   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
           ...
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
               if (inputChannel != null) {
                    if (mInputQueueCallback != null) {
                        //创建对象
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());
              }
            
        }
    }
  1. 其实Android事件的源头来自于用户输入行为,由硬件进行捕获,一般会保存在 dev/input 节点下,后续组装成KeyEvent/MotionEvent对象,经Native进入Java的InputEventReceiver.dispatchInputEvent()中。

我们先分析InputEventReceiver

 private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event);
    }

public void onInputEvent(InputEvent event) {
        finishInputEvent(event, false);
    }

由于InputEventReceiver是abstract类,因此需要找到对应的实现类,此时第5步中 WindowInputEventReceiver 即是实现类,因此找到重写的onInputEvent()方法进行下一步分析。

  1. ViewRootImpl.WindowInputEventReceiver extends InputEventReceiver,连带调用了enqueueInputEvent()->doProcessInputEvents-> deliverInputEvent(q),方法中获取到mFirstPostImeInputStage对象其实为ViewPostImeInputStage。
        @Override
        public void onInputEvent(InputEvent event) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
            List<InputEvent> processedEvents;
            try {
                processedEvents =
                    mInputCompatProcessor.processInputEventForCompatibility(event);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            if (processedEvents != null) {
                if (processedEvents.isEmpty()) {
                    // InputEvent consumed by mInputCompatProcessor
                    finishInputEvent(event, true);
                } else {
                    for (int i = 0; i < processedEvents.size(); i++) {
                        enqueueInputEvent(
                                processedEvents.get(i), this,
                                QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
                    }
                }
            } else {
                enqueueInputEvent(event, this, 0, true);
            }
        }


    void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) {
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

        // Always enqueue the input event in order, regardless of its time stamp.
        // We do this because the application or the IME may inject key events
        // in response to touch events and we want to ensure that the injected keys
        // are processed in the order they were received and we cannot trust that
        // the time stamp of injected events are monotonic.
        QueuedInputEvent last = mPendingInputEventTail;
        if (last == null) {
            mPendingInputEventHead = q;
            mPendingInputEventTail = q;
        } else {
            last.mNext = q;
            mPendingInputEventTail = q;
        }
        mPendingInputEventCount += 1;
        Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                mPendingInputEventCount);

        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }

  void doProcessInputEvents() {
        // Deliver all pending input events in the queue.
        while (mPendingInputEventHead != null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            if (mPendingInputEventHead == null) {
                mPendingInputEventTail = null;
            }
            q.mNext = null;

            mPendingInputEventCount -= 1;
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                    mPendingInputEventCount);

            long eventTime = q.mEvent.getEventTimeNano();
            long oldestEventTime = eventTime;
            if (q.mEvent instanceof MotionEvent) {
                MotionEvent me = (MotionEvent)q.mEvent;
                if (me.getHistorySize() > 0) {
                    oldestEventTime = me.getHistoricalEventTimeNano(0);
                }
            }
            mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);

            deliverInputEvent(q);
        }

        // We are done processing all input events that we can process right now
        // so we can clear the pending flag immediately.
        if (mProcessInputEventsScheduled) {
            mProcessInputEventsScheduled = false;
            mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
        }
    }

    private void deliverInputEvent(QueuedInputEvent q) {
         ...
         try {
            ...
            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                //此时返回两个InputStage类型的对象,这个InputStage为abstract因此需要找对应的实现类。
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }

            if (q.mEvent instanceof KeyEvent) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "preDispatchToUnhandledKeyManager");
                try {
                    mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
            }

            if (stage != null) {
                handleWindowFocusChanged();
                stage.deliver(q);
            } else {
                finishInputEvent(q);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

此时需要注意这个InputStage 的实现类相对比较多,不过可以重点看下setView()方法的末尾,着重看mFirstPostImeInputStage赋值逻辑。

   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
              ....               
              // Set up the input pipeline.
                CharSequence counterSuffix = attrs.getTitle();
                mSyntheticInputStage = new SyntheticInputStage();
                //最终new了ViewPostImeInputStage
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                //继续看viewPostImeStage赋值
                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
                //初始化并传入nativePostImeStage,需要查看nativePostImeStage赋值逻辑,继续往上看。
                InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
                InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
                InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
                InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);

                mFirstInputStage = nativePreImeStage;
              //这是赋值位置,可以看以上逻辑是怎么赋值earlyPostImeStage对象
                mFirstPostImeInputStage = earlyPostImeStage;
                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;

                if (mView instanceof RootViewSurfaceTaker) {
                    PendingInsetsController pendingInsetsController =
                            ((RootViewSurfaceTaker) mView).providePendingInsetsController();
                    if (pendingInsetsController != null) {
                        pendingInsetsController.replayAndAttach(mInsetsController);
                    }
                }
}

通过以上逻辑可以清晰的看到mFirstPostImeInputStage 即是ViewPostImeInputStage,我们需要跟进ViewPostImeInputStage看下对应的onProcess()方法。

final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }
...
}

以上逻辑中先判断了输入事件是键盘,还是触摸,触摸逻辑中又判断了输入源是屏幕还是trackball(我猜测这判断的是鼠标等设备输入),不过不重要,我们主要看屏幕输入的逻辑,也就是processPointerEvent(q);方法

 private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            //主要看这个地方,mView即是DecorView
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

到此我需要简单做个总结,来捋一下这个流程,方便大家理解。
其实以上的mView即是DecorView
该对象是在最开始通过PhoneWindow创建。
WindowManager.addView(View v...)传递到WindowManagerGlobal.addView(View v..)
然后通过ViewRootImpl.setView(View v...)传到了ViewRootImpl
因此ViewRootImpl的变量mView即是DecorView,所以我们需要跟进DecorView.dispatchPointerEvent()
由于DecorView extends View,因此先看View.dispatchPointerEvent()

 public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            //调用dispatchTouchEvent(),而DecorView重写了dispatchTouchEvent()
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

跟进DecorView.dispatchTouchEvent()

    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

这里需要注意下这个Window.Callback cb,是通过mWindow.getCallback()获取的。
mWindow是哪来的?
这个mWindow是一个PhoneWindow对象。
是在ActivityThread.handleResumeActivity()时,通过Activity.window.getDecorView()调用PhoneWindow.installDecor()mDecor.setWindow(this);赋值的。
因此需要查看PhoneWindow.getCallback()是哪里赋值的。
在ActivityThread.prefromLuncherActivity()中,调用的Activity.attach(),此方法中对PhoneWindow的Window.Callback进行了赋值,传入的是自己。

 mWindow.setCallback(this);

因此,Window.Callback cb即是Activity,找到Activity.dispatchTouchEvent()

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

此时事件怎么到达的Activity已经完全描述清楚,后续的流程我们就都知道了,不过我可以简单描述下。

以上方法中调用了getWindow().superDispatchTouchEvent(ev)

  • 即是调用的PhoneWindow. superDispatchTouchEvent(ev)

PhoneWindow调用mDecor.superDispatchTouchEvent(event);

  • 即是调用DecorView..superDispatchTouchEvent(event);

DecorView调用super.dispatchTouchEvent(event);

  • 因为DecorView extends FrameLayout,FrameLayout extends ViewGroup,因此调用ViewGroup.dispatchTouchEvent()

通过连带调用ViewGroup.dispatchTransformedTouchEvent() 调用super.dispatchTouchEvent(event)

  • 因为ViewGroup extends View, 因此会调用View.dispatchTouchEvent()事件又传递到了View,整个流程也就完整。至于时间拦截,异常处理等细节本篇将不在赘述,如有问题欢迎指正。

如果本篇内容对你有一点点帮助的话,麻烦帮忙点个👍🏻

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

推荐阅读更多精彩内容