Android源碼分析——事件分發(fā)機制

通過問題來學(xué)習(xí)一個東西是很好的方法痊银。學(xué)習(xí)Android中View的事件體系,我也通過給自己提問題施绎,在解決問題的同時也就知道了其中原理溯革。

0

首先來幾個問題起步:

  • 什么是事件?
  • 什么是事件分發(fā)機制谷醉?

在我們通過屏幕與手機交互的時候致稀,每一次點擊、長按俱尼、移動等都是一個個事件抖单。按照面向?qū)ο蟮乃枷耄@些一個個事件都被封裝成了MotionEvent号显。
分發(fā)機制就是某一個事件從屏幕傳遞給app視圖中的各個View臭猜,然后由其中的某個View來使用這一事件或者忽略這一事件躺酒,這整個過程的控制就是分發(fā)機制了押蚤。
要注意的是,事件分發(fā)機制中羹应,事件是按一個事件序列的形式分發(fā)給View的揽碘。這一序列由 ACTION_DOWN 開始,經(jīng)過一系列 ACTION_MOVE 等事件园匹,最后以 ACTION_UP 事件結(jié)束雳刺。這一個序列中的所有事件,要么被忽略裸违,要么就只能有一個事件能使用掖桦。要是同一個序列,比如從按下到移動這一系列的動作供汛,不同的View都能接受的話枪汪,那整個界面就會非秤磕拢混亂,而且邏輯很復(fù)雜雀久。
接下來我提出這三個問題:

  • 某一個事件從屏幕一直傳遞到View上這一過程的大致流程是怎樣的宿稀?
  • 前面說了事件分發(fā)的其實是事件序列。那么同一個序列里那么多事件赖捌,是怎樣的機制只交給一個View的祝沸?
  • 我們平時在應(yīng)用開發(fā)時,在外部給View設(shè)置的的OnClick OnLongClick 的監(jiān)聽越庇,是在哪里被View處理的罩锐?

問題一:事件傳遞的流程是怎樣的?

Android中的View是樹狀結(jié)構(gòu)悦荒,如下圖所示:

image

每一個Activity內(nèi)部都包含一個Window用來管理要顯示的視圖唯欣。而Window是一個抽象類,其具體實現(xiàn)是 PhoneWindow類搬味。DecovrView作為PhoneWindow的一個內(nèi)部類境氢,實際管理著具體視圖的顯示。他是FrameLayout的子類碰纬,盛放著我們的標(biāo)題欄和根視圖萍聊。我們自己寫的一些列View和ViewGroup都是由他來管理的。因此事件分發(fā)的時候悦析,頂層的這些“大View”們實際上是不會對事件有任何操作的寿桨,他們只是把事件不斷的向下遞交,直到我們可以使用這些事件强戴。

所以亭螟,事件自頂向下的傳遞過程應(yīng)該是這樣的:

Activity(不處理)-> 根View -> 一層一層ViewGroup(如果有的話) -> 子View

如果傳遞到最后我們的子View們沒有處理這一事件怎么辦呢?這時候就會原路返回骑歹,最終傳遞給Activity预烙。只有當(dāng)Activity也沒有處理這一事件時,這一事件才會被丟棄道媚。

Activity(不處理則丟棄) <- 根View <- 一層一層ViewGroup(如果有的話) <- 子View

具體在傳遞事件的時候扁掸,是由以下三個方法來控制的:

  • dispatchTouchEvent : 分發(fā)事件
  • onInterceptTouchEvent : 攔截事件
  • onTouchEvent : 消費事件

這三個方法有一個共同點,就是他們具體是否執(zhí)行了自己的功能(分發(fā)最域、攔截谴分、消費)完全由自己的返回值來確定,返回true就表示自己完成了自己的功能(分發(fā)镀脂、攔截牺蹄、消費)。不同之處除了功能外薄翅,還有使用的場景沙兰。dispatchTouchEvent()和onTouchEvent()這兩個方法虑省,無論是Activity ViewGroup 還是View,都會被用到。而onInterceptTouchEvent()方法因為只是為了攔截事件僧凰,那么Activity和View一個在最頂層探颈,一個在最底層,也就沒必要使用了训措。因此在View 和 Activity中是沒有onInterceptTouchEvent()方法的伪节。

我這里自定義幾個ViewGroup和View,分別重寫他們的這些方法绩鸣,在重寫的時候打上log怀大。在不添加任何監(jiān)聽(即沒有View消費事件)的條件下看一下運行結(jié)果:

點擊外部ViewGroup:

image

點擊子View:
image

可以看到,事件分發(fā)首先由ViewGroup的dispatchTouchEvent()方法開始呀闻,先調(diào)用自己的onInterceptTouchEvent()方法判斷是否攔截化借,返回false表示自己沒有攔截,那么接下來直接把事件傳給子View捡多。子View調(diào)用自己的dispatchTouchEvent()方法進行分發(fā)蓖康,因為View沒有onInterceptTouchEvent()方法,所以不存在攔截操作垒手,因此直接將事件交給自己的onTouchEvent()方法消費蒜焊。因為我的子View沒有使用這個事件,因此onTouchEvent()方法直接返回了false表示自己沒有消費科贬,那么這個事件此時就算是傳到底了泳梆。因為自己沒有消費,因此自己就沒有分發(fā)出去榜掌,那么子View的dispatchTouchEvent()方法返回false优妙,把這個事件交還給上一層的ViewGroup。ViewGroup發(fā)現(xiàn)這個事件沒有子View消費憎账,那么就自己動手吧套硼!將事件傳給自己的onTouchEvent()方法消費∈蟾纾可是ViewGroup也沒有消費熟菲,那么onTouchEvent()方法只能是再返回false了看政。同理朴恳,ViewGroup自己沒有消費事件,因此他的dispatchTouchEvent()方法也返回了false允蚣。這段文字說得可能有點亂于颖,那么就貼一張圖來演示一下:(圖中紅色箭頭表示事件自頂向下分發(fā)的過程,黃色則表示自底向上返回的過程)

image

接下來嚷兔,我在子View上添加OnClick監(jiān)聽森渐,再看一下點擊子View時的運行結(jié)果:

image

乍一看做入,呀,怎么重復(fù)打印了兩遍log?其實并不是哪里寫錯了同衣。前面我說了竟块,事件分發(fā)分發(fā)的是一個事件序列,我添加了點擊事件耐齐,那么我就要消費點擊事件浪秘。而點擊事件其實是要分成兩個事件的,即ACTION_DOWN + ACTION_UP ,只有這樣才算是一次點擊事件埠况。因此打印了“兩遍”log其實是先打印了ACTION_DOWN的分發(fā)流程耸携,再打印了一遍ACTION_UP的分發(fā)流程,因此會看到最后一行打印了click事件辕翰。即夺衍,click事件是在ACTION_UP事件發(fā)生后才發(fā)生的。
然后看看各個方法的返回值喜命。果然由于我的子View明確表示要消費這個事件序列沟沙,因此從ACTION_DOWN開始的所有事件就都交給他消費了。所以子View的onTouchEvent的返回值為true壁榕,表示自己需要消費這個事件尝胆,然后他的dispatchTouchEvent也返回了true,表示這一事件被自己分發(fā)了护桦。既然自己的子View消費了事件含衔,ViewGroup就認為這一事件是被自己分發(fā)了,因此他的dispatchTouchEvent也就返回了true二庵。還是來一張圖更清楚一點:

image

最后贪染,我在上一步的基礎(chǔ)上,給ViewGroup的onInterceptTouchEvent()方法返回值強行改為true催享,表示事件傳到這一層的時候就被攔截了杭隙,看一下log:

image

果然,雖然我要在子View消費事件因妙,但是事件在傳到子View之前就被ViewGroup攔截了痰憎,那么事件就只會由ViewGroup來消費了,所以ViewGroup就把事件傳給了自己的onTouchEvent()來消費攀涵。再來一張圖:

image

綜上铣耘,事件分發(fā)的大致流程就是這樣。

問題二:如何保證統(tǒng)一序列的事件都交給一個View來處理

先上結(jié)論:在傳遞過程中以故,只要有一個View主動去消費了第一個事件(ACTION_DOWN)蜗细,那么ViewGroup會將這個View保存起來,之后同一事件序列的其他事件都直接交給這個View來處理。具體怎么操作炉媒,需要看一下源碼:

//這是ViewGroup  dispatchTouchEvent()的源碼:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //省略前面一部分無關(guān)代碼

        //handled是返回的結(jié)果踪区,表示是否被分發(fā),默認當(dāng)然是
        boolean handled = false;

        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 判斷一下是不是ACTION_DOWN吊骤,如果是的話缎岗,代表一個新的事件序列來臨了
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //要注意一下這兩個方法,在這里會做一下相當(dāng)于是“清零”的操作
                //在這里包含了諸如mFirstTouchTarget=null這樣的初始化操作
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // intercepted是用來記錄是否被攔截的結(jié)果
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // 沒有mFirstTouchTarget白粉,同時事件為非ACTION_DOWN密强,那么就算要在這里攔截了
                intercepted = true;
            }

            //忽略部分攔截相關(guān)的代碼

            //這兩個對象記一下,后面會碰到
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
              // 這里就開始對事件類型區(qū)分了蜗元,如果是ACTION_DOWN或渤,那么就算是一個新的事件序列開始
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                      // 準(zhǔn)備一下,接下來開始遍歷自己的子View們
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        // 獲取到點擊的坐標(biāo)奕扣,用來從子View中篩選出點擊到的VIEW
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);

                        // 按從后向前的順序開始遍歷子View們
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        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);

                            // 其實篩選只是將不合適的View們過濾掉
                            //一個一個continue就表示在發(fā)現(xiàn)View不合適的時候直接進入下一次循環(huán)
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //終于找到了合適的子View,注意這里將子View封裝為一個target
                            //要是返回的結(jié)果不為空就跳出循環(huán)
                            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;
                            }

                            //就算返回結(jié)果為空也沒關(guān)系薪鹦,在這里繼續(xù)遞歸的調(diào)用子View的dispatchTransformedTouchEvent()
                            resetCancelNextUpFlag(child);
                            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;
                            }
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    //沒有找到要接受事件的View
                    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;
                    }
                }
            }

            //接下來就是對于非ACTION_DOWN事件的分發(fā)了,這里有兩種情況
            if (mFirstTouchTarget == null) {
                // 1.壓根就沒有找到要接受事件的view惯豆,或者被攔截了池磁,調(diào)用了自身的dispatchTransformedTouchEvent()且穿了一個null的View進去,這樣有什么用呢楷兽?需要后面分析dispatchTransformedTouchEvent()
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //2.有View接受ACTION_DOWN事件地熄,那么這個View也將接受其余的事件
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        //alreadyDispatchedToNewTouchTarget這個變量在前面View接受ACTION_DOWN事件時設(shè)為了true
                        //同時這個mFirstTouchTarget也就是那個View封裝好的target
                        //那么這個返回值handled就為true
                        handled = true;
                    } else {
                        //對于非ACTION_DOWN事件,依然是遞歸調(diào)用dispatchTransformedTouchEvent
                        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;
                }
            }

            // 處理ACTION_UP和ACTION_CANCEL
            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()的源碼:


//前面在分析dispatchTouchEvent()的時候發(fā)現(xiàn)有多處調(diào)用了這個dispatchTransformedTouchEvent(),而且有的地方傳來的第三個參數(shù)是null
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        //處理ACTION_CANCEL
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        //忽略部分代碼……

        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    //如果傳來的參數(shù)child為空時芯杀,調(diào)用自身dispatchTouchEvent()
                    handled = super.dispatchTouchEvent(event);
                } else {
                  //不為空端考,那么就調(diào)用他的dispatchTouchEvent()
                    handled = child.dispatchTouchEvent(event);
                }
                return handled;
            }
        } else {
          //...
        }
        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;
    }

上面是對dispatchTouchEvent()和dispatchTransformedTouchEvent()的分析,看起來有點亂揭厚,這里梳理一下:

  • 首先明確一點却特,事件分發(fā)是從ViewGroup的dispatchTouchEvent()開始的
  • ViewGroup在遇到一個新的事件序列,即事件ACTION_DOWN時筛圆,開始遍歷自己的所有子View,找到需要接收到事件的View
  • 無論是否找到裂明,都會調(diào)用dispatchTransformedTouchEvent()方法,區(qū)別在于如果找到了,那么在這個方法中傳入的是那個View太援,否則就是null
  • dispatchTransformedTouchEvent()方法中第三個參數(shù)child為空時闽晦,會調(diào)用父類的dispatchTouchEvent()方法,否則會調(diào)用那個child的dispatchTouchEvent()方法提岔∠沈龋總而言之,都會去調(diào)用View類的dispatchTouchEvent()方法唧垦。
  • dispatchTransformedTouchEvent()方法是進行具體的事件分發(fā)捅儒,除了OnClick()等事件外液样,onTouchEvent()方法就是在這里調(diào)用的
  • 只要找到了要接受事件的View,就會將他封裝為一個target,保存起來振亮,后續(xù)的其他事件都由他來接受

問題三:OnClick OnLongClick等對外的監(jiān)聽是在哪里處理的巧还?

首先想一想一個很簡單的邏輯,OnClick事件是先ACTION_DOWN之后再ACTION_UP,所以必定要在onTouchEvent()處理坊秸。同理麸祷,OnLongClick是在保持ACTION_DOWN一段時間后發(fā)生,因此也要在onTouchEvent()中處理褒搔〗纂梗看看源碼,發(fā)現(xiàn)果然是在這里:

//以下源碼均為忽略了不想關(guān)部分星瘾,只保留了重點
public boolean onTouchEvent(MotionEvent event) {
    //...
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // 處理click
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                }
                break;

            case MotionEvent.ACTION_DOWN:
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    //...
                } else {
                    // 處理longclick
                    setPressed(true, x, y);
                    checkForLongClick(0, x, y);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                //...
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_MOVE:
                //...
                break;
        }

        return true;
    }

    return false;
}

根據(jù)前面的分析走孽,在View的dispatchTouchEvent()方法中,會對


public boolean dispatchTouchEvent(MotionEvent event) {
    //...
    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    //...

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //只要獲取到的ListenerInfo不為空琳状,就說明我們設(shè)置了監(jiān)聽磕瓷,那么就會認為我們想讓這個View處理所有事件
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {//所以會在這里執(zhí)行onTouch()
            result = true;
        }

        //而如果沒有處理,那么再調(diào)用onTouchEvent(),直到onTouchEvent()也返回false才會認為該View不消費事件
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    return result;
}

可以看到念逞,在View的dispatchTouchEvent()方法中困食,會通過查看是否由設(shè)置監(jiān)聽器等方法來判斷是否要消費事件。onTouchEvent()方法永遠會調(diào)用翎承,click和longclick都在這里面硕盹。而無論內(nèi)部如何處理,只要返回了true叨咖,就會認為消費了這一事件瘩例。

分析就到這了,作為一個小菜雞甸各,分析過程難免有些錯誤和疏漏仰剿,歡迎在評論區(qū)告訴我

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市痴晦,隨后出現(xiàn)的幾起案子南吮,更是在濱河造成了極大的恐慌,老刑警劉巖誊酌,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件部凑,死亡現(xiàn)場離奇詭異,居然都是意外死亡碧浊,警方通過查閱死者的電腦和手機涂邀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箱锐,“玉大人比勉,你說我怎么就攤上這事。” “怎么了浩聋?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵观蜗,是天一觀的道長。 經(jīng)常有香客問我衣洁,道長墓捻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任坊夫,我火速辦了婚禮砖第,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘环凿。我一直安慰自己梧兼,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布智听。 她就那樣靜靜地躺著袱院,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞭稼。 梳的紋絲不亂的頭發(fā)上忽洛,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音环肘,去河邊找鬼欲虚。 笑死,一個胖子當(dāng)著我的面吹牛悔雹,可吹牛的內(nèi)容都是我干的复哆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼腌零,長吁一口氣:“原來是場噩夢啊……” “哼梯找!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起益涧,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤锈锤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闲询,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體久免,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年扭弧,在試婚紗的時候發(fā)現(xiàn)自己被綠了阎姥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸽捻,死狀恐怖呼巴,靈堂內(nèi)的尸體忽然破棺而出泽腮,到底是詐尸還是另有隱情,我是刑警寧澤衣赶,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布诊赊,位于F島的核電站,受9級特大地震影響屑埋,放射性物質(zhì)發(fā)生泄漏豪筝。R本人自食惡果不足惜痰滋,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一摘能、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敲街,春花似錦团搞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峻黍,卻和暖如春复隆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姆涩。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工挽拂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骨饿。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓亏栈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宏赘。 傳聞我的和親對象是個殘疾皇子绒北,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容