invalidate和requestLayout流程認識

我們大多都了解Android中view的繪制過程陵究,知道一個view是如何從父view往子view一層層的遞歸下去完成測量、布局和繪制的。
知道這些已經(jīng)能完成基本的簡單的自定義的view的開發(fā)了,但是在實際開發(fā)中我們往往會碰到或者使用兩個同樣很常見的方法—— invalidate和requestLayout.

invalidate

山不過來我就過去尤溜。話不多說,我們看看源碼幼衰。

    /**
     * Invalidate the whole view. If the view is visible,
     * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
     * the future.
     * <p>
     * This must be called from a UI thread. To call from a non-UI thread, call
     * {@link #postInvalidate()}.
     */
    public void invalidate() {
        invalidate(true);
    }

    /**
     * This is where the invalidate() work actually happens. A full invalidate()
     * causes the drawing cache to be invalidated, but this function can be
     * called with invalidateCache set to false to skip that invalidation step
     * for cases that do not need it (for example, a component that remains at
     * the same dimensions with the same content).
     *
     * @param invalidateCache Whether the drawing cache for this view should be
     *            invalidated as well. This is usually true for a full
     *            invalidate, but may be set to false if the View's contents or
     *            dimensions have not changed.
     */
    void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

這段代碼還是很簡單靴跛,注釋也說的很清楚,只需要注意的是這個方法只能在主線程中調用渡嚣,invalidate方法最終都是調用了invalidateInternal方法梢睛。

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        ...

        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }

            mPrivateFlags |= PFLAG_DIRTY;

            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                //往父view傳遞,調用父view的invalidateChild方法
                p.invalidateChild(this, damage);
            }

            ...
        }
    }

從上述代碼中可以發(fā)現(xiàn)识椰,View方法中的invalidateInternal實際上是將刷新區(qū)域往上傳給了父viewGroup的invalidateChild方法绝葡,也就是一個從下往上從子到父的一個回溯過程,在每一層view或者viewGroup中都對自己的顯示區(qū)域和傳過來的刷新的 damage區(qū)域Rect做一個交集腹鹉。我們可以看看ViewGroup中的invalidateChild方法藏畅。

    /**
     * Don't call or override this method. It is used for the implementation of
     * the view hierarchy.
     */
    public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;

        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            ...
            if (!childMatrix.isIdentity() ||
                    (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                RectF boundingRect = attachInfo.mTmpTransformRect;
                boundingRect.set(dirty);
                ...
                transformMatrix.mapRect(boundingRect);
                dirty.set((int) (boundingRect.left - 0.5f),
                        (int) (boundingRect.top - 0.5f),
                        (int) (boundingRect.right + 0.5f),
                        (int) (boundingRect.bottom + 0.5f));
            }

            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }
                ...

                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) (boundingRect.left - 0.5f),
                                (int) (boundingRect.top - 0.5f),
                                (int) (boundingRect.right + 0.5f),
                                (int) (boundingRect.bottom + 0.5f));
                    }
                }
            } while (parent != null);
        }
    }

其中最重要的就是第30行,parent = parent.invalidateChildInParent(location, dirty)功咒,
不停地往上遞歸調用invalidateChildInParent方法愉阎,直到頂層view也即是ViewRootImpl.

我們看看ViewGroup的invalidateChildInParent方法。

    /**
     * Don't call or override this method. It is used for the implementation of
     * the view hierarchy.
     *
     * This implementation returns null if this ViewGroup does not have a parent,
     * if this ViewGroup is already fully invalidated or if the dirty rectangle
     * does not intersect with this ViewGroup's bounds.
     */
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
                (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                        FLAG_OPTIMIZE_INVALIDATE) {
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                final int left = mLeft;
                final int top = mTop;

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                        dirty.setEmpty();
                    }
                }
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;

                if (mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                }

                return mParent;

            } else {
                mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;

                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    // in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                if (mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                }

                return mParent;
            }
        }

        return null;
    }

我們會發(fā)現(xiàn)其實這段代碼主要是對傳過來的Rect進行了運算力奋,取了交集榜旦,對damage和自己的顯示區(qū)域,返回的還是parent景殷。

之前也講到了溅呢,在invalidateChild中層層遞歸往父viewGroup回溯,直到ViewRootImpl才會停止猿挚,那我們看看ViewRootImpl中發(fā)生了什么咐旧。

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        ...
        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        ...

        invalidateRectOnScreen(dirty);

        return null;
    }

最關鍵的一步在invalidateRectOnScreen中。

    private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;
        ...

        // Add the new dirty rect to the current one
        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
        // Intersect with the bounds of the window to skip
        // updates that lie outside of the visible region
        
        ...
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            scheduleTraversals();
        }
    }

這里需要解釋一下的就是绩蜻,invalidateChildInParent中返回的是null铣墨。這個結果在ViewGroup中分析時有用到,就結束了自子view到父view的遞歸過程办绝。
因為invalidateChild中的do-while循環(huán)會終止踏兜。

往下我們看到在ViewRootImpl中調用了scheduleTraversals方法。這一步就是整個invalidate的關鍵執(zhí)行步驟了八秃。

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

原來是scheduleTraversals向handler發(fā)送了一個異步消息碱妆,會執(zhí)行TraversalRunnable,這個TraversalRunnable的run方法中昔驱,執(zhí)行的就是
doTraversal方法疹尾。

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            ...

            performTraversals();

            ...
        }
    }

看,doTraversal最后走到了我們熟悉的performTraversals了,performTraversals就是整個View樹開始繪制的起始地方纳本,所以說View調用invalidate方法的實質是層層回溯上傳到父view窍蓝,直到傳遞到ViewRootImpl后調用scheduleTraversals方法,然后整個View樹開始
重新按照Android view的繪制過程分析分析的View繪制流程再來進行view的重繪任務繁成。

postInvalidate

除了常見的invalidate外吓笙,我們還經(jīng)常碰到postInvalidate,其實關于這個的特點我們在一開始有提到過invalidate只能在UI線程進行調用巾腕,所以如果想要在非主線程中進行
invalidate的效果面睛,就需要使用postInvalidate。

    /**
     * <p>Cause an invalidate to happen on a subsequent cycle through the event loop.
     * Use this to invalidate the View from a non-UI thread.</p>
     *
     * <p>This method can be invoked from outside of the UI thread
     * only when this View is attached to a window.</p>
     *
     * @see #invalidate()
     * @see #postInvalidateDelayed(long)
     */
    public void postInvalidate() {
        postInvalidateDelayed(0);
    }

我們可以看到這里其實調用了以下的方法:

    /**
     * <p>Cause an invalidate to happen on a subsequent cycle through the event
     * loop. Waits for the specified amount of time.</p>
     *
     * <p>This method can be invoked from outside of the UI thread
     * only when this View is attached to a window.</p>
     *
     * @param delayMilliseconds the duration in milliseconds to delay the
     *         invalidation by
     *
     * @see #invalidate()
     * @see #postInvalidate()
     */
    public void postInvalidateDelayed(long delayMilliseconds) {
        // We try only with the AttachInfo because there's no point in invalidating
        // if we are not attached to our window
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
        }
    }

可以發(fā)現(xiàn)實際上是調用了ViewRootImpl.dispatchInvalidateDelayed方法尊搬,那么我們來看看這個方法:

    public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }

這里也就是ViewRootImpl的handler發(fā)送了一條消息MSG_INVALIDATE叁鉴,注意這里已經(jīng)涉及到線程間消息傳遞了。

@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
    case MSG_INVALIDATE:
        ((View) msg.obj).invalidate();
        break;
    ...
    }
}

到這里為止佛寿,postInvalidate的實質就是在UI Thread中調運了View的invalidate方法幌墓,那接下來View的invalidate方法就不再重復說了,上面已經(jīng)分析過了冀泻。

小結

關于二者的具體流程常侣,上面已經(jīng)寫的很詳細了,對于invalidate弹渔,就是從view開始一層層往上層調用胳施,直到ViewRootImpl,然后重新繪制一遍捞附。
對于postInvalidate,就是在viewRootImpl中給handler發(fā)送了一個請求重繪的消息您没,然后接著走invalidate鸟召,只是這個起始是可以在非UI線程上進行。

需要注意的是氨鹏,invalidate和postInvalidate方法請求重繪View欧募,只會調用draw方法,如果View大小沒有發(fā)生變化就不會再調用layout仆抵,并且只繪制那些需要重繪的View的臟
的Rect跟继,也就是誰調用,重繪誰镣丑。

在平常開發(fā)中舔糖,可能會有以下情況引起view重繪:

  • 直接手動調用invalidate方法.請求重新draw,但只會繪制調用者本身的view莺匠。
  • 調用setSelection方法金吗。請求重新draw,但只會繪制調用者本身。
  • 調用setVisibility方法摇庙。 當View可視狀態(tài)在INVISIBLE轉換VISIBLE時會間接調用invalidate方法旱物,繼而繪制該View。當View的可視狀態(tài)在INVISIBLE\VISIBLE轉換為GONE狀態(tài)時會間接調用requestLayout和invalidate方法卫袒,同時由于View樹大小發(fā)生了變化宵呛,所以會請求measure過程以及l(fā)ayout過程,同樣只繪制需要重新繪制的視圖夕凝。
  • 調用setEnabled方法宝穗。請求重新draw,但不會重新繪制任何View包括該調用者本身迹冤。
  • 調用requestFocus方法讽营。請求View樹的draw,只繪制需要重繪的View泡徙。

requestLayout

本文最初提到了橱鹏,除了invalidate外,常見以及常用的方法還有requestLayout堪藐。我們來看看這是怎么回事莉兰。

    /**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree. This should not be called while the view hierarchy is currently in a layout
     * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
     * end of the current layout pass (and then layout will run again) or after the current
     * frame is drawn and the next layout occurs.
     *
     * <p>Subclasses which override this method should call the superclass method to
     * handle possible request-during-layout errors correctly.</p>
     */
    @CallSuper
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

注意31-33行,是不是和invalidate的思路很像礁竞,也是一層層的回溯調用父view的requestLayout方法糖荒,直至頂級視圖ViewRootImpl。
我們看一下ViewRootImpl的requstLayout方法:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

checkThread就是檢查一下目標線程是不是當前線程模捂。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

scheduleTraversals做的事情就是和invalidate最后的過程差不多了捶朵,向viewRootImpl的

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

最后也是走到performTraversals了。

因此這些過程和invalidate一樣的狂男。

private void performTraversals() {
    ...
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
    ...
    boolean insetsChanged = false;

    if (layoutRequested) {

        final Resources res = mView.getContext().getResources();

        if (mFirst) {
            // make sure touch mode code executes by setting cached value
            // to opposite of the added touch mode.
            mAttachInfo.mInTouchMode = !mAddedTouchMode;
            ensureTouchModeLocally(mAddedTouchMode);
        } else {
            if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
                insetsChanged = true;
            }
            if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
                insetsChanged = true;
            }
            if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
                insetsChanged = true;
            }
            if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
                mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
                if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " + mAttachInfo.mVisibleInsets);
            }
            if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
                insetsChanged = true;
            }
            if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                windowSizeMayChange = true;

                if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                        || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                    // NOTE -- system code, won't try to do compat mode.
                    Point size = new Point();
                    mDisplay.getRealSize(size);
                    desiredWindowWidth = size.x;
                    desiredWindowHeight = size.y;
                } else {
                    DisplayMetrics packageMetrics = res.getDisplayMetrics();
                    desiredWindowWidth = packageMetrics.widthPixels;
                    desiredWindowHeight = packageMetrics.heightPixels;
                }
            }
        }

        // Ask host how big it wants to be
        windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
    }

    ...
    boolean windowShouldResize = layoutRequested && windowSizeMayChange
            && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
                || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.width() < desiredWindowWidth && frame.width() != mWidth)
                || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.height() < desiredWindowHeight && frame.height() != mHeight));
    ...

    if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null) {
        ...
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        ...
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ...
    }
    ...
}

可以看看上述的關鍵代碼综看,由于在ViewRootImpl的requestLayout中設置了mLayoutRequested為true,在一些boolean值的計算后岖食,所以在performTraversal中可以進入走measure和layout红碑,但是從invalidate中進入的performTraversal不會進入measure和layout。

而且由于surface是valid泡垃,所以也不會走到performDraw析珊。

小結

requestLayout方法會層層遞歸到父view中,直至viewRootImpl蔑穴,調用measure過程和layout過程忠寻,不會調用draw過程,也不會重新繪制任何View包括該調用者本身存和。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末锡溯,一起剝皮案震驚了整個濱河市赶舆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祭饭,老刑警劉巖芜茵,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異倡蝙,居然都是意外死亡九串,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門寺鸥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猪钮,“玉大人,你說我怎么就攤上這事胆建】镜停” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵笆载,是天一觀的道長扑馁。 經(jīng)常有香客問我,道長凉驻,這世上最難降的妖魔是什么腻要? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮涝登,結果婚禮上雄家,老公的妹妹穿的比我還像新娘。我一直安慰自己胀滚,他們只是感情好趟济,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咽笼,像睡著了一般顷编。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上褐荷,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天勾效,我揣著相機與錄音嘹悼,去河邊找鬼叛甫。 笑死,一個胖子當著我的面吹牛杨伙,可吹牛的內容都是我干的其监。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼限匣,長吁一口氣:“原來是場噩夢啊……” “哼抖苦!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤锌历,失蹤者是張志新(化名)和其女友劉穎贮庞,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體究西,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡窗慎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了卤材。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遮斥。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扇丛,靈堂內的尸體忽然破棺而出术吗,到底是詐尸還是另有隱情,我是刑警寧澤帆精,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布较屿,位于F島的核電站,受9級特大地震影響实幕,放射性物質發(fā)生泄漏吝镣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一昆庇、第九天 我趴在偏房一處隱蔽的房頂上張望末贾。 院中可真熱鬧,春花似錦整吆、人聲如沸拱撵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拴测。三九已至,卻和暖如春府蛇,著一層夾襖步出監(jiān)牢的瞬間集索,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工汇跨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留务荆,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓穷遂,卻偏偏與公主長得像函匕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蚪黑,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容