Android事件分发流程(API-27)(二)

Android事件分发流程(API-27)(1)中我们有分析到当我们在屏幕的一个点击会走到Activity.dispatchTouchEvent(),现在我们分析Actvitiy.dispatchTouchEvent()之后的流程

  • Activity分发流程

    • Activity.dispatchTouchEvent()

      public boolean dispatchTouchEvent(MotionEvent ev) {
          if (ev.getAction() == MotionEvent.ACTION_DOWN) {
              // onUserInteraction是Activity的一个空方法,当有用户和设备有交互就会触发
              onUserInteraction();
          }
          if (getWindow().superDispatchTouchEvent(ev)) {
              // 调用window的superDispatchTouchEvent(), 这里的getWindow我们都知道是PhoneWindow
              return true;
          }
          return onTouchEvent(ev);
      }
      

      这里我们看到当PhoneWindow返回true就会被拦截掉,dispatchTouchEvent直接返回true,而不再执行Activity.onTouchEvent()方法了

    • PhoneWindow.superDispatchTouchEvent()

      @Override
      public boolean superDispatchTouchEvent(MotionEvent event) {
          // 这里的mDecor我们都知道是DecorView,所以PhoneWindow直接交给了DecorView处理了
          return mDecor.superDispatchTouchEvent(event);
      }
      
    • DecorView.superDispatchTouchEvent()

      public boolean superDispatchTouchEvent(MotionEvent event) {
          // DecorView继承的FrameLayout,FrameLayout继承ViewGroup,而FrameLayout并没有重写dispatchTouchEvent()方法,所以直接调用ViewGroup的该方法
          return super.dispatchTouchEvent(event);
      }
      

      上面的流程我们可以知道,在getWindow().superDispatchTouchEvent(ev)的流程交给了ViewGroup处理,我们先来看看ViewGroup返回false之后的流程Activity.onTouchEvent()

    • Activity.onTouchEvent()

      public boolean onTouchEvent(MotionEvent event) {
          // mWindow是PhoneWindow,PhoneWindow继承Window这个抽象类,PhoneWindow没有重写该方法,直接看Window的该方法
          if (mWindow.shouldCloseOnTouch(this, event)) {
              finish();
              return true;
          }
      
          return false;
      }
      
    • Window.shouldCloseOnTouch()

      /** @hide */
      public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
          final boolean isOutside =
              event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
              || event.getAction() == MotionEvent.ACTION_OUTSIDE;
          if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
              // 如果是在Window的区域之外,则返回true
              return true;
          }
          // 否则返回false
          return false;
      }
      
      // 这里主要是判断是否在Window的边界外
      private boolean isOutOfBounds(Context context, MotionEvent event) {
          final int x = (int) event.getX();
          final int y = (int) event.getY();
          final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
          final View decorView = getDecorView();
          return (x < -slop) || (y < -slop)
              || (x > (decorView.getWidth()+slop))
              || (y > (decorView.getHeight()+slop));
      }
      

      所以Activity.onTouchEvent()主要是判断是否在Window的区域之外,如果在区域之外则finish当前Activity,并返回true,否则返回false,总之到这里ActivitydispatchTouchEvent的事件分发流程就结束了

    • 流程图

      Activity事件分发流程.jpg
  • ViewGroup分发流程

    上面我们分析了Activity层的事件分发的简单流程,现在来看下ViewGroup.dispatchTouchEvent之后的流程是怎样的

    • ViewGroup.dispatchTouchEvent里的流程较多,我们只看主要的代码

      @Override
      public boolean dispatchTouchEvent(MotionEvent ev) {
          if (mInputEventConsistencyVerifier != null) {
              // 用于调试,输入事件一致性校验,View中的InputEventConsistencyVerifier属性
              mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
          }
      
          // If the event targets the accessibility focused view and this is it, start
          // normal event dispatch. Maybe a descendant is what will handle the click.
          // 处理辅助功能
          if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
              ev.setTargetAccessibilityFocus(false);
          }
      
          boolean handled = false; // 返回值,默认false
          if (onFilterTouchEventForSecurity(ev)) {
              // 安全策略过滤
              final int action = ev.getAction();
              final int actionMasked = action & MotionEvent.ACTION_MASK;
      
              // Handle an initial down.
              if (actionMasked == MotionEvent.ACTION_DOWN) {
                  // Throw away all previous state when starting a new touch gesture.
                  // The framework may have dropped the up or cancel event for the previous gesture
                  // due to an app switch, ANR, or some other state change.
                  cancelAndClearTouchTargets(ev);
                  resetTouchState();
              }
      
              // Check for interception.
              // 当前ViewGroup是否要拦截
              final boolean intercepted;
              if (actionMasked == MotionEvent.ACTION_DOWN
                  || mFirstTouchTarget != null) {
                  // FLAG_DISALLOW_INTERCEPT 禁止拦截标识,可以调用requestDisallowInterceptTouchEvent(boolean disallowIntercept)禁止父ViewGroup拦截
                  final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                  if (!disallowIntercept) {
                      // 1. 重点来了,调用了ViewGroup.onInterceptTouchEvent
                      intercepted = onInterceptTouchEvent(ev);
                      ev.setAction(action); // restore action in case it was changed
                  } else {
                      // 不进行拦截
                      intercepted = false;
                  }
              } else {
                  // There are no touch targets and this action is not an initial down
                  // so this view group continues to intercept touches.
                  intercepted = true;
              }
      
              // If intercepted, start normal event dispatch. Also if there is already
              // a view that is handling the gesture, do normal event dispatch.
              if (intercepted || mFirstTouchTarget != null) {
                  ev.setTargetAccessibilityFocus(false);
              }
      
              // Check for cancelation.
              // 当前事件是否被取消
              final boolean canceled = resetCancelNextUpFlag(this)
                  || actionMasked == MotionEvent.ACTION_CANCEL;
      
              // Update list of touch targets for pointer down, if needed.
              final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
              TouchTarget newTouchTarget = null;
              boolean alreadyDispatchedToNewTouchTarget = false;
              if (!canceled && !intercepted) {
                  // 如果事件没有被取消也没有被拦截,则分发给对应的子View/ViewGroup
                  // If the event is targeting accessiiblity focus we give it to the
                  // view that has accessibility focus and if it does not handle it
                  // we clear the flag and dispatch the event to all children as usual.
                  // We are looking up the accessibility focused host to avoid keeping
                  // state since these events are very rare.
                  View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                      ? findChildWithAccessibilityFocus() : null;
      
                  if (actionMasked == MotionEvent.ACTION_DOWN
                      || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                      || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                      final int actionIndex = ev.getActionIndex(); // always 0 for down
                      final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                          : TouchTarget.ALL_POINTER_IDS;
      
                      // Clean up earlier touch targets for this pointer id in case they
                      // have become out of sync.
                      removePointersFromTouchTargets(idBitsToAssign);
      
                      final int childrenCount = mChildrenCount;
                      if (newTouchTarget == null && childrenCount != 0) {
                          final float x = ev.getX(actionIndex);
                          final float y = ev.getY(actionIndex);
                          // Find a child that can receive the event.
                          // Scan children from front to back.
                          final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                          final boolean customOrder = preorderedList == null
                              && isChildrenDrawingOrderEnabled();
                          // 遍历所有的子View/ViewGroup
                          final View[] children = mChildren;
                          for (int i = childrenCount - 1; i >= 0; i--) {
                              final int childIndex = getAndVerifyPreorderedIndex(
                                  childrenCount, i, customOrder);
                              final View child = getAndVerifyPreorderedView(
                                  preorderedList, children, childIndex);
      
                              // If there is a view that has accessibility focus we want it
                              // to get the event first and if not handled we will perform a
                              // normal dispatch. We may do a double iteration but this is
                              // safer given the timeframe.
                              if (childWithAccessibilityFocus != null) {
                                  if (childWithAccessibilityFocus != child) {
                                      continue;
                                  }
                                  childWithAccessibilityFocus = null;
                                  i = childrenCount - 1;
                              }
                              
                              // 子控件是否能够接受事件或者点击位置是否在该子控件上
                              if (!canViewReceivePointerEvents(child)
                                  || !isTransformedTouchPointInView(x, y, child, null)) {
                                  ev.setTargetAccessibilityFocus(false);
                                  continue;
                              }
      
                              newTouchTarget = getTouchTarget(child);
                              if (newTouchTarget != null) {
                                  // Child is already receiving touch within its bounds.
                                  // Give it the new pointer in addition to the ones it is handling.
                                  newTouchTarget.pointerIdBits |= idBitsToAssign;
                                  break;
                              }
      
                              resetCancelNextUpFlag(child);
                              // 调用子控件进行分发 dispatchTransformedTouchEvent
                              if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                  // Child wants to receive touch within its bounds.
                                  mLastTouchDownTime = ev.getDownTime();
                                  if (preorderedList != null) {
                                      // childIndex points into presorted list, find original index
                                      for (int j = 0; j < childrenCount; j++) {
                                          if (children[childIndex] == mChildren[j]) {
                                              mLastTouchDownIndex = j;
                                              break;
                                          }
                                      }
                                  } else {
                                      mLastTouchDownIndex = childIndex;
                                  }
                                  mLastTouchDownX = ev.getX();
                                  mLastTouchDownY = ev.getY();
                                  newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                  alreadyDispatchedToNewTouchTarget = true;
                                  break;
                              }
      
                              // The accessibility focus didn't handle the event, so clear
                              // the flag and do a normal dispatch to all children.
                              ev.setTargetAccessibilityFocus(false);
                          }
                          if (preorderedList != null) preorderedList.clear();
                      }
      
                      if (newTouchTarget == null && mFirstTouchTarget != null) {
                          // Did not find a child to receive the event.
                          // Assign the pointer to the least recently added target.
                          newTouchTarget = mFirstTouchTarget;
                          while (newTouchTarget.next != null) {
                              newTouchTarget = newTouchTarget.next;
                          }
                          newTouchTarget.pointerIdBits |= idBitsToAssign;
                      }
                  }
              }
      
              // Dispatch to touch targets.
              if (mFirstTouchTarget == null) {
                  // No touch targets so treat this as an ordinary view.
                  // 没有子视图接收,分发给当前视图
                  handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                                          TouchTarget.ALL_POINTER_IDS);
              } else {
                  // Dispatch to touch targets, excluding the new touch target if we already
                  // dispatched to it.  Cancel touch targets if necessary.
                  TouchTarget predecessor = null;
                  TouchTarget target = mFirstTouchTarget;
                  while (target != null) {
                      final TouchTarget next = target.next;
                      if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                          handled = true;
                      } else {
                          final boolean cancelChild = resetCancelNextUpFlag(target.child)
                              || intercepted;
                          if (dispatchTransformedTouchEvent(ev, cancelChild,
                                                            target.child, target.pointerIdBits)) {
                              handled = true;
                          }
                          if (cancelChild) {
                              if (predecessor == null) {
                                  mFirstTouchTarget = next;
                              } else {
                                  predecessor.next = next;
                              }
                              target.recycle();
                              target = next;
                              continue;
                          }
                      }
                      predecessor = target;
                      target = next;
                  }
              }
      
              // Update list of touch targets for pointer up or cancel, if needed.
              if (canceled
                  || actionMasked == MotionEvent.ACTION_UP
                  || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                  resetTouchState();
              } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                  final int actionIndex = ev.getActionIndex();
                  final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                  removePointersFromTouchTargets(idBitsToRemove);
              }
          }
      
          if (!handled && mInputEventConsistencyVerifier != null) {
              mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
          }
          return handled;
      }
      
    • 将事件分发给指定的控件dispatchTransformedTouchEvent

      private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                                    View child, int desiredPointerIdBits) {
          final boolean handled;
      
          // Canceling motions is a special case.  We don't need to perform any transformations
          // or filtering.  The important part is the action, not the contents.
          // 事件取消
          final int oldAction = event.getAction();
          if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
              event.setAction(MotionEvent.ACTION_CANCEL);
              if (child == null) {
                  // 调用onTouchEvent方法处理
                  handled = super.dispatchTouchEvent(event);
              } else {
                  handled = child.dispatchTouchEvent(event);
              }
              event.setAction(oldAction);
              return handled;
          }
      
          // Calculate the number of pointers to deliver.
          // 计算触摸事件id
          final int oldPointerIdBits = event.getPointerIdBits();
          final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
      
          // If for some reason we ended up in an inconsistent state where it looks like we
          // might produce a motion event with no pointers in it, then drop the event.
          if (newPointerIdBits == 0) {
              return false;
          }
      
          // If the number of pointers is the same and we don't need to perform any fancy
          // irreversible transformations, then we can reuse the motion event for this
          // dispatch as long as we are careful to revert any changes we make.
          // Otherwise we need to make a copy.
          final MotionEvent transformedEvent;
          if (newPointerIdBits == oldPointerIdBits) {
              // 前后id相同,不需要重新计算
              if (child == null || child.hasIdentityMatrix()) {
                  if (child == null) {
                      // super这里是View,child为空,把自己当成View,调用dispatchTouchEvent方法
                      // 调用onTouchEvent方法处理
                      handled = super.dispatchTouchEvent(event);
                  } else {
                      // 不为空,调用相应子View/ViewGroup的dispatchTouchEvent
                      final float offsetX = mScrollX - child.mLeft;
                      final float offsetY = mScrollY - child.mTop;
                      event.offsetLocation(offsetX, offsetY);
      
                      handled = child.dispatchTouchEvent(event);
      
                      event.offsetLocation(-offsetX, -offsetY);
                  }
                  return handled;
              }
              transformedEvent = MotionEvent.obtain(event);
          } else {
              transformedEvent = event.split(newPointerIdBits);
          }
      
          // Perform any necessary transformations and dispatch.
          if (child == null) {
              handled = super.dispatchTouchEvent(transformedEvent);
          } else {
              final float offsetX = mScrollX - child.mLeft;
              final float offsetY = mScrollY - child.mTop;
              transformedEvent.offsetLocation(offsetX, offsetY);
              if (! child.hasIdentityMatrix()) {
                  transformedEvent.transform(child.getInverseMatrix());
              }
      
              handled = child.dispatchTouchEvent(transformedEvent);
          }
      
          // Done.
          transformedEvent.recycle();
          return handled;
      }
      
    • 是否拦截方法,ViewGroup.onInterceptTouchEvent

      public boolean onInterceptTouchEvent(MotionEvent ev) {
          if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
              && ev.getAction() == MotionEvent.ACTION_DOWN
              && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
              && isOnScrollbarThumb(ev.getX(), ev.getY())) {
              // true拦截
              return true;
          }
          // false不拦截
          return false;
      }
      
    • ViewGroup的分发流程相对较为复杂,简单流程图如下

      ViewGroup事件分发流程.jpg
  • View分发流程

    • 接下来分析View.dispatchTouchEvent

      /**
       * Pass the touch screen motion event down to the target view, or this
       * view if it is the target.
       *
       * @param event The motion event to be dispatched.
       * @return True if the event was handled by the view, false otherwise.
       */
      public boolean dispatchTouchEvent(MotionEvent event) {
          // If the event should be handled by accessibility focus first.
          if (event.isTargetAccessibilityFocus()) {
              // We don't have focus or no virtual descendant has it, do not handle the event.
              if (!isAccessibilityFocusedViewOrHost()) {
                  return false;
              }
              // We have focus and got the event, then use normal event dispatch.
              event.setTargetAccessibilityFocus(false);
          }
      
          // 返回值,默认false
          boolean result = false;
      
          if (mInputEventConsistencyVerifier != null) {
              mInputEventConsistencyVerifier.onTouchEvent(event, 0);
          }
      
          final int actionMasked = event.getActionMasked();
          if (actionMasked == MotionEvent.ACTION_DOWN) {
              // Defensive cleanup for new gesture
              stopNestedScroll();
          }
      
          if (onFilterTouchEventForSecurity(event)) {
              if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                  // View是Enable状态且处理ScrollBar的drag操作返回true
                  result = true;
              }
              //noinspection SimplifiableIfStatement
              ListenerInfo li = mListenerInfo;
              if (li != null && li.mOnTouchListener != null
                  && (mViewFlags & ENABLED_MASK) == ENABLED
                  && li.mOnTouchListener.onTouch(this, event)) {
                  // mOnTouchListener不为空(设置了setOnTouchListener)
                  // 且View是Enable状态
                  // 且OnTouchListener.onTouch返回true
                  result = true;
              }
      
              if (!result && onTouchEvent(event)) {
                  // result为false则执行onTouchEvent方法
                  result = true;
              }
          }
      
          if (!result && mInputEventConsistencyVerifier != null) {
              mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
          }
      
          // Clean up after nested scrolls if this is the end of a gesture;
          // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
          // of the gesture.
          if (actionMasked == MotionEvent.ACTION_UP ||
              actionMasked == MotionEvent.ACTION_CANCEL ||
              (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
              stopNestedScroll();
          }
      
          return result;
      }
      

      resultfalse时,会调用onTouchEvent方法,这里OnTouchListener.onTouch方法会执行在performClick之前,即可能会拦截OnClickListener事件,使得onClick方法不会执行,所以当我们重写setOnTouchListener方法时AS往往会给我们一个如下提示

      onTouch should call View#performClick when a click is detected less... (⌘F1) 
      If a View that overrides onTouchEvent or uses an OnTouchListener does not also implement performClick and call it when clicks are detected, the View may not handle accessibility actions properly. Logic handling the click actions should ideally be placed in View#performClick as some accessibility services invoke performClick when a click action should occur.
      
    • View.onTouchEvent

      public boolean onTouchEvent(MotionEvent event) {
          ...
      
          if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
              switch (action) {
                  case MotionEvent.ACTION_UP:
                      // 抬起
                      ...
                      boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                      if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                          // take focus if we don't have it already and we should in
                          // touch mode.
                          boolean focusTaken = false;
                          if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                              focusTaken = requestFocus();
                          }
      
                          if (prepressed) {
                              // The button is being released before we actually
                              // showed it as pressed.  Make it show the pressed
                              // state now (before scheduling the click) to ensure
                              // the user sees it.
                              setPressed(true, x, y);
                          }
      
                          if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                              // This is a tap, so remove the longpress check
                              removeLongPressCallback();
      
                              // Only perform take click actions if we were in the pressed state
                              // 执行点击操作
                              if (!focusTaken) {
                                  // Use a Runnable and post this rather than calling
                                  // performClick directly. This lets other visual state
                                  // of the view update before click actions start.
                                  if (mPerformClick == null) {
                                      mPerformClick = new PerformClick();
                                  }
                                  // 通过Handler.post(Runnable)方式执行performClick方法
                                  if (!post(mPerformClick)) {
                                      // post方式失败则直接调用performClick
                                      performClick();
                                  }
                              }
                          }
                      ...
                      break;
      
                  case MotionEvent.ACTION_DOWN:
                      // 按下
                      ...
                      break;
      
                  case MotionEvent.ACTION_CANCEL:
                      // 取消
                      ...
                      break;
      
                  case MotionEvent.ACTION_MOVE:
                      // 滑动
                      ...
                      break;
              }
      
              return true;
          }
      
          return false;
      }
      
    • View.performClick()

      public boolean performClick() {
          final boolean result;
          final ListenerInfo li = mListenerInfo;
          if (li != null && li.mOnClickListener != null) {
              // mClickListener不为空,即setOnClickListener()
              playSoundEffect(SoundEffectConstants.CLICK);
              // 执行onClick方法
              li.mOnClickListener.onClick(this);
              result = true;
          } else {
              result = false;
          }
      
          sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
      
          notifyEnterOrExitForAutoFillIfNeeded(true);
          // 默认返回false
          return result;
      }
      
      
    • 流程图

      View事件分发流程.jpg
  • 整体流程

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

推荐阅读更多精彩内容