十分鐘了解Android觸摸事件原理(InputManagerService)

從手指接觸屏幕到MotionEvent被傳送到Activity或者View士修,中間究竟經(jīng)歷了什么?Android中觸摸事件到底是怎么來(lái)的呢?源頭是哪呢榨乎?本文就直觀的描述一個(gè)整個(gè)流程,不求甚解瘫筐,只求了解蜜暑。

Android觸摸事件模型

觸摸事件肯定要先捕獲才能傳給窗口,因此策肝,首先應(yīng)該有一個(gè)線程在不斷的監(jiān)聽屏幕肛捍,一旦有觸摸事件,就將事件捕獲之众;其次拙毫,還應(yīng)該存在某種手段可以找到目標(biāo)窗口,因?yàn)榭赡苡卸鄠€(gè)APP的多個(gè)界面為用戶可見棺禾,必須確定這個(gè)事件究竟通知那個(gè)窗口缀蹄;最后才是目標(biāo)窗口如何消費(fèi)事件的問題。

觸摸事件模型.jpg

InputManagerService是Android為了處理各種用戶操作而抽象的一個(gè)服務(wù)膘婶,自身可以看做是一個(gè)Binder服務(wù)實(shí)體缺前,在SystemServer進(jìn)程啟動(dòng)的時(shí)候?qū)嵗⒆?cè)到ServiceManager中去悬襟,不過這個(gè)服務(wù)對(duì)外主要是用來(lái)提供一些輸入設(shè)備的信息的作用衅码,作為Binder服務(wù)的作用比較小:

private void startOtherServices() {
        ...
        inputManager = new InputManagerService(context);
        wm = WindowManagerService.main(context, inputManager,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                !mFirstBoot, mOnlyCore);
        ServiceManager.addService(Context.WINDOW_SERVICE, wm);
        ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
       ...
       }

InputManagerService跟WindowManagerService幾乎同時(shí)被添加脊岳,從一定程度上也能說(shuō)明兩者幾乎是相生的關(guān)系肆良,而觸摸事件的處理也確實(shí)同時(shí)涉及兩個(gè)服務(wù),最好的證據(jù)就是WindowManagerService需要直接握著InputManagerService的引用逸绎,如果對(duì)照上面的處理模型惹恃,InputManagerService主要負(fù)責(zé)觸摸事件的采集,而WindowManagerService負(fù)責(zé)找到目標(biāo)窗口棺牧。接下來(lái)巫糙,先看看InputManagerService如何完成觸摸事件的采集。

如何捕獲觸摸事件

InputManagerService會(huì)單獨(dú)開一個(gè)線程專門用來(lái)讀取觸摸事件颊乘,

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
     ...
    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

這里有個(gè)EventHub参淹,它主要是利用Linux的inotify和epoll機(jī)制醉锄,監(jiān)聽設(shè)備事件:包括設(shè)備插拔及各種觸摸、按鈕事件等浙值,可以看做是一個(gè)不同設(shè)備的集線器恳不,主要面向的是/dev/input目錄下的設(shè)備節(jié)點(diǎn),比如說(shuō)/dev/input/event0上的事件就是輸入事件开呐,通過EventHub的getEvents就可以監(jiān)聽并獲取該事件:

EventHub模型.jpg

在new InputManager時(shí)候烟勋,會(huì)新建一個(gè)InputReader對(duì)象及InputReaderThread Loop線程,這個(gè)loop線程的主要作用就是通過EventHub的getEvents獲取Input事件

InputRead線程啟動(dòng)流程
InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    <!--事件分發(fā)執(zhí)行類-->
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    <!--事件讀取執(zhí)行類-->
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

void InputReader::loopOnce() {
        int32_t oldGeneration;
        int32_t timeoutMillis;
        bool inputDevicesChanged = false;
        Vector<InputDeviceInfo> inputDevices;
        {  
      ...<!--監(jiān)聽事件-->
        size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
       ....<!--處理事件-->
           processEventsLocked(mEventBuffer, count);
       ...
       <!--通知派發(fā)-->
        mQueuedListener->flush();
    }

通過上面流程筐付,輸入事件就可以被讀取卵惦,經(jīng)過processEventsLocked被初步封裝成RawEvent,最后發(fā)通知瓦戚,請(qǐng)求派發(fā)消息沮尿。以上就解決了事件讀取問題,下面重點(diǎn)來(lái)看一下事件的分發(fā)较解。

事件的派發(fā)

在新建InputManager的時(shí)候畜疾,不僅僅創(chuàng)建了一個(gè)事件讀取線程,還創(chuàng)建了一個(gè)事件派發(fā)線程印衔,雖然也可以直接在讀取線程中派發(fā)庸疾,但是這樣肯定會(huì)增加耗時(shí),不利于事件的及時(shí)讀取当编,因此,事件讀取完畢后徒溪,直接向派發(fā)線程發(fā)個(gè)通知忿偷,請(qǐng)派發(fā)線程去處理,這樣讀取線程就可以更加敏捷臊泌,防止事件丟失鲤桥,因此InputManager的模型就是如下樣式:

InputManager模型.jpg

InputReader的mQueuedListener其實(shí)就是InputDispatcher對(duì)象,所以mQueuedListener->flush()就是通知InputDispatcher事件讀取完畢渠概,可以派發(fā)事件了, InputDispatcherThread是一個(gè)典型Looper線程茶凳,基于native的Looper實(shí)現(xiàn)了Hanlder消息處理模型,如果有Input事件到來(lái)就被喚醒處理事件播揪,處理完畢后繼續(xù)睡眠等待贮喧,簡(jiǎn)化代碼如下:

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    {  
      <!--被喚醒 ,處理Input消息-->
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
       ...
    } 
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    <!--睡眠等待input事件-->
    mLooper->pollOnce(timeoutMillis);
}

以上就是派發(fā)線程的模型猪狈,dispatchOnceInnerLocked是具體的派發(fā)處理邏輯箱沦,這里看其中一個(gè)分支,觸摸事件:

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
        ...
    case EventEntry::TYPE_MOTION: {
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
        ...
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }

bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ...     
    Vector<InputTarget> inputTargets;
    bool conflictingPointerActions = false;
    int32_t injectionResult;
    if (isPointerEvent) {
    <!--關(guān)鍵點(diǎn)1 找到目標(biāo)Window-->
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
    }
    ...
    <!--關(guān)鍵點(diǎn)2  派發(fā)-->
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

從以上代碼可以看出雇庙,對(duì)于觸摸事件會(huì)首先通過findTouchedWindowTargetsLocked找到目標(biāo)Window谓形,進(jìn)而通過dispatchEventLocked將消息發(fā)送到目標(biāo)窗口灶伊,下面看一下如何找到目標(biāo)窗口,以及這個(gè)窗口列表是如何維護(hù)的寒跳。

如何為觸摸事件找到目標(biāo)窗口

Android系統(tǒng)能夠同時(shí)支持多塊屏幕聘萨,每塊屏幕被抽象成一個(gè)DisplayContent對(duì)象,內(nèi)部維護(hù)一個(gè)WindowList列表對(duì)象童太,用來(lái)記錄當(dāng)前屏幕中的所有窗口米辐,包括狀態(tài)欄、導(dǎo)航欄康愤、應(yīng)用窗口儡循、子窗口等。對(duì)于觸摸事件征冷,我們比較關(guān)心可見窗口择膝,用adb shell dumpsys SurfaceFlinger看一下可見窗口的組織形式:

焦點(diǎn)窗口

那么,如何找到觸摸事件對(duì)應(yīng)的窗口呢检激,是狀態(tài)欄肴捉、導(dǎo)航欄還是應(yīng)用窗口呢,這個(gè)時(shí)候DisplayContent的WindowList就發(fā)揮作用了叔收,DisplayContent握著所有窗口的信息齿穗,因此,可以根據(jù)觸摸事件的位置及窗口的屬性來(lái)確定將事件發(fā)送到哪個(gè)窗口饺律,當(dāng)然其中的細(xì)節(jié)比一句話復(fù)雜的多窃页,跟窗口的狀態(tài)、透明复濒、分屏等信息都有關(guān)系脖卖,下面簡(jiǎn)單瞅一眼,達(dá)到主觀理解的流程就可以了巧颈,

int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
        const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
        bool* outConflictingPointerActions) {
        ...
        sp<InputWindowHandle> newTouchedWindowHandle;
        bool isTouchModal = false;
        <!--遍歷所有窗口-->
        size_t numWindows = mWindowHandles.size();
        for (size_t i = 0; i < numWindows; i++) {
            sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
            const InputWindowInfo* windowInfo = windowHandle->getInfo();
            if (windowInfo->displayId != displayId) {
                continue; // wrong display
            }
            int32_t flags = windowInfo->layoutParamsFlags;
            if (windowInfo->visible) {
                if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                    isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
                            | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
         <!--找到目標(biāo)窗口-->
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        newTouchedWindowHandle = windowHandle;
                        break; // found touched window, exit window loop
                    }
                }
              ...

mWindowHandles代表著所有窗口畦木,findTouchedWindowTargetsLocked的就是從mWindowHandles中找到目標(biāo)窗口,規(guī)則太復(fù)雜砸泛,總之就是根據(jù)點(diǎn)擊位置更窗口Z order之類的特性去確定十籍,有興趣可以自行分析。不過這里需要關(guān)心的是mWindowHandles唇礁,它就是是怎么來(lái)的勾栗,另外窗口增刪的時(shí)候如何保持最新的呢?這里就牽扯到跟WindowManagerService交互的問題了盏筐,mWindowHandles的值是在InputDispatcher::setInputWindows中設(shè)置的械姻,

void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
        ...
        mWindowHandles = inputWindowHandles;
       ...

誰(shuí)會(huì)調(diào)用這個(gè)函數(shù)呢? 真正的入口是WindowManagerService中的InputMonitor會(huì)簡(jiǎn)介調(diào)用InputDispatcher::setInputWindows,這個(gè)時(shí)機(jī)主要是跟窗口增改刪除等邏輯相關(guān)楷拳,以addWindow為例:

更新窗口邏輯.png

從上面流程可以理解為什么說(shuō)WindowManagerService跟InputManagerService是相輔相成的了绣夺,到這里,如何找到目標(biāo)窗口已經(jīng)解決了欢揖,下面就是如何將事件發(fā)送到目標(biāo)窗口的問題了陶耍。

如何將事件發(fā)送到目標(biāo)窗口

找到了目標(biāo)窗口,同時(shí)也將事件封裝好了她混,剩下的就是通知目標(biāo)窗口烈钞,可是有個(gè)最明顯的問題就是,目前所有的邏輯都是在SystemServer進(jìn)程坤按,而要通知的窗口位于APP端的用戶進(jìn)程毯欣,那么如何通知呢?下意識(shí)的可能會(huì)想到Binder通信臭脓,畢竟Binder在Android中是使用最多的IPC手段了酗钞,不過Input事件處理這采用的卻不是Binder:高版本的采用的都是Socket的通信方式,而比較舊的版本采用的是Pipe管道的方式来累。

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    pokeUserActivityLocked(eventEntry);
    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        } else {
        }
    }
}

代碼逐層往下看會(huì)發(fā)現(xiàn)最后會(huì)調(diào)用到InputChannel的sendMessage函數(shù)砚作,最會(huì)通過socket發(fā)送到APP端(Socket怎么來(lái)的接下來(lái)會(huì)分析),

send流程.png

這個(gè)Socket是怎么來(lái)的呢嘹锁?或者說(shuō)兩端通信的一對(duì)Socket是怎么來(lái)的呢葫录?其實(shí)還是要牽扯到WindowManagerService,在APP端向WMS請(qǐng)求添加窗口的時(shí)候领猾,會(huì)伴隨著Input通道的創(chuàng)建米同,窗口的添加一定會(huì)調(diào)用ViewRootImpl的setView函數(shù):

ViewRootImpl

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
            requestLayout();
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                 <!--創(chuàng)建InputChannel容器-->
                mInputChannel = new InputChannel();
            }
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                <!--添加窗口,并請(qǐng)求開辟Socket Input通信通道-->
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            }...
            <!--監(jiān)聽摔竿,開啟Input信道-->
            if (mInputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                        Looper.myLooper());
            }

在IWindowSession.aidl定義中 InputChannel是out類型面粮,也就是說(shuō)需要服務(wù)端進(jìn)行填充,那么接著看服務(wù)端WMS如何填充的呢拯坟?

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        InputChannel outInputChannel) {            
          ...
        if (outInputChannel != null && (attrs.inputFeatures
                & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            String name = win.makeInputChannelName();
            <!--關(guān)鍵點(diǎn)1創(chuàng)建通信信道 -->
            InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
            <!--本地用-->
            win.setInputChannel(inputChannels[0]);
            <!--APP端用-->
            inputChannels[1].transferTo(outInputChannel);
            <!--注冊(cè)信道與窗口-->
            mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
        }

WMS首先創(chuàng)建socketpair作為全雙工通道,并分別填充到Client與Server的InputChannel中去韭山;之后讓InputManager將Input通信信道與當(dāng)前的窗口ID綁定郁季,這樣就能知道哪個(gè)窗口用哪個(gè)信道通信了;最后通過Binder將outInputChannel回傳到APP端钱磅,下面是SocketPair的創(chuàng)建代碼:

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
        ...
        return result;
    }

    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    <!--填充到server inputchannel-->
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);
     <!--填充到client inputchannel-->
    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

這里socketpair的創(chuàng)建與訪問其實(shí)是還是借助文件描述符梦裂,WMS需要借助Binder通信向APP端回傳文件描述符fd,這部分只是可以參考Binder知識(shí)盖淡,主要是在內(nèi)核層面實(shí)現(xiàn)兩個(gè)進(jìn)程fd的轉(zhuǎn)換年柠,窗口添加成功后,socketpair被創(chuàng)建褪迟,被傳遞到了APP端冗恨,但是信道并未完全建立答憔,因?yàn)檫€需要一個(gè)主動(dòng)的監(jiān)聽,畢竟消息到來(lái)是需要通知的掀抹,先看一下信道模型

InputChannl信道.jpg

APP端的監(jiān)聽消息的手段是:將socket添加到Looper線程的epoll數(shù)組中去虐拓,一有消息到來(lái)Looper線程就會(huì)被喚醒,并獲取事件內(nèi)容傲武,從代碼上來(lái)看蓉驹,通信信道的打開是伴隨WindowInputEventReceiver的創(chuàng)建來(lái)完成的。

fd打開通信信道.png

信息到來(lái)揪利,Looper根據(jù)fd找到對(duì)應(yīng)的監(jiān)聽器:NativeInputEventReceiver态兴,并調(diào)用handleEvent處理對(duì)應(yīng)事件

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
   ...
    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
  ...

之后會(huì)進(jìn)一步讀取事件,并封裝成Java層對(duì)象疟位,傳遞給Java層瞻润,進(jìn)行相應(yīng)的回調(diào)處理:

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,  
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {  
        ...
    for (;;) {  
        uint32_t seq;  
        InputEvent* inputEvent;  
        <!--獲取事件-->
        status_t status = mInputConsumer.consume(&mInputEventFactory,  
                consumeBatches, frameTime, &seq, &inputEvent);  
        ...
        <!--處理touch事件-->
      case AINPUT_EVENT_TYPE_MOTION: {
        MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
        if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
            *outConsumedBatch = true;
        }
        inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
        break;
        } 
        <!--回調(diào)處理函數(shù)-->
       if (inputEventObj) {
                    env->CallVoidMethod(receiverObj.get(),
                            gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
                    env->DeleteLocalRef(inputEventObj);
                }

所以最后就是觸摸事件被封裝成了inputEvent,并通過InputEventReceiver的dispatchInputEvent(WindowInputEventReceiver)進(jìn)行處理献汗,這里就返回到我們常見的Java世界了敢订。

目標(biāo)窗口中的事件處理

最后簡(jiǎn)單看一下事件的處理流程,Activity或者Dialog等是如何獲得Touch事件的呢罢吃?如何處理的呢楚午?直白的說(shuō)就是將監(jiān)聽事件交給ViewRootImpl中的rootView,讓它自己去負(fù)責(zé)完成事件的消費(fèi)尿招,究竟最后被哪個(gè)View消費(fèi)了要看具體實(shí)現(xiàn)了矾柜,而對(duì)于Activity與Dialog中的DecorView重寫了View的事件分配函數(shù)dispatchTouchEvent,將事件處理交給了CallBack對(duì)象處理就谜,至于View及ViewGroup的消費(fèi)怪蔑,算View自身的邏輯了。

APP端事件處理流程

總結(jié)

現(xiàn)在把所有的流程跟模塊串聯(lián)起來(lái)丧荐,流程大致如下:

  • 點(diǎn)擊屏幕
  • InputManagerService的Read線程捕獲事件缆瓣,預(yù)處理后發(fā)送給Dispatcher線程
  • Dispatcher找到目標(biāo)窗口
  • 通過Socket將事件發(fā)送到目標(biāo)窗口
  • APP端被喚醒
  • 找到目標(biāo)窗口處理事件
InputManager完整模型.jpg

作者:看書的小蝸牛
十分鐘了解Android觸摸事件原理(InputManagerService)

僅供參考,歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虹统,一起剝皮案震驚了整個(gè)濱河市弓坞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌车荔,老刑警劉巖渡冻,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異忧便,居然都是意外死亡族吻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)超歌,“玉大人砍艾,你說(shuō)我怎么就攤上這事∥沾。” “怎么了辐董?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)禀综。 經(jīng)常有香客問我简烘,道長(zhǎng),這世上最難降的妖魔是什么定枷? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任孤澎,我火速辦了婚禮,結(jié)果婚禮上欠窒,老公的妹妹穿的比我還像新娘覆旭。我一直安慰自己,他們只是感情好岖妄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布型将。 她就那樣靜靜地躺著,像睡著了一般荐虐。 火紅的嫁衣襯著肌膚如雪七兜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天福扬,我揣著相機(jī)與錄音腕铸,去河邊找鬼。 笑死铛碑,一個(gè)胖子當(dāng)著我的面吹牛狠裹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播汽烦,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼涛菠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了撇吞?” 一聲冷哼從身側(cè)響起俗冻,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梢夯,沒想到半個(gè)月后言疗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晴圾,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颂砸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片人乓。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡勤篮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出色罚,到底是詐尸還是另有隱情碰缔,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布戳护,位于F島的核電站金抡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏腌且。R本人自食惡果不足惜梗肝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铺董。 院中可真熱鬧巫击,春花似錦、人聲如沸精续。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)重付。三九已至顷级,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堪夭,已是汗流浹背愕把。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留森爽,地道東北人恨豁。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爬迟,于是被迫代替她去往敵國(guó)和親橘蜜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345