Android View的事件分發(fā)機(jī)制

準(zhǔn)備了一陣子民镜,一直想寫一篇事件分發(fā)的文章總結(jié)一下弄唧,這個(gè)知識點(diǎn)實(shí)在是太重要了适肠。
一個(gè)應(yīng)用的布局是豐富的,有TextView候引,ImageView侯养,Button等,這些子View的外層還有ViewGroup澄干,如RelativeLayout逛揩,LinearLayout。作為一個(gè)開發(fā)者麸俘,我們會思考辩稽,當(dāng)點(diǎn)擊一個(gè)按鈕,Android系統(tǒng)是怎樣確定我點(diǎn)的就是按鈕而不是TextView的从媚?然后還正確的響應(yīng)了按鈕的點(diǎn)擊事件逞泄。內(nèi)部經(jīng)過了一系列什么過程呢?
先鋪墊一些知識能更加清晰的理解事件分發(fā)機(jī)制:

  1. 通過setContentView設(shè)置的View就是DecorView的子view拜效,即DecorView是父容器喷众。
  2. 點(diǎn)擊屏幕時(shí),在手指按下和抬起間紧憾,會產(chǎn)生很多事件到千,down…move…move…up,中間會有很多的move事件稻励,這一系列的事件為一個(gè)事件序列
  3. dispatchTouchEvent方法用于分發(fā)事件
  4. onInterceptTouchEvent方法用于攔截事件
  5. onTouchEvent方法用于處理事件

當(dāng)一個(gè)點(diǎn)擊事件(MotionEvent)產(chǎn)生后父阻,事件最先傳遞給當(dāng)前的界面(Activity)愈涩,這點(diǎn)是很好理解的望抽。 Activity再將事件傳遞給窗口(Window),然后Window將事件傳遞給頂級View(DecorView)履婉。此時(shí)煤篙,事件已經(jīng)到達(dá)了View了。之后頂級View就會按照事件分發(fā)機(jī)制去分發(fā)事件毁腿。具體是這樣的:

對于一個(gè)根ViewGroup來說辑奈,點(diǎn)擊事件產(chǎn)生后苛茂,首先會傳遞給它,這時(shí)它的 dispatchTouchEvent 方法就會被調(diào)用鸠窗,如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當(dāng)前事件妓羊,接著事件就會交給這個(gè)ViewGroup處理,即它的onTouchEvent方法就會被調(diào)用稍计。如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回false躁绸,就表示它不攔截當(dāng)前事件,這時(shí)當(dāng)前事件就會繼續(xù)傳遞給它的子元素臣嚣,接著子元素的dispatchTouchEvent方法就會被調(diào)用净刮,如此反復(fù)直到事件被最終處理。
如果一個(gè)View的onTouchEvent方法返回false硅则,那么它的父容器的onTouchEvent方法會被調(diào)用淹父,如果它的父容器的onTouchEvent方法還是返回false,那就繼續(xù)往上拋怎虫,當(dāng)所有的元素都不處理這個(gè)事件暑认,那么這個(gè)事件會最終傳遞給Activity處理,即Activity的onTouchEvent方法會被調(diào)用揪垄。

好了穷吮,現(xiàn)在已經(jīng)鋪墊了基礎(chǔ),那么接下來就從源碼的角度來分析事件分發(fā)機(jī)制饥努。
當(dāng)然是從Activity的dispatchTouchEvent方法開始分析捡鱼。源碼如下:

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

如果當(dāng)前事件是down的話,就調(diào)用onUserInteraction方法酷愧,onUserInteraction是一個(gè)空方法驾诈,我們可以暫時(shí)不搭理。然后調(diào)用getWindow方法獲取到當(dāng)前Activity關(guān)聯(lián)的Window溶浴,Window再調(diào)用superDispatchTouchEvent方法將事件傳入進(jìn)行分發(fā)乍迄。 如果superDispatchTouchEvent方法返回true的話, view已經(jīng)處理了事件士败。整個(gè)事件循環(huán)結(jié)束闯两。如果返回false,沒有view處理這個(gè)事件谅将。事件往上拋漾狼,那就Activity自己處理了,即Activity的onTouchEvent方法會被調(diào)用饥臂。
因?yàn)橄胍朗录恼麄€(gè)分發(fā)過程逊躁,現(xiàn)在關(guān)注的是Window的superDispatchTouchEvent方法,那么就跟進(jìn)去看看:

public abstract boolean superDispatchTouchEvent(MotionEvent event);

Window是一個(gè)抽象類隅熙,superDispatchTouchEvent是一個(gè)抽象的方法稽煤,那么我們必須要找到window的實(shí)現(xiàn)類才行核芽,可是茫茫人海怎么找呢?看到window類的說明就明白了

* <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window

意思是Window存在唯一的實(shí)現(xiàn)是android.view.PhoneWindow
那么PhoneWindow里的superDispatchTouchEvent方法就是我們要找的信息酵熙,如下:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

直接將事件傳遞給了DecorView轧简。這時(shí)事件已經(jīng)是到達(dá)View了哦。
那么跟進(jìn)DecorView的superDispatchTouchEvent方法看看匾二,如下:

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

內(nèi)部調(diào)用了父類的dispatchTouchEvent方法吉懊,那么DecorView的父類是什么呢?DecorView肯定是View的假勿,那么剛才開篇提到借嗽,我們通過setContentView設(shè)置的View,是DecorView的子View转培。那么更加準(zhǔn)確的說DecorView是一個(gè)ViewGroup恶导。

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

可以看到DecorView是繼承自FrameLayout,F(xiàn)rameLayout是ViewGroup浸须,也就是說DecorView是一個(gè)ViewGroup惨寿。
那么現(xiàn)在只需要關(guān)注ViewGroup的dispatchTouchEvent方法。繼續(xù)前進(jìn)
ViewGroup的事件分發(fā)
ViewGroup的dispatchTouchEvent方法如下:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

        //代碼省略

        // 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 (!canceled && !intercepted) {

        //代碼省略

                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 = buildOrderedChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(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);
        }

        //代碼省略

        return handled;
}

代碼比較長删窒,一點(diǎn)一點(diǎn)分析裂垦,先看到一開始的判斷

if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)

mFirstTouchTarget != null的意義是ViewGroup不攔截事件并將事件交由子元素處理,先這樣記著肌索,這從后面的addTouchTarget方法可以得出結(jié)論的蕉拢。
然后又會來到這個(gè)if判斷。

if (!disallowIntercept) {
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action); // restore action in case it was changed
}

那我們看看disallowIntercept诚亚。而disallowIntercept的賦值過程中晕换,有一個(gè) FLAG_DISALLOW_INTERCEPT 標(biāo)記位

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

這個(gè) FLAG_DISALLOW_INTERCEPT 標(biāo)記位是可以通過requestDisallowInterceptTouchEvent方法來設(shè)置的。
回到if (!disallowIntercept)的判斷站宗,進(jìn)入這個(gè)if判斷后闸准,就會來到

intercepted = onInterceptTouchEvent(ev);

調(diào)用onInterceptTouchEvent方法,詢問ViewGroup是否攔截事件梢灭。
讀到這里夷家,可以回憶下開篇時(shí)鋪墊的結(jié)論,對于ViewGroup敏释,點(diǎn)擊事件產(chǎn)生后库快,首先會傳遞給它,這時(shí)它的 dispatchTouchEvent 方法就會被調(diào)用颂暇,接著會調(diào)用它的onInterceptTouchEvent方法缺谴,如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當(dāng)前事件但惶,接著事件就會交給這個(gè)ViewGroup處理耳鸯,即它的onTouchEvent方法就會被調(diào)用湿蛔。如果返回false表示不攔截,通常ViewGroup也是不攔截事件的县爬。
那現(xiàn)在先分析不攔截的情況阳啥,不攔截那就好辦了的。經(jīng)過一系列的判斷财喳,就會來到一個(gè)for循環(huán)遍歷察迟。

for (int i = childrenCount - 1; i >= 0; i--)

這時(shí)ViewGroup開始分發(fā)傳遞事件,遍歷子元素了耳高。
首先肯定需要過濾掉一些無關(guān)點(diǎn)擊事件的子元素的扎瓶,判斷子元素是否能夠接收點(diǎn)擊事件,點(diǎn)擊事件的坐標(biāo)是否落在子元素區(qū)域內(nèi)泌枪。

if (!canViewReceivePointerEvents(child)
        || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}

如果不能夠接收點(diǎn)擊事件或者點(diǎn)擊事件的坐標(biāo)沒有落在子元素區(qū)域概荷,就會跳出當(dāng)前循環(huán),繼續(xù)遍歷下一個(gè)子元素碌燕。這下就知道了Android系統(tǒng)為什么能夠知道點(diǎn)擊的是Button而不是TextView误证,其實(shí)內(nèi)部就只是做了一個(gè)判斷嘛。
那么繼續(xù)分析修壕,子元素符合以上兩個(gè)條件后愈捅,就將事件傳遞給這個(gè)子元素。會來到了這個(gè)判斷慈鸠。

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

執(zhí)行dispatchTransformedTouchEvent方法蓝谨,將子元素傳進(jìn)去。這個(gè)方法很重要青团,那么跟進(jìn)看看

/**
 * Transforms a motion event into the coordinate space of a particular child view,
 * filters out irrelevant pointer ids, and overrides its action if necessary.
 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 */
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) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    //代碼省略
}

我們看到child像棘!=null的情況,如果子元素不為空壶冒,調(diào)用子元素的dispatchTouchEvent方法繼續(xù)分發(fā)事件缕题,同時(shí)返回處理結(jié)果布爾值,這時(shí)就將事件傳遞到了子View處理胖腾。完成了一輪的事件分發(fā)烟零。這個(gè)方法先到這里就好。
再看回ViewGroup的dispatchTouchEvent方法咸作,如果dispatchTransformedTouchEvent方法返回true的話锨阿,這時(shí)事件已經(jīng)傳遞給子元素處理,ViewGroup已經(jīng)不管這個(gè)事件了记罚。 那么就會進(jìn)入if語句墅诡,最后會來到addTouchTarget方法,這個(gè)方法之前是提到過的桐智,用于mFirstTouchTarget標(biāo)記位的賦值末早。
那跟進(jìn)這個(gè)方法看看

/**
 * Adds a touch target for specified child to the beginning of the list.
 * Assumes the target child is not already present.
 */
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

其實(shí)就是讓mFirstTouchTarget指向子元素烟馅。
執(zhí)行完這個(gè)addTouchTarget方法后,最終會到break語句然磷,那么就會跳出整個(gè)for循環(huán)體郑趁。ViewGroup結(jié)束分發(fā)過程!
又回到dispatchTransformedTouchEvent方法姿搜,如果dispatchTransformedTouchEvent方法返回false寡润,那么if語句的一大段代碼都不執(zhí)行了,而是回到for循環(huán)繼續(xù)遍歷子元素進(jìn)行分發(fā)舅柜。如此重復(fù)完成事件的傳遞過程梭纹。
現(xiàn)在分析ViewGroup攔截事件的情況,如果ViewGroup攔截事件的話致份,那么就會進(jìn)入以下這個(gè)判斷

if (mFirstTouchTarget == null) {
   // No touch targets so treat this as an ordinary view.
   handled = dispatchTransformedTouchEvent(ev, canceled, null,
   TouchTarget.ALL_POINTER_IDS);
}

注意到dispatchTransformedTouchEvent方法的第三個(gè)參數(shù)child傳入的是null栗柒,那么就是在dispatchTransformedTouchEvent方法中走以下的語句

if (child == null) { handled = super.dispatchTouchEvent(event);}

而ViewGroup是繼承自View的,那么就是ViewGroup自己處理事件了知举。這點(diǎn)我們以下分析了View的事件分發(fā)過程就能搞明白了瞬沦。
以上就是ViewGroup的事件分發(fā)
那么現(xiàn)在分析已經(jīng)將事件傳遞給了子View的情況,View繼續(xù)調(diào)用dispatchTouchEvent方法雇锡,那我們看看View的dispatchTouchEvent方法逛钻。
View的事件分發(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 (onFilterTouchEventForSecurity(event)) {
        //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;
}

相比于ViewGroup的dispatchTouchEvent方法,View的dispatchTouchEvent方法代碼量少了锰提。也相對簡單些了曙痘。 首先會來到如下判斷:

if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event))

li變量在哪里被賦值的呢?通常是在setOnClickListener方法或setOnTouchListener方法的時(shí)候立肘。

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
}

而這個(gè)getListenerInfo()如下:

ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

ListenerInfo是一個(gè)內(nèi)部類边坤,里面存放的是各種監(jiān)聽事件的引用。
之后會判斷如下條件:

li.mOnTouchListener != null

同理只要setOnTouchListener方法設(shè)置了谅年,這個(gè)引用就不空茧痒。這些都是好理解的。那關(guān)鍵到了融蹂,

li.mOnTouchListener.onTouch(this, event)

到了最后一個(gè)條件旺订。這個(gè)onTouch方法是我們?nèi)?shí)現(xiàn)的,它也返回一個(gè)布爾值超燃,如果返回true的話区拳,那么就會進(jìn)入這個(gè)if判斷最終返回true,跳出整個(gè)方法意乓,那么我們可以看到接下來的onTouchEvent方法是不會得到執(zhí)行的樱调。 也就是onTouch的執(zhí)行在onTouchEvent之前。那么如果我們也調(diào)用了setOnClickListener方法監(jiān)聽點(diǎn)擊事件的話,onClick方法是在哪里調(diào)用的呢笆凌?我們有理由相信是在onTouchEvent方法里調(diào)用的圣猎。那么就跟進(jì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:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;

   //代碼省略

                        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();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }

             //代碼省略  

                break;
        }

        return true;
    }

    return false;
}

只要CLICKABLE或LONG_CLICKABLE不空菩颖, 就會處理這個(gè)事件,然而怎么保證CLICKABLE或LONG_CLICKABLE不空呢为障?其實(shí)細(xì)心的你會發(fā)現(xiàn)晦闰,剛才上面貼出的setOnClickListener源代碼中,會將CLICKABL屬性設(shè)置會true

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

這樣就能進(jìn)入if判斷去處理這個(gè)事件了鳍怨,之后就會來到performClick()方法呻右,應(yīng)該就是它了,跟進(jìn)去看看吧鞋喇。

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

熟悉接口回調(diào)機(jī)制的你声滥,一定也讀懂了performClick()方法的源碼,

li.mOnClickListener.onClick(this);

是在執(zhí)行這行代碼時(shí)侦香,調(diào)用了我們熟悉的onClick方法
以上就是View的事件分發(fā)機(jī)制落塑。
此時(shí)已經(jīng)將事件分發(fā)機(jī)制分析完了,由于我的技術(shù)的原因罐韩,駕馭的不好憾赁,有些關(guān)鍵點(diǎn)還是沒分析清楚,但我相信學(xué)完了這篇文章能讓我和你都對事件分發(fā)機(jī)制的實(shí)現(xiàn)有一個(gè)大致的認(rèn)識散吵,有這個(gè)已經(jīng)可以了龙考,之后還可以一點(diǎn)點(diǎn)去強(qiáng)化鍛煉,深入理解事件分發(fā)機(jī)制矾睦。才能為自定義控件鋪墊良好的基礎(chǔ)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晦款,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子枚冗,更是在濱河造成了極大的恐慌缓溅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赁温,死亡現(xiàn)場離奇詭異肛宋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)束世,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門酝陈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人毁涉,你說我怎么就攤上這事沉帮。” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵穆壕,是天一觀的道長待牵。 經(jīng)常有香客問我,道長喇勋,這世上最難降的妖魔是什么缨该? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮川背,結(jié)果婚禮上苹祟,老公的妹妹穿的比我還像新娘苦丁。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布厨喂。 她就那樣靜靜地躺著领猾,像睡著了一般跳座。 火紅的嫁衣襯著肌膚如雪杯巨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天练般,我揣著相機(jī)與錄音矗漾,去河邊找鬼。 笑死薄料,一個(gè)胖子當(dāng)著我的面吹牛缩功,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播都办,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼嫡锌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琳钉?” 一聲冷哼從身側(cè)響起势木,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歌懒,沒想到半個(gè)月后啦桌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡及皂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年甫男,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片验烧。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡板驳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碍拆,到底是詐尸還是另有隱情若治,我是刑警寧澤慨蓝,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站端幼,受9級特大地震影響礼烈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜婆跑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一此熬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滑进,春花似錦犀忱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽近哟。三九已至驮审,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吉执,已是汗流浹背疯淫。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戳玫,地道東北人熙掺。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像咕宿,于是被迫代替她去往敵國和親币绩。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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