Android設(shè)計(jì)模式04-責(zé)任鏈模式(與事件分發(fā))

一阱驾、責(zé)任鏈模式

責(zé)任鏈模式是一種行為模式,為請(qǐng)求創(chuàng)建一個(gè)接收者的對(duì)象鏈.這樣就避免,一個(gè)請(qǐng)求鏈接多個(gè)接收者的情況.進(jìn)行外部解耦.類似于單向鏈表結(jié)構(gòu)攒驰。

優(yōu)點(diǎn):

1. 降低耦合度。它將請(qǐng)求的發(fā)送者和接收者解耦。

2. 簡(jiǎn)化了對(duì)象瑟啃。使得對(duì)象不需要知道鏈的結(jié)構(gòu)。

3. 增強(qiáng)給對(duì)象指派職責(zé)的靈活性揩尸。通過(guò)改變鏈內(nèi)的成員或者調(diào)動(dòng)它們的次 序蛹屿,允許動(dòng)態(tài)地新增或者刪除責(zé)任。

4. 增加新的請(qǐng)求處理類很方便岩榆。

缺點(diǎn):

1. 不能保證請(qǐng)求一定被接收错负。

2. 系統(tǒng)性能將受到一定影響,而且在進(jìn)行代碼調(diào)試時(shí)不太方便勇边,可能會(huì)造成循環(huán)調(diào)用犹撒。

3. 可能不容易觀察運(yùn)行時(shí)的特征,有礙于除錯(cuò)粒褒。

在Android事件分發(fā)機(jī)制是責(zé)任鏈模式最典型的應(yīng)用:

dispatchTouchEvent的,就是責(zé)任鏈中的將事件交給下一級(jí)處理的.

onInterceptTouchEvent ,就是責(zé)任鏈中,處理自己處理事務(wù)的方法.

onTouchEvent 是責(zé)任鏈中 事件上報(bào)的事件鏈识颊。

下面我們來(lái)通過(guò)Android事件分發(fā)機(jī)制來(lái)感悟一下責(zé)任鏈模式在Android中的應(yīng)用。

二奕坟、Android 事件分發(fā)傳遞機(jī)制

1. View事件傳遞分發(fā)層級(jí)結(jié)構(gòu)

a). 事件收集之后最先傳遞給 Activity祥款, 然后依次向下傳遞,大致如下:

Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View

這樣的事件分發(fā)機(jī)制邏輯非常清晰月杉,可是刃跛,你是否注意到一個(gè)問(wèn)題?如果最后分發(fā)到View沙合,如果這個(gè)View也沒(méi)有處理事件怎么辦,就這樣讓事件浪費(fèi)掉跌帐?當(dāng)然不會(huì)啦首懈。

b). 如果沒(méi)有任何View消費(fèi)掉事件,那么這個(gè)事件會(huì)按照反方向回傳谨敛,最終傳回給Activity究履,如果最后 Activity 也沒(méi)有處理,本次事件才會(huì)被拋棄:

Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View

可以看到脸狸,這是一個(gè)非常經(jīng)典的責(zé)任鏈模式最仑,如果我能處理就攔截下來(lái)自己干,如果自己不能處理或者不確定就交給責(zé)任鏈中下一個(gè)對(duì)象炊甲。 這種設(shè)計(jì)是非常精巧的泥彤,上層View既可以直接攔截該事件,自己處理卿啡,也可以先詢問(wèn)(分發(fā)給)子View吟吝,如果子View需要就交給子View處理,如果子View不需要還能繼續(xù)交給上層View處理颈娜。既保證了事件的有序性剑逃,又非常的靈活浙宜。

View點(diǎn)擊事件分發(fā)有三個(gè)關(guān)鍵流程方法:

1.dispatchTouchEvent:事件下發(fā) --- View和ViewGroup都有的方法

2.onInterceptTouchEvent:攔截下發(fā)的事件,并交給自己OnTouchEvent處理處理 ---ViewGroup才有的方法

3.onTouchEvent:事件上報(bào) --- View和ViewGroup都有的方法

以下是不同層級(jí)對(duì)事件的分發(fā)、攔截和消費(fèi)的功能表:

image

可以看到 Activity 和 View 都是沒(méi)有事件攔截的:

a). Activity 作為原始的事件分發(fā)者蛹磺,如果 Activity 攔截了事件會(huì)導(dǎo)致整個(gè)屏幕都無(wú)法響應(yīng)事件粟瞬,這肯定不是我們想要的效果。

b). View最為事件傳遞的最末端萤捆,要么消費(fèi)掉事件裙品,要么不處理進(jìn)行回傳,根本沒(méi)必要進(jìn)行事件攔截鳖轰。

下圖是點(diǎn)擊View清酥,事件傳遞但是都沒(méi)有被處理,生成的一個(gè)完整的事件分發(fā)流程圖:

image

如果事件被View處理了蕴侣,那么事件分發(fā)流程圖應(yīng)該如下:

image

如果事件被ViewGroup攔截處理了焰轻, 那么事件分發(fā)流程圖應(yīng)該如下:

image

從上面的流程,我們可以概括Android的事件分發(fā)機(jī)制為:責(zé)任鏈模式昆雀,事件層層傳遞辱志,直到被消費(fèi)。

三狞膘、Q&A

上面我們講解了一下Android的事件分發(fā)機(jī)制揩懒,可能很多人會(huì)有疑惑,下面我們針對(duì)部分疑惑進(jìn)行分析和說(shuō)明:

1. 為什么 View 會(huì)有 dispatchTouchEvent ?

答:我們知道 View 可以注冊(cè)很多事件監(jiān)聽器挽封,例如:?jiǎn)螕羰录?onClick)已球、長(zhǎng)按事件(onLongClick)、觸摸事件(onTouch)辅愿,并且View自身也有 onTouchEvent 方法智亮,那么問(wèn)題來(lái)了,這么多與事件相關(guān)的方法應(yīng)該由誰(shuí)管理点待?毋庸置疑就是 dispatchTouchEvent阔蛉,所以 View 也會(huì)有事件分發(fā)。

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

        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)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            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;
    }

2. View事件分發(fā)時(shí)各個(gè)方法調(diào)用順序是怎樣的癞埠?

a). 單擊事件(onClickListener) 需要兩個(gè)兩個(gè)事件(ACTION_DOWN 和 ACTION_UP )才能觸發(fā)状原,如果先分配給onClick判斷,等它判斷完再交由其他相應(yīng)時(shí)間顯然是不合理的苗踪,會(huì)造成 View 無(wú)法響應(yīng)其他事件颠区,應(yīng)該最后調(diào)用。(所以此調(diào)用順序最后)

b). 長(zhǎng)按事件(onLongClickListener) 同理通铲,也是需要長(zhǎng)時(shí)間等待才能出結(jié)果瓦呼,肯定不能排到前面,但因?yàn)椴恍枰狝CTION_UP,應(yīng)該排在 onClick 前面央串。(onLongClickListener > onClickListener)

c). 觸摸事件(onTouchListener) 如果用戶注冊(cè)了觸摸事件磨澡,說(shuō)明用戶要自己處理觸摸事件了,這個(gè)應(yīng)該排在最前面质和。(最前)

d). View自身處理(onTouchEvent) 提供了一種默認(rèn)的處理方式稳摄,如果用戶已經(jīng)處理好了,也就不需要了饲宿,所以應(yīng)該排在 onClickListener 后面厦酬。(onTouchListener > onClickListener)

所以事件的調(diào)度順序應(yīng)該是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener

3. ViewGroup 的事件分發(fā)流程又是如何的呢瘫想?

在默認(rèn)的情況下 ViewGroup 事件分發(fā)流程是這樣的仗阅。

a). 判斷自身是否需要(詢問(wèn) onInterceptTouchEvent 是否攔截),如果需要国夜,調(diào)用自己的 onTouchEvent减噪。

b). 自身不需要或者不確定,則詢問(wèn) ChildView 车吹,一般來(lái)說(shuō)是調(diào)用手指觸摸位置的 ChildView筹裕。

c). 如果子 ChildView 不需要?jiǎng)t調(diào)用自身的 onTouchEvent。

ViewGroup的dispatchTouchEvent源碼:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            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;
        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.
            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 {
                // 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) {

                // If the event is targeting accessibility 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();
                        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);
                            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;
    }

可能看這么長(zhǎng)的代碼窄驹,我們會(huì)比較懵朝卒,下面我們用偽代碼來(lái)表示一下:

public boolean dispatchTouchEvent(MotionEvent ev) {
     boolean consume = false;
     if (onInterceptTouchEvent(ev)) {
          consume = onTouchEvent(ev);
     } else {
          consume = child.dispatchTouchEvent(ev)
     }
     return consume;
}

這樣我們就能更直白的看懂View事件的傳遞機(jī)制了。

4. ViewGroup將事件分發(fā)給ChildView的機(jī)制

  • ViewGroup分發(fā)事件時(shí)會(huì)遍歷 ChildView乐埠,如果手指觸摸的點(diǎn)在 ChildView 區(qū)域內(nèi)就分發(fā)給這個(gè)View抗斤。當(dāng) ChildView 重疊時(shí),一般會(huì)分配給顯示在最上面的 ChildView丈咐。
  • ViewGroup判斷是否需要攔截瑞眼,主要是根據(jù)onInterruptTouchEvent的返回值進(jìn)行判斷。
  • 在Down事件中將touch事件分發(fā)給ChildView扯罐,如果有ChildView捕獲消費(fèi)了Down事件负拟,就會(huì)對(duì)mFirstTouchTarget進(jìn)行賦值烦衣。mFirstTouchTarget的作用就是記錄消費(fèi)事件的View歹河。
  • 在ViewGroup的dispatchTouchEvent方法中,會(huì)根據(jù)mFirstTouchTarget 是否為 null花吟,決定是自己處理 touch 事件秸歧,還是分發(fā)給子 View。
  • Down事件是touch事件序列的起點(diǎn)衅澈,決定了后續(xù)的事件由誰(shuí)來(lái)消費(fèi)處理键菱。Cancel事件的觸發(fā)場(chǎng)景為:父View先不攔截,但在MOVE事件中又重新攔截今布,此時(shí)子View會(huì)收到一個(gè)Cancel事件经备,

5. ViewGroup 和 ChildView 同時(shí)注冊(cè)了事件監(jiān)聽器(onClick等)拭抬,哪個(gè)會(huì)執(zhí)行?

事件優(yōu)先給 ChildView,會(huì)被 ChildView消費(fèi)掉侵蒙,ViewGroup 不會(huì)響應(yīng)造虎。

四、參考資料

  1. Android事件傳遞機(jī)制分析

  2. Android 事件分發(fā)機(jī)制詳解

  3. 安卓自定義View進(jìn)階-事件分發(fā)機(jī)制原理

  4. 必問(wèn)的事件分發(fā)纷闺,你答得上來(lái)嗎
    轉(zhuǎn)載自‘灰色飄零’ https://www.cnblogs.com/renhui/p/12127680.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末算凿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子犁功,更是在濱河造成了極大的恐慌氓轰,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浸卦,死亡現(xiàn)場(chǎng)離奇詭異署鸡,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)镐躲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門储玫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人萤皂,你說(shuō)我怎么就攤上這事撒穷。” “怎么了裆熙?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵端礼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我入录,道長(zhǎng)蛤奥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任僚稿,我火速辦了婚禮凡桥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚀同。我一直安慰自己缅刽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布蠢络。 她就那樣靜靜地躺著衰猛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刹孔。 梳的紋絲不亂的頭發(fā)上啡省,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼卦睹。 笑死畦戒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的结序。 我是一名探鬼主播兢交,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼笼痹!你這毒婦竟也來(lái)了配喳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凳干,失蹤者是張志新(化名)和其女友劉穎晴裹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體救赐,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涧团,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了经磅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泌绣。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖预厌,靈堂內(nèi)的尸體忽然破棺而出阿迈,到底是詐尸還是另有隱情,我是刑警寧澤轧叽,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布苗沧,位于F島的核電站,受9級(jí)特大地震影響炭晒,放射性物質(zhì)發(fā)生泄漏待逞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一网严、第九天 我趴在偏房一處隱蔽的房頂上張望识樱。 院中可真熱鬧,春花似錦震束、人聲如沸怜庸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)休雌。三九已至灶壶,卻和暖如春肝断,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工胸懈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留担扑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓趣钱,卻偏偏與公主長(zhǎng)得像涌献,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子首有,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354