android源码之view事件分发机制

MotionEvent之来龙去脉

1. 输入事件分 类
  • KeyEvent
  • MotionEvent
2. InputManagerService 系统服务
  • 流程概述:
    1.当用户输入操作(触摸/按键/鼠标),在驱动层会受到信号后,会将信号写入到输入设备节点,从而产生内核事件
    2.IMS接受到内核时间后,进过处理和封装后包装成KeyEvent/MotionEvent
    3.IMS共WMS讲给对应的window来消费输入事件

  • InputManagerService 组成结构以及工作原理
    1.结构:
    1)InputManagerService.

    1. NativeInputManager
      2.1)EventHub 监听输入启动,添加到epoll中
      2.2)InputManager
      // 生产者消费者模式, 两个线程
      2.2.1)InputReaderThread && InputReader
      从EventHub,监听设备节点,创建epoll对象, epoll_wait 从管道中读取出事件, 唤醒inputReader
      2.2.2)InputDispatcherThread && InputDispatcher
      接收来自InputReader的输入时间,然后分发到合适的window中去
      其中InputReaderThread, InputDispatcherThread 运行在SystermServer进程中的
  • InputChannel 对socket对的封装,通过这种方式进入快进程, 一般server和client端各一个, server端的最终是注册到InputManagerService中,并且在InputDispatcherThread转发调用,client端则是通过注册到InputEventReceiver中通过epoll机制,接收InputManagerService发送过来的信息。并且转发出来

[图片上传失败...(image-220752-1554567102902)]

3. framwork层Activity, view, window 三者产生关系的时机
  • Activity,ViewRootImpl, Window, WindowManagerService
  • Activity与Window的关系
  1. 在ActivityThread中Activity attatch的时候创建Window,Window的实现类为PhoneWindow
    ActivityThread.java
class ActivityThread{
    private Activity performLaunchActivity(ActivityRecord r, Intent intent){
        // 省略。。。
        // 执行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);
                        
            
        // 省略。。。
    }
    
    public void handleResumeActivity(IBinder token, ...){
        // 省略 ...
        // 执行activity的生命周期onResumeActivity()
        final ActivityClientdeRecord r = performResumeActivity(token, finalStateRequest, reason);
        // 省略 ...
        
        // 调用Activity的方法,最终实现将window上addView, 详见Activity.java##makeVisible()方法
        r.activity.makeVisible();
        
    }
}

Activity.java

class Activity{
    void attach(Context context, ActivityThread aThread, ....){
        // [1] 创建window
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        
        // window 中设置了WindowManager, 其中 context.getSystemService(xxx)
        //返回的是WindowManagerImpl,在WindowManagerImpl中持有WindowManagerGlobal成员,这个是用于与WMS进行通信
         mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    }
    
    void setContentView(int id){
        // phoneWindow setContentView
        // [2] 设置window 里的DecorView
        getWindow().setContentView(id);
    }
    
    // window manager 
    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
}

interface Window.Callback{
    public boolean dispatchTouchEvent(MotionEvent event);
}

class PhoneWindow extends Window{
    // [2.1] 初始化window里的decor
    void setContentView(int id){
        //  new DecorView, 并且将mDecor赋值
        installDecor();
    }
}

WindowManagerGlobal.java

class WindowManagerGlobal{
    void addView(View view, ....){
        root = new RootViewImpl(context, display);
        mViews.add(view);
        mRoot.add(root);
        
        // 
        root.setView(view, );
    }
}

// ViewRootImpl 从当的角色是管理和执行view的生命周期, 以及时间机制
class ViewRootImpl{
    void setView(){
        // 1. 创建一个InputChannel
        mInputChannel = new InputChannel();

        // 2.这里将InputChannel添加到InputManangerService中产紧固件socketpair, 用来接受和发送事件
        mWindowSession.addToDisplay(mWindow, ..., mInputChannel);
    
    
        // 3.set up input pipline
         CharSequence counterSuffix = attrs.getTitle();
        mSyntheticInputStage = new SyntheticInputStage();
        InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
        InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
        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);
    }
}

这里InputStage由一个KeyEvent链表结构组成一个队列,
NativePreImeInputStage-> ViewPreImeInputStage -> EarlyPostImeInputStage->ViewPostImeInputStage->SyntheticInputStage

class ViewRootImpl{
    private void deliverInputEvent(QueuedInputEvent q){
        // 省略
         InputStage stage;
         // 只有在设置为FLAG_UNHANDLED 情况下直接交给mSyntheticInputStage 处理
         // 只在##dispatchUnhandledInputEvent()方法中产生
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            // 所以一般为 mFirstPostImeInputStage 或者 mFirstInputStage
            // 区别在于 看是否要跳过ime的处理, 如果跳过,采用EarlyPostImeInputStage 处理
            // 否则从责任链的 NativePreImeInputStage 开始处理
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        // 省略
      
        if (stage != null) {
            handleWindowFocusChanged();
            // 交给以上对应的步骤进程处理
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }
    
    void scheduleConsumeBatchedInput() {
        if (!mConsumeBatchedInputScheduled) {
            mConsumeBatchedInputScheduled = true;
            // 
            mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
                    mConsumedBatchedInputRunnable, null);
        }
    }
    
    final class ConsumeBatchedInputRunnable implements Runnable {
        @Override
        public void run() {
            // 
            doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
        }
    }
    
      void doConsumeBatchedInput(long frameTimeNanos) {
        if (mConsumeBatchedInputScheduled) {
            mConsumeBatchedInputScheduled = false;
            if (mInputEventReceiver != null) {
                if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
                        && frameTimeNanos != -1) {
                    // 请求绘制下一阵的处理batch事件
                    scheduleConsumeBatchedInput();
                }
            }
            doProcessInputEvents();
        }
    }
}

android_view_inputEventReceiver.cpp

static jlong nativeInit(*env, ...., inputChannelObj, messageQueueObj){
    // 获取native 的InputChannel
        sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(inputChannelObj);
    
    // 2. 获取native Messsagequeue
    sp<MessageQueue> messsageQueue = android_os_MesssageQueue_getMessageQeueue(messageQueueObj);
    // 3 创建NativeInputEventReceiver
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(inputchanel, mmessage)
    receiver->initialize();

}

NativeInputEventReceiver::initialize(){
    // 注册ALOOPER_EVENT_INPUT 到epoll中
    setFdEvent(ALOOPER_EVENT_INPUT)    
}

// 从InputConsummer中获取channel的fd, 然后调用Looper addFd 注册到epoll中去,这里的looper对应的是主线程即UI线程里的looper, 添加epoll监控
// 因为NativeInputEventReceiver继承了LooperCallback, 所有当epoll机制里面,pollOnce的时候,有对应的事件消息,就会调用 handleEvent 处理
NativeInputEventReceiver::setFdEvent(){
      if (mFdEvents != events) {
        mFdEvents = events;
        int fd = mInputConsumer.getChannel()->getFd();
        if (events) {
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
        } else {
            mMessageQueue->getLooper()->removeFd(fd);
        }
    }
}

// 此处接收到输入的事件消息处理, 最终通过consumeEvents方法处理
NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
     if (events & ALOOPER_EVENT_INPUT) {
        // 交给consumeEvents 处理
        status_t status = consumeEvents (env, false /*consumeBatches*/, -1, NULL);
    }
}

NativeInputEventReceiver::consumeEvents(JNIEnv* env,...){
     // ...
     for(;;){
        // 注意此处mInputConsumer是在NativeInputEvnetRecevier的初始化列表中创建
        //1. mInputConsumer(inputChannel), 里面持有InputChannel
          status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
        // 2.调用java中 InputEventReceiver.java##dispatchInputEvent() 方法
        if(inputEventObj){
               env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
        }
        
         
         
     }   
}

InputTransport.cpp

InputConsumer::consume(..){
    while(cond){
    // 可以知道最终是通过Channel去处理消息
        status_t result = mChannel->receiveMessage(&mMsg);
        if(result){
            consumeBatch(x)
        }
    }
}

InputChannel::receiveMessage(InputMessage* msg){
    // 从缓冲区里读取消息
}

梳理一下整个input事件的前因后果
1、ViewRootImpl 在setView的时候会new WindowInputEventReceiver()

  1. InputEventReceiver 在初始化的时候调用nativeInit 创建一个NativeInputEventReceiver,注册到主线程的Looper, epoll中处理,同时NativeInputEventReceiver继承LoopCallback回调函数。在pollonce,当有相应的Input事件的时候.
    3.每次receiver端的在有事件产生时事件触发NativeInputEventReceiver的handleEvent, 继而 InputConsumer::consume
    4.最终会调用InputEventReceiver##dispatchInputEvent,最终enqueueInputEvent, 然后进入责任链调用中
    5.在责任链中ViewPostImeInputStage##processPointerEvent## mView.dispatchPointerEvent(event);其中此处的view
    就是DecorView, 继而就是后续的dispatchTouchEvent(), onInterceptTouchEvent(), onTouchEvent()

涉及系统层的服务有哪些:
1.InputManagerService
2.WindowManagerService, 其中WindowManagerService在SystemService初始化过程中持有InputManagerService对象

在ViewRootImpl中setView中,会将InputChannel添加到WMS中并创建SocketPair用于处理接收和发送

关注3点:

  1. Activity 中new了一个PhoneWindow
  2. Activity 中实现了window的callback接口
    3)Window 设置了WindowManager(实现类WindowManagerImpl)
4、framework输入系统结构

[图片上传失败...(image-3ed534-1554567102902)]

5.InputEvent如何从InpuntReceiver最终传递到子view中去的

在经过InpuntReceiver state链的处理之后,
如果是TouchEvent的话
在链ViewPostImeInputStage中processPointerEvent方法中会调用
DecorView中基类View中的dispatchPointerEvent 方法如下图

[图片上传失败...(image-29d550-1554567102902)]

6. 在ViewGroup 和View之间传递过程

class ViewGroup{
    public void dispathTouchEvent(){
    
        // 1.对ACTION_DOWN事件的特殊处理
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 如果是ACTION_DOWN事件
            //1.清除前一个事件序列的相关信息,这些信息保存在名为mFirstTouchTarget的TouchTarget对象中,
            //2.TouchTarget是一个链表,里面保存了应当处理该事件的View链
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
    
        // 2.拦截处理
        //2.1 disallowIntercept 全局标志禁止拦截,
        //2.2 onInterceptTouchEvent 根据具体情况判断是否需要拦截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            // 1.这里disallowIntercept 是否允许事件被拦截
            if (!disallowIntercept) {
                2.经过intercept机制进一步作判断
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                    intercepted = false;
            }
        } else {
            intercepted = true;
        }

        //  如果被拦截,或者已经有目标VIew在处理,执行普通的时间Dispatch
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }
        
        // 取消标志的判断
        final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
                    
        // split表达的意思是当前ViewGroup是否支持分割motionEvent到不同的view中
        // 这里setMotionEventSplittingEnabled 设置,这个一般用于多点触摸
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

        if(!canceled && ! intercepted){
            // 3. 事件既不是取消时间,又没有拦截
            // 3.1 事件为ACTION_DOWN/支持多点/hover
             if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE){
                // 重新计算新的newTouchTarget
                 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 View[] children = mChildren;
                        //是否使用自定义的顺序来添加控件, 这里可以重写getChildDrawingOrder方法
                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            //如果是用了自定义的顺序来添加控件,那么绘制的View的顺序和mChildren的顺序是不一样的
                            //所以要根据getChildDrawingOrder取出真正的绘制的View
                            //自定义的绘制,可能第一个会画到第三个,和第四个,第二个画到第一个,这样里面的内容和Children是不一样的
                            final int childIndex = customOrder ?
                                    getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            //如果child不可以接收这个触摸的事件,或者触摸事件发生的位置不在这个View的范围内
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }
                            //获取新的触摸对象,如果当前的子View在之前的触摸目标的列表当中就返回touchTarget
                            //子View不在之前的触摸目标列表那么就返回null
                            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.
                                //如果新的触摸目标对象不为空,那么就把这个触摸的ID赋予它,这样子,
                                //这个触摸的目标对象的id就含有了好几个pointer的ID了
 
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                            //如果子View不在之前的触摸目标列表中,先重置childView的标志,去除掉CACEL的标志
                            resetCancelNextUpFlag(child);
                            //调用子View的dispatchTouchEvent,并且把pointer的id 赋予进去
                            //如果说,子View接收并且处理了这个事件,那么就更新上一次触摸事件的信息,
                            //并且为创建一个新的触摸目标对象,并且绑定这个子View和Pointer的ID
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //这里给mFirstTouchTarget赋值
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                
            }
            //如果newTouchTarget为null,就代表,这个事件没有找到子View去处理它,
            //那么,如果之前已经有了触摸对象(比如,我点了一张图,另一个手指在外面图的外面点下去)
            //那么就把这个之前那个触摸目标定为第一个触摸对象,并且把这个触摸(pointer)分配给最近添加的触摸目标
         
            if(newTouchTarget ==null && mFirsttouchTarget != null){
                 newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
        
        // 如果没有目标
        if(mFirstTouchTarget == null){
            // 这里会交给View的dispatchTouchEvent处理
             handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        }else{
            //遍历TouchTargt树,分发事件
             TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                
                // .....
                // 往下递归下去分发事件, 
                dispatchTransformedTouchEvent();
             
                predecessor = target;
                target = next;       
            }
        }
    }
    
    public bool onInterceptTouchEvent(){
        // 重写 
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }
}

class View{
    public boolean dispatchTouchEvent(){
        //最前面这一段就是判断当前事件是否能获得焦点,
        // 如果不能获得焦点或者不存在一个View,那我们就直接返回False跳出循环
        if (event.isTargetAccessibilityFocus()) {
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;
        // ...
        //Android用一个32位的整型值表示一次TouchEvent事件,低8位表示touch事件的具体动作,比如按下,抬起,滑动,还有多点触控时的按下,抬起,这个和单点是区分开的,下面看具体的方法:
        //1 getAction:触摸动作的原始32位信息,包括事件的动作,触控点信息
        //2 getActionMasked:触摸的动作,按下,抬起,滑动,多点按下,多点抬起
        //3 getActionIndex:触控点信息
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
             //当我们手指按到View上时,其他的依赖滑动都要先停下
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //重点,优先级:
            // 1. 处理onTouch 事件
            // 2. 处理onTouchEvent时间
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                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;
    }
    
    public bool onTouchEvent(){
         if ((viewFlags & ENABLED_MASK) == DISABLED) {
             return ;
         }
         
         //如果一个view过小,不容易点击,通过扩大点击区域实现更好的交互。可以暂且叫为代理,代理处理该事件。
        //一般不怎么用到,所以可以忽略
         if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        
        switch (action) {
            case MotionEvent.ACTION_UP:
            //指的是,如果view包裹在一个scrolling View中,可能会进行滑动处理,所以设置了一个prePress的状态
            //大致是等待一定时间,然后没有被父类拦截了事件,则认为是点击到了当前的view,从而显示点击态
                    
            if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            //取消LongPress的检测
                            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) {
                                 //执行点击,回调onClickListener的onClick,也就是我们经常使用的setOnClickListener中的操作
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                
                                    //
                                    performClick();
                                }
                            }
                }
            
            case MotionEvent.ACTION_DOWN:
            
            
            case MotionEvent.ACTION_CANCEL:
            
            case MotionEvent.ACTION_MOVE:

        }
    }
}

ViewGroup三个核心的问题

    1. 是否拦截
    1. 检查子view,并且往下传递
    1. 如何处理事件

View 处理事件

当我们对View进行一个点击,可以分为以下三个状态

1、PrePress(姑且叫为预按),这个状态是我们的view被加入到一个scrolling view中才会存在。具体的意义是,举个简单的例子,当我们将手放在listView的一item上的时候,由于当前还不知道用户是要点击item还是想滑动listview,所以先将view设为PrePress状态;
2、Press状态,用户将手放到view上面,如果不是1(上述)的状态,就里面设置为Press状态。那么先进入了PrePress,那么将会触发一个检测,也即CheckForTap(),默认时间是100ms,如果超过了100ms,将由PrePress进入到Press状态;

3、LongPress状态,这个状态由Press状态过度过来,如果用户在一个view上停留超过一定的时间(默认为500ms),将进入该状态

总结

在onTouchEvent的
MotionEvent.ACTION_UP中会判断是否执行PerformClick。因此onTouchEvent的优先于PerformClick。

DISABLED下view任然可能消费事件,CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE。
如button的Clickable为true,而textview的Clickable为false。
onclick会发生的前提是当前view可点击—即收到up和down的事件(必要不充分)。

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

推荐阅读更多精彩内容