SurfaceView源碼分析(三):SurfaceView繪制過(guò)程

之前我們講了Surface的創(chuàng)建過(guò)程SurfaceView的"挖洞"過(guò)程
)。這一篇我們就開始講講SurfaceView的完整繪制流程


前面也有說(shuō)過(guò),雖然SurfaceView具有自己的獨(dú)立的surface黔寇,但是畢竟是在一個(gè)View Hierarchy中索守,所以依然還會(huì)遵循View的繪制流程访锻。當(dāng)ViewRootImpl在執(zhí)行performTravesals的時(shí)候,回一次執(zhí)行performMeasure玖绿,performLayout和performDraw。如何測(cè)量和布局不是本文的重點(diǎn)叁巨,我們直接關(guān)注performDraw好了

    private void performDraw() {

        ...代碼省略...
        try {
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        ...代碼省略...
    }

    private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (!surface.isValid()) {
            return false;
        }

        ...代碼省略...

        boolean useAsyncReport = false;
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                ...代碼省略...
            } else {
                ...代碼省略...
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }

        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
        return useAsyncReport;
    }

performDraw 里面調(diào)用的是draw的方法斑匪,draw方法里面先賦值了surface,然后調(diào)用drawSoftware方法锋勺,將surface傳入:

     * @return true if drawing was successful, false if an error occurred
     */
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
        ...代碼省略...
        // Draw with software renderer.
        final Canvas canvas;

        try {
            ...代碼省略...
            canvas = mSurface.lockCanvas(dirty);

            ...代碼省略...
        }

        ...代碼省略...
        
        try {
            ...代碼省略...
            try {
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;

                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {
                if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;
                }
            }
        } finally {
            try {
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
        return true;
    }

對(duì)這段代碼是否比較眼熟?沒(méi)錯(cuò)蚀瘸,SurfaceView平時(shí)在繪制的時(shí)候就會(huì)讓我們?cè)谧泳€程中先通過(guò)Surface獲取canvas,然后canvas操作完以后需要通過(guò)surface的unlockCanvasAndPost進(jìn)行屏幕刷新庶橱。這里面普通的View也是一樣的操作贮勃,只不過(guò)將surface封裝了起來(lái),對(duì)于開發(fā)者來(lái)說(shuō)是不透明的而已苏章。當(dāng)然這些也不是本文的重點(diǎn)衙猪。我們繼續(xù)往下看,這里最終會(huì)調(diào)用mView的draw方法布近,由于DecorView 和ViewGroup都沒(méi)有重寫draw方法垫释,所以直接進(jìn)入View的draw方法中

     public void draw(Canvas canvas) {
        ...代碼省略...

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }

       ...代碼省略...
    }

這里面就是View draw的主要操作,由于當(dāng)前仍然是DecorView撑瞧,所以在執(zhí)行dispatchDraw 的時(shí)候會(huì)調(diào)用到ViewGroup的dispatchDraw方法,而dispatchDraw方法又調(diào)用drawChild棵譬,最終還是調(diào)用會(huì)View的draw方法,不過(guò)這次的draw方法跟剛才的略有不同:

    /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...代碼省略...

        if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
            }
        } else if (cache != null) {
            ...代碼省略...
        }

        ...代碼省略...

        return more;
    }

這里面判斷(mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW條件成立的時(shí)候會(huì)調(diào)用dispatchDraw方法预伺,條件不成立的時(shí)候才會(huì)draw订咸。之前我們有講過(guò)SurfaceView在初始化的時(shí)候就會(huì)調(diào)用setWillNotDraw(true)方法:

void setflags(int flags, int mask){
        if ((changed & DRAW_MASK) != 0) {
            if ((mViewFlags & WILL_NOT_DRAW) != 0) {
                if (mBackground != null
                        || mDefaultFocusHighlight != null
                        || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                } else {
                    mPrivateFlags |= PFLAG_SKIP_DRAW;
                }
            } else {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
            requestLayout();
            invalidate(true);
        }
}

從這里可以看出來(lái)我們?nèi)绻麤](méi)有對(duì)View設(shè)置背景或者前景,然后當(dāng)我們?cè)O(shè)置了setWillNotDraw(true)之后酬诀,此時(shí)mPrivateFlags會(huì)和PFLAG_SKIP_DRAW做一次邏輯或脏嚷。所以SurfaceView最終是不會(huì)調(diào)用draw方法的,而調(diào)用了dispatchDraw方法瞒御,那我們?cè)谶M(jìn)入SurfaceView的dispatchDraw方法看看:

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (mDrawFinished && !isAboveParent()) {
            // draw() is not called when SKIP_DRAW is set
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                // punch a whole in the view-hierarchy below us
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
        }
        super.dispatchDraw(canvas);
    }

最終SurfaceView只是簡(jiǎn)單地將它所占據(jù)的區(qū)域繪制為黑色父叙,所以我們看到初始化的SurfaceView會(huì)是一個(gè)黑色的View。


所以SurfaceView無(wú)法在onDraw方法上面進(jìn)行繪制,那么我們就只能自己主動(dòng)的進(jìn)行繪制了趾唱。又因?yàn)镾urfaceView不需要告知ViewRootImpl進(jìn)行CheckThread操作涌乳,所以可以直接放在子線程中進(jìn)行操作:

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    SurfaceHolder holder = mSurfaceView.getHolder();
                    Canvas canvas = holder.lockCanvas();
                    //canvas 的繪制操作
                    holder.unlockCanvasAndPost(canvas);
                }
            }
        }).start();

這里我們細(xì)細(xì)講下這三步主要做了什么。
先來(lái)看第一步:surfaceView.getHolder()

    /**
     * Return the SurfaceHolder providing access and control over this
     * SurfaceView's underlying surface.
     *
     * @return SurfaceHolder The holder of the surface.
     */
    public SurfaceHolder getHolder() {
        return mSurfaceHolder;
    }

直接返回mSurfaceHolder對(duì)象甜癞,我們看下mSurfaceHolder是什么:

private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        ...代碼省略...
    };

其實(shí)就是在SurfaceView初始化的時(shí)候就會(huì)初始化SurfaceHolder夕晓。
那么接下來(lái)看第二步:holder.lockCanvas();
SurfaceHolder本身是一個(gè)接口,但是它在SurfaceView里面初始化的時(shí)候?qū)崿F(xiàn)了里面的方法:

private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
         @Override
        public Canvas lockCanvas() {
            return internalLockCanvas(null, false);
        }

        private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
            mSurfaceLock.lock();

            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
                    + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);

            Canvas c = null;
            if (!mDrawingStopped && mSurfaceControl != null) {
                try {
                    if (hardware) {
                        c = mSurface.lockHardwareCanvas();
                    } else {
                        c = mSurface.lockCanvas(dirty);
                    }
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }

            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
            if (c != null) {
                mLastLockTime = SystemClock.uptimeMillis();
                return c;
            }

            // If the Surface is not ready to be drawn, then return null,
            // but throttle calls to this function so it isn't called more
            // than every 100ms.
            long now = SystemClock.uptimeMillis();
            long nextTime = mLastLockTime + 100;
            if (nextTime > now) {
                try {
                    Thread.sleep(nextTime-now);
                } catch (InterruptedException e) {
                }
                now = SystemClock.uptimeMillis();
            }
            mLastLockTime = now;
            mSurfaceLock.unlock();

            return null;
        }
};

lockCanvas會(huì)調(diào)用internalLockCanvas方法悠咱,傳入的參數(shù)是null和false蒸辆,前者傳null就表示要獲取整塊的canvas,后者傳false是代表不要硬件加速析既。最后會(huì)調(diào)用Surface的lockCanvas方法:

    public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mLockedObject != 0) {
                // Ideally, nativeLockCanvas() would throw in this situation and prevent the
                // double-lock, but that won't happen if mNativeObject was updated.  We can't
                // abandon the old mLockedObject because it might still be in use, so instead
                // we just refuse to re-lock the Surface.
                throw new IllegalArgumentException("Surface was already locked");
            }
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
    }

會(huì)發(fā)現(xiàn)這個(gè)時(shí)候需要通過(guò)C++層將canvas傳回來(lái)吁朦。

/frameworks/base/core/jni/android_view_Surface.cpp

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    if (!isSurfaceValid(surface)) {
        doThrowIAE(env);
        return 0;
    }

    Rect dirtyRect(Rect::EMPTY_RECT);
    Rect* dirtyRectPtr = NULL;

    if (dirtyRectObj) {
        //獲取臟數(shù)據(jù)區(qū)域,也就是要更新的范圍渡贾,這里傳了null逗宜,所以dirtyRect不設(shè)置區(qū)域默認(rèn)整個(gè)View都要更新
        dirtyRect.left   = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirtyRect.top    = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirtyRect.right  = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirtyRect.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        dirtyRectPtr = &dirtyRect;
    }

    ANativeWindow_Buffer outBuffer;
    status_t err = surface->lock(&outBuffer, dirtyRectPtr);
    if (err < 0) {
        const char* const exception = (err == NO_MEMORY) ?
                OutOfResourcesException :
                "java/lang/IllegalArgumentException";
        jniThrowException(env, exception, NULL);
        return 0;
    }

    SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
                                         convertPixelFormat(outBuffer.format),
                                         outBuffer.format == PIXEL_FORMAT_RGBX_8888
                                                 ? kOpaque_SkAlphaType : kPremul_SkAlphaType,
                                         GraphicsJNI::defaultColorSpace());

    SkBitmap bitmap;
    ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
    bitmap.setInfo(info, bpr);
    if (outBuffer.width > 0 && outBuffer.height > 0) {
        bitmap.setPixels(outBuffer.bits);
    } else {
        // be safe with an empty bitmap.
        bitmap.setPixels(NULL);
    }

    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    nativeCanvas->setBitmap(bitmap);

    if (dirtyRectPtr) {
        nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top,
                dirtyRect.right, dirtyRect.bottom, SkClipOp::kIntersect);
    }

    if (dirtyRectObj) {
        env->SetIntField(dirtyRectObj, gRectClassInfo.left,   dirtyRect.left);
        env->SetIntField(dirtyRectObj, gRectClassInfo.top,    dirtyRect.top);
        env->SetIntField(dirtyRectObj, gRectClassInfo.right,  dirtyRect.right);
        env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, dirtyRect.bottom);
    }

    // Create another reference to the surface and return it.  This reference
    // should be passed to nativeUnlockCanvasAndPost in place of mNativeObject,
    // because the latter could be replaced while the surface is locked.
    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get();
}
  1. 這里首先會(huì)調(diào)用getSurface獲得與參數(shù)clazz即Surface對(duì)象所對(duì)應(yīng)的C++層的surface。
  2. 然后設(shè)置更新區(qū)域空骚,這里默認(rèn)是整個(gè)View
  3. 通過(guò)前面獲取的Surface的lock方法來(lái)獲取一個(gè)圖形緩沖區(qū)
  4. 新建SKBitmap纺讲,然后獲取canvas,將這個(gè)bitmap放入了Canvas中囤屹。

簡(jiǎn)單來(lái)說(shuō)就是向SurfaceFlinger申請(qǐng)一塊緩沖區(qū)熬甚,然后構(gòu)造一個(gè)SKBitmap對(duì)象,將緩沖區(qū)與SkBitmap關(guān)聯(lián)起來(lái)肋坚,存入Canvas中乡括,然后傳回給上層。

后面就是Canvas的繪制智厌,并不是本文的重點(diǎn)诲泌,繪制完成以后,接下來(lái)就是最后一步了:holder.unlockCanvasAndPost(canvas);.將canvas給解鎖铣鹏,然后post敷扫。

    /**
     * Posts the new contents of the {@link Canvas} to the surface and
     * releases the {@link Canvas}.
     *
     * @param canvas The canvas previously obtained from {@link #lockCanvas}.
     */
    public void unlockCanvasAndPost(Canvas canvas) {
        synchronized (mLock) {
            checkNotReleasedLocked();

            if (mHwuiContext != null) {
                mHwuiContext.unlockAndPost(canvas);
            } else {
                unlockSwCanvasAndPost(canvas);
            }
        }
    }

由于我們并沒(méi)有開啟硬件加速,所以這個(gè)地方會(huì)調(diào)用unlockSwCanvasAndPost方法诚卸。

   private void unlockSwCanvasAndPost(Canvas canvas) {
        if (canvas != mCanvas) {
            throw new IllegalArgumentException("canvas object must be the same instance that "
                    + "was previously returned by lockCanvas");
        }
        if (mNativeObject != mLockedObject) {
            Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
                    Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
                    Long.toHexString(mLockedObject) +")");
        }
        if (mLockedObject == 0) {
            throw new IllegalStateException("Surface was not locked");
        }
        try {
            nativeUnlockCanvasAndPost(mLockedObject, canvas);
        } finally {
            nativeRelease(mLockedObject);
            mLockedObject = 0;
        }
    }

最終還是會(huì)調(diào)用到C++層中去

/frameworks/base/core/jni/android_view_Surface.cpp

static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
    if (!isSurfaceValid(surface)) {
        return;
    }

    // detach the canvas from the surface
    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    nativeCanvas->setBitmap(SkBitmap());

    // unlock surface
    status_t err = surface->unlockAndPost();
    if (err < 0) {
        doThrowIAE(env);
    }
}

這個(gè)方法會(huì)跟局上層傳來(lái)的對(duì)象獲取對(duì)應(yīng)的Surface對(duì)象葵第,此處調(diào)用surface的unlockAndPost:

frameworks/native/libs/gui/Surface.cpp

status_t Surface::unlockAndPost()
{
    if (mLockedBuffer == 0) {
        ALOGE("Surface::unlockAndPost failed, no locked buffer");
        return INVALID_OPERATION;
    }

    int fd = -1;
    status_t err = mLockedBuffer->unlockAsync(&fd);
    ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);

    err = queueBuffer(mLockedBuffer.get(), fd);
    ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)",
            mLockedBuffer->handle, strerror(-err));

    mPostedBuffer = mLockedBuffer;
    mLockedBuffer = 0;
    return err;
}

這里會(huì)調(diào)用queueBuffer方法,最終會(huì)調(diào)用mGraphicBufferProducer的queueBuffer:

frameworks/native/libs/gui/BufferQueueProducer.cpp

status_t BufferQueueProducer::queueBuffer(int slot, const QueueBufferInput &input, QueueBufferOutput *output) { 

    //從input中獲取一些列參數(shù)
    input.deflate(&requestedPresentTimestamp, &isAutoTimestamp, &dataSpace,
        &crop, &scalingMode, &transform, &acquireFence, &stickyTransform,
        &getFrameTimestamps);


    sp<IConsumerListener> frameAvailableListener;
    sp<IConsumerListener> frameReplacedListener;
    //待渲染的一幀
    BufferItem item; 

    //下面是對(duì)這一幀的系列操作
    item.mAcquireCalled = mSlots[slot].mAcquireCalled;
    item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
    item.mCrop = crop;
    item.mTransform = transform &
                      ~static_cast<uint32_t>(NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
    item.mTransformToDisplayInverse =
            (transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) != 0;
    item.mScalingMode = static_cast<uint32_t>(scalingMode);
    item.mTimestamp = requestedPresentTimestamp;
    item.mIsAutoTimestamp = isAutoTimestamp;
    item.mDataSpace = dataSpace;
    item.mHdrMetadata = hdrMetadata;
    item.mFrameNumber = currentFrameNumber;
    item.mSlot = slot;
    item.mFence = acquireFence;
    item.mFenceTime = acquireFenceTime;
    item.mIsDroppable = mCore->mAsyncMode ||
                        mCore->mDequeueBufferCannotBlock ||
                        (mCore->mSharedBufferMode && mCore->mSharedBufferSlot == slot);
    item.mSurfaceDamage = surfaceDamage;
    item.mQueuedBuffer = true;
    item.mAutoRefresh = mCore->mSharedBufferMode && mCore->mAutoRefresh;
    item.mApi = mCore->mConnectedApi;
    ...代碼省略...

    if (frameAvailableListener != NULL) {
         //一幀準(zhǔn)備完畢合溺,通知外界
        frameAvailableListener->onFrameAvailable(item);
    } else if (frameReplacedListener != NULL) {
        frameReplacedListener->onFrameReplaced(item);
    }

    addAndGetFrameTimestamps(&newFrameEventsEntry,etFrameTimestamps ? &output->frameTimestamps : nullptr);

    return NO_ERROR;
}

這里主要是對(duì)一幀進(jìn)行了大量的完善處理卒密,然后將這一幀數(shù)據(jù)通知SurfaceFlinger進(jìn)行更新。這樣整個(gè)SurfaceView的繪制流程就結(jié)束了棠赛。


其實(shí)SurfaceView的繪制流程與View是一模一樣的哮奇,只不過(guò)View的繪制流程已經(jīng)在ViewRootImpl上面已經(jīng)封裝好了膛腐,我們只需要在onDraw里面處理canvas就行。而SurfaceView因?yàn)楸旧砭陀蠸urface屏镊,因此不需要聽從ViewRootImpl調(diào)度來(lái)繪制依疼。所以SurfaceView需要自己對(duì)canvas進(jìn)行鎖定和解鎖更新操作痰腮。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末而芥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子膀值,更是在濱河造成了極大的恐慌棍丐,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沧踏,死亡現(xiàn)場(chǎng)離奇詭異歌逢,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)翘狱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門秘案,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人潦匈,你說(shuō)我怎么就攤上這事阱高。” “怎么了茬缩?”我有些...
    開封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵赤惊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我凰锡,道長(zhǎng)未舟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任掂为,我火速辦了婚禮裕膀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勇哗。我一直安慰自己魂角,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開白布智绸。 她就那樣靜靜地躺著野揪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞧栗。 梳的紋絲不亂的頭發(fā)上斯稳,一...
    開封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音迹恐,去河邊找鬼挣惰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的憎茂。 我是一名探鬼主播珍语,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼竖幔!你這毒婦竟也來(lái)了板乙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拳氢,失蹤者是張志新(化名)和其女友劉穎募逞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馋评,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡放接,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了留特。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纠脾。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蜕青,靈堂內(nèi)的尸體忽然破棺而出苟蹈,到底是詐尸還是另有隱情,我是刑警寧澤市咆,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布汉操,位于F島的核電站,受9級(jí)特大地震影響蒙兰,放射性物質(zhì)發(fā)生泄漏磷瘤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一搜变、第九天 我趴在偏房一處隱蔽的房頂上張望采缚。 院中可真熱鬧,春花似錦挠他、人聲如沸扳抽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)贸呢。三九已至,卻和暖如春拢军,著一層夾襖步出監(jiān)牢的瞬間楞陷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工茉唉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留固蛾,地道東北人结执。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像艾凯,于是被迫代替她去往敵國(guó)和親献幔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361