Android的按鍵分發(fā)--遇到問題后結(jié)合源碼進行分析

前記:

按鍵分發(fā)是android面試的一個重點芒炼,大家有必要好好掌握一下。
在手機上拌蜘,重點考察的是觸摸事件的分發(fā)彼棍,TV上考察的則是對按鍵分發(fā)的掌握情況。

研究的切入點:

客戶的需求:開機向?qū)pp跷乐,在遙控器連接上之后肥败,用戶可以按任意鍵跳轉(zhuǎn)到下一步。


mView 代表整個紅色的跟布局愕提,即LinearLayout馒稍。
跳轉(zhuǎn)代碼如下:只要有按鍵分發(fā)下來,就做跳轉(zhuǎn)的動作浅侨。

mView.setOnKeyListener(new View.OnKeyListener() {
            
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                //跳轉(zhuǎn)到下一個界面
                mHandler.sendEmptyMessageDelayed(MSG_ID_GO_NEXT_STEP, 200);
                return true;
            }
        });

遇到的問題:阿倫反饋OK鍵不能跳轉(zhuǎn)纽谒,但是方向鍵可以跳轉(zhuǎn)!仗颈!
那么我們就帶著這個問題佛舱,來從源碼角度分析為什么OK鍵不可,上下左右可挨决?
提問時間:大家能看出是什么問題嗎请祖?? (5分鐘)

開始分析:

屏蔽跳轉(zhuǎn)+加log

mView.setOnKeyListener(new View.OnKeyListener() {
            
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                CLog.d(TAG, "onKey keyCode:" + keyCode);
                CLog.d(TAG, "event:" + event.getAction());
                //mHandler.sendEmptyMessageDelayed(MSG_ID_GO_NEXT_STEP, 200);
                return true;
            }
        });

Log分析結(jié)果
首先按OK鍵脖祈,沒有任何打印肆捕。
然后按下鍵,有響應(yīng)盖高,但是僅僅是收到了下鍵的Up事件慎陵。
再按OK鍵,可收到OK鍵的Down+Up事件喻奥。
看起來就有點奇怪了席纽!

提問時間:這就是造成OK鍵無法跳轉(zhuǎn)的原因!撞蚕!大家能看出是什么問題嗎润梯?? (5分鐘)

開始看源碼
LinearLayout 沒有重寫dispatchKeyEvent()甥厦,故繼承父類ViewGroup的分發(fā)方法纺铭。
ViewGroup.java

// The view contained within this ViewGroup that has or contains focus.
private View mFocused; //子view
int mPrivateFlags //代表“當(dāng)前ViewGroup”的屬性
@Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }
      //只有eng版本有效,可忽略(輸入事件一致性校驗器)
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
        }//只有eng版本有效刀疙,可忽略(輸入事件一致性校驗器)
        return false;
    }

時間很充裕舶赔,我們來簡單講一下這個輸入事件一致性效驗器:mInputEventConsistencyVerifier
下面結(jié)合源代碼來看
有幾個要點:
1.只有eng版本有效,user版本直接跳過
2.eng版本它能用來干啥谦秧?只講一個點:up事件彈起的時候竟纳,會檢測上一個鍵是不是同樣的keycode撵溃。
因為正常情況下,肯定是先有down再有up蚁袭,如果up的前一個不是down那么就是異常征懈。
異常會最終通過finishEvent()方法通過log打印出來JА揩悄!

更多的細節(jié)暫時沒研究了,大家自己去發(fā)現(xiàn)鬼悠!

由于user版本删性,一致性效驗器不生效,那么我們簡化下代碼焕窝。

// The view contained within this ViewGroup that has or contains focus.
//子view有焦點或者子view包含有焦點的子view
private View mFocused; 
//代表“當(dāng)前ViewGroup”的屬性
int mPrivateFlags 
@Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        //...
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }
        //...
        return false;
    }

現(xiàn)在來分析這個分發(fā)方法

  1. 屬性PFLAG_FOCUSED 表示有焦點
  2. 屬性PFLAG_HAS_BOUNDS 表示有邊界(一個view繪制完成蹬挺,就肯定有邊界的!)

如果當(dāng)前的ViewGroup有焦點+有邊界它掂,則進入super.dispatchKeyEvent(event) 方法巴帮!
ViewGroup的Super是View.java
//View.java的按鍵分發(fā)代碼:

public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }//一致性校驗,跳過

        // Give any attached key listener a first crack at the event.
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }

        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }//一致性校驗虐秋,跳過
        return false;
    }

繼續(xù)分析:
app層調(diào)用了setOnKeyListener()方法榕茧。
View.java 執(zhí)行g(shù)etListenerInfo().mOnKeyListener = l;
執(zhí)行之后,mListenerInfo不為空了客给。mListenerInfo.mOnKeyListener 也不為空用押。
(mViewFlags & ENABLED_MASK) == ENABLED 成立,正常的View肯定是enable狀態(tài)的靶剑!
接下來蜻拨,就進入 li.mOnKeyListener.onKey(this, event.getKeyCode(), event) 完成app的回調(diào)!

mView.setOnKeyListener(new View.OnKeyListener() {
            
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                CLog.d(TAG, "onKey keyCode:" + keyCode);
                CLog.d(TAG, "event:" + event.getAction());
                //mHandler.sendEmptyMessageDelayed(MSG_ID_GO_NEXT_STEP, 200);
                return true;
            }
        });

不做上層開發(fā)的桩引,可能覺得回調(diào)不好理解缎讼,這里就是實現(xiàn)的方法!好像以前凱榮疑惑過這個問題坑匠。

這里大家可以一起溝通(5分鐘)血崭,最好是能問懵的那種~··

整個回調(diào)過程已經(jīng)看清楚了,并沒有對OK鍵跟方向鍵做差異笛辟,那么問題在哪呢功氨??

繼續(xù)排查
首先從ViewGroup開始加log:

// The view contained within this ViewGroup that has or contains focus.
//子view有焦點或者子view包含有焦點的子view
private View mFocused; 
//代表“當(dāng)前ViewGroup”的屬性
int mPrivateFlags 
@Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        //...
        Log.d(TAG, "mPrivateFlags :" + mPrivateFlags);
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }
        //...
        return false;
    }

通過打印發(fā)現(xiàn)手幢,原來是mPrivateFlags 沒有焦點捷凄!
那么基本可以推測:OK鍵之所以沒有分發(fā)下來,是因為無焦點造成的围来。而方向鍵會賦予ViewGroup焦點跺涤。所以方向鍵可實現(xiàn)跳轉(zhuǎn)P僬觥!

嘗試解決:

mView.requestFocus();

測試驗證OK!


上面講的是解決+分析問題的過程桶错,現(xiàn)在我們稍微延伸一下航唆。

View焦點獲取的過程

方向鍵Down事件,使該ViewGroup獲取的焦點院刁,調(diào)用堆棧:

I ViewGroup: android.view.ViewGroup.requestChildFocus
I ViewGroup: android.view.View.handleFocusGainInternal
I ViewGroup: android.view.ViewGroup.handleFocusGainInternal
I ViewGroup: android.view.View.requestFocusNoSearch
I ViewGroup: android.view.View.requestFocus
I ViewGroup: android.view.ViewGroup.requestFocus
I ViewGroup: android.view.View.requestFocus
I ViewGroup: android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent

下面看一下代碼:
代碼位于 ViewRootImpl.java
看下source的官網(wǎng)說明:

/*
 * The top of a view hierarchy, implementing the needed protocol between View
 * and the WindowManager.  This is for the most part an internal implementation
 * detail of {@link WindowManagerGlobal}.
 * {@hide}
 */

view層級的頂部糯钙,JAVA層的按鍵事件、觸摸事件退腥、View的繪制流程任岸,都是由改類分發(fā)發(fā)起。
但是ViewRootImpl是不是View狡刘,更像是View圖層的大腦O砬薄!

討論5分鐘:假如我們不看代碼嗅蔬,大致猜猜焦點切換邏輯剑按,越詳細越好
...
...
...

下面我們一起看下代碼層,看看焦點是如何完成切換的:
計算機術(shù)語中modifierkey 是“輔助按鍵”的意思澜术。

private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;
            //... 省略不相關(guān)的代碼
            // Handle automatic focus changes.
            //下面的代碼塊艺蝴,完成的方向判定,即焦點切換方向(上瘪板、下吴趴、左、右)侮攀。
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                int direction = 0;
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_LEFT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_RIGHT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_UP:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_UP;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_DOWN;
                        }
                        break;
                    case KeyEvent.KEYCODE_TAB:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_FORWARD;
                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                            direction = View.FOCUS_BACKWARD;
                        }
                        break;
                }
                //焦點切換方向已經(jīng)確定
                if (direction != 0) {
                    //找到當(dāng)前獲取焦點的View(因為要以當(dāng)前焦點View為基點锣枝,然后才能計算出下一個焦點是哪個小伙~)
                    View focused = mView.findFocus();
                    if (focused != null) {
                        //根據(jù)方向direction,計算到下一個Foucus View兰英,如果我們要該修改聚焦邏輯撇叁,那么可以從這里著手。
                        View v = focused.focusSearch(direction);
                        if (v != null && v != focused) {
                            // do the math the get the interesting rect
                            // of previous focused into the coord system of
                            // newly focused view
                            focused.getFocusedRect(mTempRect);
                            if (mView instanceof ViewGroup) {
                                ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                        focused, mTempRect);
                                ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                        v, mTempRect);
                            }
                            //自此下一個View已經(jīng)獲得了焦點(對應(yīng)的UI把焦點畫上)
                            if (v.requestFocus(direction, mTempRect)) {
                                //咔一下畦贸,播放下焦點改變的聲音陨闹。
                                //這里可以加log,下面再講做啥
                                playSoundEffect(SoundEffectConstants
                                        .getContantForFocusDirection(direction));
                                return FINISH_HANDLED;
                            }
                        }

                        // Give the focused view a last chance to handle the dpad key.
                        if (mView.dispatchUnhandledMove(focused, direction)) {
                            return FINISH_HANDLED;
                        }
                    } else {
                        // find the best view to give focus to in this non-touch-mode with no-focus
                        View v = focusSearch(null, direction);
                        if (v != null && v.requestFocus(direction)) {
                            return FINISH_HANDLED;
                        }
                    }
                }
            }
            return FORWARD;
        }

上面的代碼薄坏,就是焦點如何完成切換的代碼趋厉。
大致過程是:首先找點找點當(dāng)前焦點View,然后根據(jù)位置關(guān)系+dirrection胶坠,計算出下一個焦點View.
聚焦邏輯的代碼細節(jié)君账,暫時不仔細分析,如果你非要問沈善,那只能說:


澤寶反饋的嘟嘟嘟bug:

大家一致的總結(jié)是兩個方向:
1.按鍵事件異常
2.audio播放異常

想法:
我們可以在上面的播放按鍵聲音的地方乡数,添加上log椭蹄,然后壓測。
如果異常復(fù)現(xiàn)的時候净赴,沒有反復(fù)調(diào)用這個播放代碼绳矩,則不是按鍵異常的問題。
基本定位在Audio上面了玖翅,也許是audio模塊的一個概率性播放異常R砉荨!烧栋!
這個需要大家下來好好分析了P赐住!审姓!

下面我們繼續(xù)聚焦在按鍵分發(fā)上面~~

安卓View層級的按鍵分發(fā)過程:

最后再根據(jù)Listener的不同返回值,來看下代碼調(diào)用過程:

APP響應(yīng)代碼

mView.setOnKeyListener(new View.OnKeyListener() {
            //onKey( )方
              @Override
              public boolean onKey(View v, int keyCode, KeyEvent event) {
                // TODO Auto-generated method stub
                     return false; //case 1
                     return true; //case 2
            }
        });

ViewGroup.java 分發(fā)流程

@Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))  //自己有焦點
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            //VG -1
            if (super.dispatchKeyEvent(event)) {//VG -2
                return true;//VG -3
            }
          //VG -4
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) //包含焦點View
                == PFLAG_HAS_BOUNDS) {
             //VG -5
            if (mFocused.dispatchKeyEvent(event)) { //VG -6
                return true; //VG -7
            }
        }
        return false; //VG -8
    }

View.java 分發(fā)流程

public boolean dispatchKeyEvent(KeyEvent event) {
        // Give any attached key listener a first crack at the event.
        //noinspection SimplifiableIfStatement
        //V-1
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) { //V -2
            return true; //V -3
        }
        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }
        return false; //V -4
    }

要點提要

一祝峻、mPrivateFlags 代表“當(dāng)前ViewGroup”的屬性

if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
            == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
    //當(dāng)前ViewGroup有焦點且有邊框魔吐,條件才成立。
}

二莱找、mFocused

// The view contained within this ViewGroup that has or contains focus.
private View mFocused;
意思是當(dāng)前的子ViewGroup有焦點酬姆,或者當(dāng)前子ViewGroup包含有焦點。    
是當(dāng)前ViewGroup的子View奥溺。

onKey( )方法返回false辞色,整個調(diào)用流程
層級1. DecorView :VG-5 VG-6
層級2. LinearLayout : VG-5 VG-6
層級3. FrameLayout : VG-5 VG-6
層級4. LinearLayout : VG-5 VG-6
層級5. LinearLayout : VG-5 VG-6
層級6. FrameLayout : VG-5 VG-6 ( framelayout_container )
層級7. LinearLayout VG-1 VG-2 V-1 V-2 V-4
接著開始返回了:
VG-8( 層級7返回 ) VG-8( 層級6返回 ) VG-8( 層級5返回 ) VG-8( 層級4返回 ) VG-8( 層級3返回 ) VG-8( 層級2返回 ) VG-8( 層級1返回 )

onKey( )方法返回true,整個調(diào)用流程
層級1. DecorView :VG-5 VG-6
層級2. LinearLayout : VG-5 VG-6
層級3. FrameLayout : VG-5 VG-6
層級4. LinearLayout : VG-5 VG-6
層級5. LinearLayout : VG-5 VG-6
層級6. FrameLayout : VG-5 VG-6 ( framelayout_container )
層級7. LinearLayout VG-1 VG-2 V-1 V-2 V-3
接著開始返回了:
VG-3(層級7返回)VG-7( 層級6返回 ) VG-7(層級5返回 ) VG-7(層級 4返回 ) VG-7( 層級3返回 ) VG-7(層級 2返回 ) VG-7( 層級1返回 )

總結(jié):

總體上浮定,按鍵分發(fā)是按View的層級逐層分發(fā)相满,分發(fā)的規(guī)則是以焦點View為基礎(chǔ),焦點View-> 焦點View -> 焦點View ... 桦卒。

彩蛋:

一立美、KeyEvent傳遞過程:

主要可以劃分為三步:過濾器、View樹方灾、Activity建蹄。

1.WindowManagerService.java內(nèi)有兩個線程,一個負責(zé)分發(fā)按鍵消息裕偿,一個負責(zé)讀取按鍵消息洞慎。在執(zhí)行processEvent分發(fā)事件時,系統(tǒng)有一些過濾器可以進行一些特定的處理操作嘿棘,這些過濾器的處理既可以在事件分發(fā)前也可以在事件分發(fā)后劲腿。比如home鍵在分發(fā)前進行處理消費,應(yīng)用無法監(jiān)聽該類消息蔫巩,而返回鍵是在事件分發(fā)之后谆棱,只有當(dāng)應(yīng)用不進行處理時快压,系統(tǒng)才會處理返回鍵用來退出當(dāng)前Activity,垃瞧。

2.processEvent分發(fā)事件后蔫劣,先傳遞到ViewRootImpl的deliverKeyEvent中,如果mView(即根DecorView)為空或者mAdded為false个从,則直接返回脉幢。若不為空,然后判斷IME窗口(輸入法)是否存在嗦锐,若存在則優(yōu)先傳遞到IME中嫌松,當(dāng)輸入窗口沒有處理這個事件,事件則會真正派發(fā)到根視圖mView.dispatchKeyEvent中奕污,然后回調(diào)Activity的dispatchKeyEvent(event)去處理萎羔,由于Activity中的dispatchKeyEvent最終調(diào)用的是mDecor中的superDispatchKeyEvent(event),之后就是ViewGroup通過遞歸調(diào)用把Event傳遞到指定的View上碳默。

3.事件傳遞到VIew之后贾陷,先會調(diào)用View的dispatchKeyEvent,如果有注冊Listener嘱根,就直接調(diào)用Listener的onKey去響應(yīng)髓废,如果沒有注冊Listener,z之后會根據(jù)Action的類型調(diào)用對應(yīng)的onXXX(onKeyDown,onKeyup)函數(shù)去處理,如果所有的view都沒有進行處理该抒,那么最終會回調(diào)到activity的onXXX方法中慌洪。

二、Activity View的dispatchKeyEvent凑保,onkeyDown冈爹,onKeyUp, setListener處理順序:

  1. Activity.dispatchKeyEvent(down) ----->view.dispatchKeyEvent ------>view.setListener(如setOnKeyListener) ------>view.onkeyDown------->Activity.onkeyDown------>Activity.dispatchKeyEven(up)------>view.dispatchKeyEvent---->view.setListener(如setOnKeyListener) ------>view.onkeyup----->view.onClick(setOnClickListener)---->Activity.onkeyUp

  2. 當(dāng)一個event事件傳遞到某個view上時愉适,如果對一些Action(比如Down)進行了消費后犯助,則該View下的子view以及想消費該event的Action的行為都不會執(zhí)行。默認(rèn)情況下维咸,ViewGroup控件不會執(zhí)行onkeyDown和onkeyup剂买,只有當(dāng)其焦點屬性為true時,才可以傳遞到執(zhí)行

以后再來進一步分析一下~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末癌蓖,一起剝皮案震驚了整個濱河市瞬哼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌租副,老刑警劉巖坐慰,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異用僧,居然都是意外死亡结胀,警方通過查閱死者的電腦和手機赞咙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糟港,“玉大人攀操,你說我怎么就攤上這事〗崭В” “怎么了速和?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剥汤。 經(jīng)常有香客問我颠放,道長,這世上最難降的妖魔是什么吭敢? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任碰凶,我火速辦了婚禮,結(jié)果婚禮上省有,老公的妹妹穿的比我還像新娘痒留。我一直安慰自己,他們只是感情好蠢沿,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著匾效,像睡著了一般舷蟀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上面哼,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天野宜,我揣著相機與錄音,去河邊找鬼魔策。 笑死匈子,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闯袒。 我是一名探鬼主播虎敦,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼政敢!你這毒婦竟也來了其徙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤喷户,失蹤者是張志新(化名)和其女友劉穎唾那,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褪尝,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡闹获,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年期犬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片避诽。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡龟虎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茎用,到底是詐尸還是另有隱情遣总,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布轨功,位于F島的核電站旭斥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏古涧。R本人自食惡果不足惜垂券,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羡滑。 院中可真熱鬧菇爪,春花似錦、人聲如沸柒昏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽职祷。三九已至氏涩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間有梆,已是汗流浹背是尖。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泥耀,地道東北人饺汹。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像痰催,于是被迫代替她去往敵國和親兜辞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345