硬件渲染_樹形視圖節(jié)點繪制記錄


視圖樹形結(jié)構(gòu)皇钞,每一個節(jié)點均有Java層DisplayListCanvas和底層DisplayListCanvas冠息。在Java層则拷,調(diào)用Canvas#drawXxx方法江场,如drawPoint,drawPath嘲碱,drawRect金砍,DisplayListCanvas或Canvas中有JNI#方法,根據(jù)mNativeCanvasWrapper指針獲取底層DisplayListCanvas麦锯。底層DisplayListCanvas繼承底層Canvas恕稠。頭文件定義在/frameworks/base/libs/hwui/Canvas.h
此外扶欣,除了繪制方法drawXxx鹅巍,還有變換方法,如translate料祠,scale等骆捧,以及save和restore方法,下面會通過一個繪圖實例具體分析這些方法實現(xiàn)的操作髓绽。
前面已經(jīng)介紹過凑懂,從頂層視圖DecorView#updateDisplayListIfDirty方法開始繪制樹形視圖結(jié)構(gòu),為了分析簡單梧宫,截取一小段樹分支(虛線框內(nèi))接谨,通過下面視圖結(jié)構(gòu),分析繪制時具體操作塘匣。

樹形節(jié)點繪制示例圖.jpg
手機上顯示的視圖圖片如下脓豪。
手機上顯示的視圖圖片.jpg

容器視圖繪制

虛線框的樹形結(jié)構(gòu)截取如圖。
截取虛線框的視圖.jpg

父視圖0是LinearLayout忌卤,三個子視圖分別是自定義CanvasView和兩個TextView扫夜。
當遍歷到達LinearLayout節(jié)點#updateDisplayListIfDirty方法時,再看一下此方法代碼驰徊,如下笤闯。

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    // ThreadedRenderer是空搬味,直接返回節(jié)點
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()//false燃领,還未記錄繪制
            || (mRecreateDisplayList)) {//重建Canvas
        ...//省略掉不需要重建Canvas的部分代碼
        //第一次進來肯定需要建立Canvas,renderNode也還未記錄书蚪。
        mRecreateDisplayList = true;//重建Canvas
        int width = mRight - mLeft;
        int height = mBottom - mTop;
        int layerType = getLayerType();
        //創(chuàng)建DisplayListCanvas
        final DisplayListCanvas canvas = renderNode.start(width, height);
        //判斷LayerType牺弹,以及獲取HardwareLayer的部分代碼省略掉浦马,LinearLayout不需要。
        try {
            // 一般視圖走硬件渲染都執(zhí)行下面程序
            computeScroll();
            canvas.translate(-mScrollX, -mScrollY);
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                //LinearLayout視圖會跳過繪制张漂,則直接派發(fā)給子視圖
                dispatchDraw(canvas);
            } else {
                draw(canvas);//繪制晶默,包括繪制自身,修飾航攒,以及派發(fā)磺陡,共六個步驟,此處不執(zhí)行漠畜。
            }
        } finally {
            renderNode.end(canvas);//繪制結(jié)束币他,保存canvas記錄內(nèi)容
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

LinearLayout視圖創(chuàng)建畫布DisplayListCanvas后,因有PFLAG_SKIP_DRAW標志盆驹,選擇跳過繪制圆丹,它沒有設置Background,在View框架源碼中會設置此標志躯喇。跳過繪制辫封,即不會走LinearLayout的onDraw方法,另外四個步驟都不會觸發(fā)廉丽,僅僅觸發(fā)父類ViewGroup的dispatchDraw方法倦微。繪制直接向子視圖派發(fā)。
ViewGroup#dispatchDraw方法正压。

@Override
protected void dispatchDraw(Canvas canvas) {
    ...
    boolean more = false;
    final long drawingTime = getDrawingTime();
    //LinearLayout的畫布寫入Reorder柵欄 
    if (usingRenderNodeProperties) canvas.insertReorderBarrier();
    ...
    //繪制子視圖
    for (int i = 0; i < childrenCount; i++) {
        ...
        int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
        final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
      }
    ...
    //LinearLayout的畫布寫入Inorder柵欄 
    if (usingRenderNodeProperties) canvas.insertInorderBarrier();
    ...
}
//drawChild方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

dispatchDraw源碼比較多欣福,主要功能是派發(fā)子視圖繪制。觸發(fā)每個子視圖三個參數(shù)的draw重載方法焦履。在遍歷子視圖前后拓劝,有兩個方法雏逾,LinearLayout畫布的insertReorderBarrier和insertInorderBarrier方法。
他們在觸發(fā)底層DisplayListCanvas的方法一樣郑临,只是enableReorder標志不同栖博,insertReorderBarrier支持重排。

void DisplayListCanvas::insertReorderBarrier(bool enableReorder) {
    flushRestoreToCount();
    flushTranslate();
    mDeferredBarrierType = enableReorder ? kBarrier_OutOfOrder : kBarrier_InOrder;
}

柵欄設置成kBarrier_OutOfOrder或kBarrier_InOrder類型厢洞,kBarrier_OutOfOrder用于標記Chunk的一個變量值reorderChildren仇让。DisplayListCanvas的prepareDirty方法初始化值默認是kBarrier_InOrder,這個值后續(xù)addOpAndUpdateChunk方法會用到躺翻,在繪制子視圖之前丧叽,已經(jīng)創(chuàng)建過第一個Chunk。
在繪制子視圖前公你,插入一個kBarrier_OutOfOrder柵欄踊淳,LinearLayout畫布drawXxx方法繪制會創(chuàng)建一個新Chunk,該Chunk索引LinearLayout畫布繪制的子視圖節(jié)點省店。繪制子視圖結(jié)束后嚣崭,再次插入一個kBarrier_InOrder柵欄,再次新建一個Chunk塊懦傍,索引LinearLayout畫布后續(xù)繪制的內(nèi)容雹舀。

LinearLayout子視圖是葉子節(jié)點,子視圖繪制粗俱,三個參數(shù)的draw重載方法说榆。
View#draw方法。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
    boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;
    boolean more = false;
    ...
    RenderNode renderNode = null;
    Bitmap cache = null;
    ...
    //硬件渲染
    if (drawingWithRenderNode) {
        //在這里觸發(fā)子視圖渲染方法寸认。
        renderNode = updateDisplayListIfDirty();
        if (!renderNode.isValid()) {
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }
    ...
    if (!drawingWithDrawingCache) {
        if (drawingWithRenderNode) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            //父視圖畫布繪制子視圖RenderNode節(jié)點
            ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
        } else {
            ...
        }
    } else if (cache != null) {
        ...
    }

    if (restoreTo >= 0) {
        canvas.restoreToCount(restoreTo);
    }
    ..
    mRecreateDisplayList = false;

    return more;
}

draw方法很長签财,這里指摘取硬件渲染相關的。關注兩個點偏塞。
1:子視圖#updateDisplayListIfDirty方法唱蒸,重建Canvas,傳遞給一個參數(shù)的draw重載方法灸叼,繪制神汹,返回子視圖RenderNode節(jié)點,該節(jié)點相關畫布已經(jīng)完成繪制內(nèi)容記錄古今。
2:LinearLayout畫布drawRenderNode繪制子視圖RenderNode節(jié)點屁魏。

void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) {
    DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(
            renderNode,
            *mState.currentTransform(),
            mState.clipIsSimple());
    addRenderNodeOp(op);
}

創(chuàng)建一個DrawRenderNodeOp,DrawRenderNodeOp繼承DrawBoundedOp捉腥,DrawBoundedOp繼承DrawOp氓拼,基類是DisplayListOp。

size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
    //增加一個繪制操作到mDisplayListData的displayListOps數(shù)組。
    int opIndex = addDrawOp(op);
    //mDisplayListData是LinearLayout底層存儲繪制數(shù)據(jù)的對象
    //增加操作到DisplayListData的mChildren數(shù)組桃漾,代表子視圖
    int childIndex = mDisplayListData->addChild(op);
    //寫入了一個繪制子節(jié)點的操作坏匪,告訴Chunk的endChildIndex自增。
    DisplayListData::Chunk& chunk = mDisplayListData->chunks.editTop();
    chunk.endChildIndex = childIndex + 1;

    if (op->renderNode()->stagingProperties().isProjectionReceiver()) {
        mDisplayListData->projectionReceiveIndex = opIndex;
    }
    return opIndex;
}

addDrawOp方法增加一個繪制Op呈队,后面會詳細介紹剥槐,總之,將DrawRenderNodeOp加入到DisplayListData的displayListOps數(shù)組中宪摧。
addChild方法將操作加入到DisplayListData的mChildren數(shù)組中。
注意颅崩,因前期插入一個kBarrier_OutOfOrder柵欄几于,因此在addDrawOp觸發(fā)的addOpAndUpdateChunk中,會創(chuàng)建一個新Chunk塊沿后,寫入DrawRenderNodeOp后沿彭,更新該Chunk中指向mChildren數(shù)組的endChildIndex 索引。
三個葉子RenderNode節(jié)點操作寫入LinearLayout底層數(shù)據(jù)數(shù)組中尖滚,他們在一個Chunk塊中喉刘。子視圖繪制完畢,再次插入一個kBarrier_InOrder柵欄漆弄,創(chuàng)建一個新Chunk睦裳。

父視圖LinearLayout繪制時Chunk與記錄.jpg

葉子節(jié)點視圖繪制

葉子節(jié)點繪制主要關注onDraw方法,CanvasView的onDraw方法撼唾,繪制三個矩形區(qū)域廉邑。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.parseColor("#ffffcc")); //畫布顏色
    Paint paint = new Paint();
    paint.setStyle(Paint.Style.STROKE);//設置非填充風格
    Rect rect1 = new Rect(20, 20, 180, 180); 
    canvas.drawRect(rect1, paint);//繪制一個正方形
    /************************第一層*****************************/
    canvas.save();//當前狀態(tài)保存
    /***********************第二層*****************************/
    canvas.translate(180, 180);
    Rect rect2 = new Rect(20, 20, 180, 180); 
    canvas.drawRect(rect2, paint);//再次繪制一個正方形
    /************************第二層***************************/
    canvas.restore();//恢復到第一層狀態(tài)
    /************************第一層**************************/
    Rect rect3 = new Rect(20, 200, 180, 360); 
    canvas.drawRect(rect3, paint);//再次繪制一個正方形
}

四個Canvas方法

drawRect,繪制一個矩形區(qū)域倒谷。
save蛛蒙,保存當前圖層狀態(tài)。
restore渤愁,恢復上一個圖層狀態(tài)牵祟。
translate,兩個方向x和y抖格,偏移一定距離诺苹。

先繪制一個矩形,然后保存狀態(tài)他挎,進行偏移筝尾,繪制第二個矩形,第二個矩形受到偏移影響办桨,恢復狀態(tài)筹淫,繪制第三個矩形,第三個矩形不受偏移影響。

詳細分析

執(zhí)行順序
drawRect(1)
save
translate
drawRect(2)
restore
drawRect(3)

1损姜,drawRect:觸發(fā)底層DisplayListCanvas的drawRect方法饰剥。該方法是
在DisplayListCanvas的頭文件中定義虛函數(shù),在Canvas的頭文件中也有定義摧阅。
底層 DisplayListCanvas#drawRect方法汰蓉。

void DisplayListCanvas::drawRect(float left, float top, float right, float bottom,
        const SkPaint& paint) {
    addDrawOp(new (alloc()) DrawRectOp(left, top, right, bottom, refPaint(&paint)));
}

addDrawOp增加一個繪制Op,DrawRectOp類型棒卷,不同的繪制Op不同顾孽,如其drawPoints繪制增加的Op是DrawPointsOp類型,總之比规,他們都繼承DrawOp類若厚。

addDrawOp增加一個繪制操作DrawOp。DrawOp繼承DisplayListOp類蜒什。

底層DisplayListCanvas#addDrawOp方法测秸。

size_t DisplayListCanvas::addDrawOp(DrawOp* op) {
    Rect localBounds;
    ...
    mDisplayListData->hasDrawOps = true;
    return flushAndAddOp(op);
}

觸發(fā)flushAndAddOp方法。
2灾常,save:觸發(fā)底層DisplayListCanvas的save方法霎冯。注意:save和restore一般是成對出現(xiàn)的。
底層DisplayListCanvas#save方法钞瀑。

int DisplayListCanvas::save(SkCanvas::SaveFlags flags) {
    addStateOp(new (alloc()) SaveOp((int) flags));
    return mState.save((int) flags);
}

addStateOp增加狀態(tài)操作StateOp沈撞,前面的是增加DrawOp,而現(xiàn)在是StateOp

和DrawOp一樣仔戈,狀態(tài)StateOp也繼承DisplayListOp類关串。

底層DisplayListCanvas#addStateOp方法。

size_t DisplayListCanvas::addStateOp(StateOp* op) {
    return flushAndAddOp(op);
}

觸發(fā)flushAndAddOp方法监徘。
除了flushAndAddOp保存晋修,還會觸發(fā)CanvasState保存。

總結(jié):
DisplayListCanvas的save方法凰盔,addStateOp增加一個SaveOp墓卦,addStateOp方法和addDrawOp類似,都觸發(fā)flushAndAddOp方法户敬,入?yún)⑹峭粋€基類DisplayListOp落剪。

CanvasState#save方法。

int CanvasState::save(int flags) {
    return saveSnapshot(flags);
}

int CanvasState::saveSnapshot(int flags) {
    mSnapshot = new Snapshot(mSnapshot, flags);
    return mSaveCount++;
}

在CanvasState類save操作中尿庐,創(chuàng)建一個Snapshot對象忠怖,封裝當前對象,新建的Snapshot放到鏈表最前面抄瑟,mSaveCount自增凡泣,mSaveCount代表當前操作的是位于第幾層次,若上層調(diào)用了restore,則觸發(fā)CanvasState的restore方法鞋拟,回退到上一層骂维。

可以看出,每一個層次狀態(tài)的save都保存在Snapshot中贺纲。save之后的改變在新建的Snapshot保存航闺,restore后回到之前的Snapshot。

3猴誊,translate:觸發(fā)底層DisplayListCanvas的translate方法潦刃,x和y軸方向上的偏移。
底層DisplayListCanvas#translate方法

void DisplayListCanvas::translate(float dx, float dy) {
    if (dx == 0.0f && dy == 0.0f) return;

    mHasDeferredTranslate = true;
    mTranslateX += dx;
    mTranslateY += dy;
    flushRestoreToCount();
    mState.translate(dx, dy, 0.0f);
}

增加偏移量dx和dy稠肘。
flushRestoreToCount方法福铅,在mRestoreSaveCount>=0時才起作用,即執(zhí)行過restore或restoreToCount项阴。
寫入一個RestoreToCountOp操作。
CanvasState的translate方法笆包,當前mSnapshot的transform觸發(fā)translate偏移环揽。因前面觸發(fā)過一個save方法,因此有兩個Snapshot庵佣,當前mSnapshot是表頭第一個歉胶。

4,restore:觸發(fā)底層DisplayListCanvas的restore方法巴粪,恢復到上一個繪制狀態(tài)通今。
底層DisplayListCanvas#restore方法。

void DisplayListCanvas::restore() {
    if (mRestoreSaveCount < 0) {
        restoreToCount(getSaveCount() - 1);
        return;
    }

    mRestoreSaveCount--;
    flushTranslate();
    mState.restore();
}

若mRestoreSaveCount已經(jīng)>=0肛根,則直接自減辫塌。每次restore只能回退一層。同時CanvasState回退派哲,內(nèi)部mSaveCount保存層級臼氨。
若mRestoreSaveCount小于0,即有可能是初始值-1芭届,則觸發(fā)restoreToCount方法储矩,getSaveCount獲取當前層級。若執(zhí)行過一個save褂乍,restore時持隧,getSaveCount得到2,那么restoreToCount恢復的值就是1逃片,mRestoreSaveCount設置成1屡拨。

總結(jié):restore回退一層。

上層直接調(diào)用restoreToCount方法時,直接回退saveCount層洁仗,這個方法中會設置mRestoreSaveCount值层皱。

底層DisplayListCanvas#restoreToCount方法。

void DisplayListCanvas::restoreToCount(int saveCount) {
    mRestoreSaveCount = saveCount;
    flushTranslate();
    mState.restoreToCount(saveCount);
}

save和restore成對出現(xiàn)赠潦,多個save后叫胖,第一次restore時,mRestoreSaveCount將會是初始值-1她奥,restoreToCount方法會第一次設置其值瓮增,設置成restore回退后的層級。
即mRestoreSaveCount存儲當前層級哩俭。

例如绷跑,3個save后,第一次restore后凡资,則mRestoreSaveCount變?yōu)?砸捏。

總結(jié):
在上層一次save,將當前的狀態(tài)保存下來隙赁,包括偏移垦藏,縮放等狀態(tài),后續(xù)的改變?nèi)匀辉诖嘶A上伞访,偏移掂骏,繪制等,若執(zhí)行restore厚掷,則回到上一個save保存的狀態(tài)中弟灼,中間的改變忽略掉。

硬件渲染節(jié)點繪制寫入流程.jpg

1:drawRect冒黑,繪制正方形田绑,第一層,寫入DrawRectOp薛闪,mRestoreSaveCount是-1辛馆,flushRestoreCount無作用,無偏移豁延,flushTranslate無作用昙篙。
addOpAndUpdateChunk寫入繪制。
2:save诱咏,保存當前層級狀態(tài)苔可,mRestoreSaveCount是-1,flushRestoreCount無作用袋狞,無偏移焚辅,flushTranslate無作用映屋。
addOpAndUpdateChunk寫入狀態(tài)。
3:translate同蜻,偏移棚点,第二層,mRestoreSaveCount是-1湾蔓,flushRestoreCount無作用瘫析,設置偏移值mTranslateX與mTranslateY,當前Snapshot(第二層)偏移默责。
4:drawRect贬循,繪制正方形,第二層桃序,寫入DrawRectOp杖虾,mRestoreSaveCount是-1,flushRestoreCount無作用媒熊,有偏移奇适,
addOpAndUpdateChunk寫入TranslateOp,并恢復偏移值芦鳍。
addOpAndUpdateChunk寫入繪制滤愕。

5:restore,恢復上一個層級怜校,前面僅有一個save,因此mRestoreSaveCount是-1注竿,觸發(fā)restoreToCount方法茄茁,設置mRestoreSaveCount=1。無偏移巩割,flushTranslate無作用裙顽。
6:drawRect,繪制正方形宣谈,第一層愈犹,寫入DrawRectOp,mRestoreSaveCount是1闻丑,無偏移漩怎,flushTranslate無作用,
addOpAndUpdateChunk寫入RestoreToCountOp嗦嗡。并恢復mRestoreSaveCount為-1勋锤。
addOpAndUpdateChunk寫入繪制。

flushAndAddOp方法在上層寫入DrawOp或StateOp時觸發(fā)侥祭。
最終目的是addOpAndUpdateChunk叁执。
當前存在mRestoreSaveCount>=0或偏移茄厘,首先寫入相應Op,即RestoreToCountOp與TranslateOp谈宛。

底層DisplayListCanvas#flushAndAddOp方法次哈。

size_t DisplayListCanvas::flushAndAddOp(DisplayListOp* op) { //
    flushRestoreToCount();
    flushTranslate();
    return addOpAndUpdateChunk(op);
}

三個方法。
flushRestoreToCount:寫入RestoreToCountOp吆录。
flushTranslate:寫入TranslateOp窑滞。
addOpAndUpdateChunk:執(zhí)行寫入。

DisplayListCanvas#flushRestoreToCount方法径筏。

void DisplayListCanvas::flushRestoreToCount() {
    if (mRestoreSaveCount >= 0) {
        addOpAndUpdateChunk(new (alloc()) RestoreToCountOp(mRestoreSaveCount));
        mRestoreSaveCount = -1;
    }
}

若mRestoreSaveCount>=0葛假,增加一個RestoreToCountOp操作。mRestoreSaveCount在剛初始化時prepareDirty滋恬,設置成-1聊训。
DisplayListCanvas#flushTranslate方法。

void DisplayListCanvas::flushTranslate() {
    if (mHasDeferredTranslate) {
        if (mTranslateX != 0.0f || mTranslateY != 0.0f) {
            addOpAndUpdateChunk(new (alloc()) TranslateOp(mTranslateX, mTranslateY));
            mTranslateX = mTranslateY = 0.0f;
        }
        mHasDeferredTranslate = false;
    }
}

mTranslateX或mTranslateY不是0時寫入TranslateOp恢氯。

addOpAndUpdateChunk方法負責增加Op带斑,不僅包括RestoreToCountOp和TranslateOp。

DisplayListCanvas#addOpAndUpdateChunk方法勋拟。

size_t DisplayListCanvas::addOpAndUpdateChunk(DisplayListOp* op) {//增加op,更新chunk
    int insertIndex = mDisplayListData->displayListOps.add(op);
    if (mDeferredBarrierType != kBarrier_None) {
        // op is first in new chunk
        mDisplayListData->chunks.push();
        DisplayListData::Chunk& newChunk = mDisplayListData->chunks.editTop();//新Chunk
        newChunk.beginOpIndex = insertIndex;
        newChunk.endOpIndex = insertIndex + 1;
        newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);

        int nextChildIndex = mDisplayListData->children().size();
        newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
        mDeferredBarrierType = kBarrier_None;
    } else {
        // standard case - append to existing chunk
        mDisplayListData->chunks.editTop().endOpIndex = insertIndex + 1;//最上面Chunk修改
    }
    return insertIndex;
}

將操作DisplayListOp加入DisplayListData的內(nèi)部數(shù)組勋磕,類型全部都是DisplayListOp,返回插入的索引敢靡。
DisplayListData在底層Canvas的prepareDirty時創(chuàng)建挂滓,不管是繪制操作還是狀態(tài)操作,都屬于DisplayListOp類型啸胧,按照順序放到相同數(shù)組赶站。

上述例子中DisplayListData內(nèi)部數(shù)組的內(nèi)容如下圖所示。
DisplayListData內(nèi)部數(shù)組寫入的操作.jpg

依次是DrawRectOp纺念,SaveOp贝椿,TranslateOp,DrawRectOp陷谱,RestoreToCountOp烙博,DrawRectOp。


任重而道遠

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烟逊,一起剝皮案震驚了整個濱河市渣窜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌焙格,老刑警劉巖图毕,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異眷唉,居然都是意外死亡予颤,警方通過查閱死者的電腦和手機囤官,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛤虐,“玉大人党饮,你說我怎么就攤上這事〔低ィ” “怎么了刑顺?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長饲常。 經(jīng)常有香客問我蹲堂,道長,這世上最難降的妖魔是什么贝淤? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任柒竞,我火速辦了婚禮,結(jié)果婚禮上播聪,老公的妹妹穿的比我還像新娘朽基。我一直安慰自己,他們只是感情好离陶,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布稼虎。 她就那樣靜靜地躺著,像睡著了一般招刨。 火紅的嫁衣襯著肌膚如雪霎俩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天沉眶,我揣著相機與錄音茸苇,去河邊找鬼。 笑死沦寂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的淘衙。 我是一名探鬼主播传藏,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彤守!你這毒婦竟也來了毯侦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤具垫,失蹤者是張志新(化名)和其女友劉穎侈离,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筝蚕,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡卦碾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年铺坞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洲胖。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡济榨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绿映,到底是詐尸還是另有隱情擒滑,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布叉弦,位于F島的核電站丐一,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏淹冰。R本人自食惡果不足惜栈幸,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望末贾。 院中可真熱鬧筐钟,春花似錦、人聲如沸疹鳄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘪弓。三九已至垫蛆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腺怯,已是汗流浹背袱饭。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呛占,地道東北人虑乖。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像晾虑,于是被迫代替她去往敵國和親疹味。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

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