RecycleView緩存機制

recycleview代碼一萬三千多行赫模,內(nèi)部類26個,看起來感覺無從下手

然而,作為一個控件來講舱卡,將onMeasure()和onLayout() 作為入口方法比較合適。其緩存復用機制队萤,定會蘊藏其中轮锥。

先來看onMeasure方法

protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    // 以上代碼解釋了為什么我們忘記設置layoutManger時,頁面什么也展示不出來
    
    if (mLayout.isAutoMeasureEnabled()) {
       ...
        final boolean measureSpecModeIsExactly =
            widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (measureSpecModeIsExactly || mAdapter == null) {
         // 如果測量的是絕對值要尔,則跳過onMeasure過程
            return;
        }
        ...
        if (mState.mLayoutStep == State.STEP_START) {
            //mState.mLayoutStep的默認值為State.STEP_START
            dispatchLayoutStep1();
        }
        ...
        dispatchLayoutStep2();
        
        //開始測量條目
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

        if (mLayout.shouldMeasureTwice()) {
            ...
            //如果RecycleView沒有確定的寬高舍杜,或者至少有一個條目沒有確切的寬高新娜,則需要二次測量
            dispatchLayoutStep2();
            ...
        }
    } else {
        if (mHasFixedSize) {
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            return;
        }
        ...
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        ...
        mState.mInPreLayout = false; // clear
    }
}

mState.mLayoutStep的默認值為State.STEP_START,所以既绩,程序會先執(zhí)行dispatchLayoutStep1()方法概龄,然后執(zhí)行dispatchLayoutStep2(), 如果RecycleView沒有確定的寬高饲握,或者至少有一個條目沒有確切的寬高私杜,則再執(zhí)行一次dispatchLayoutStep2()方法。

先來看dispatchLayoutStep1()救欧,這個方法注釋很明晰衰粹,主要進行一些和動畫相關的操作,以及進行一些預布局笆怠。當該方法執(zhí)行完成后铝耻,會將狀態(tài)改為State.STEP_LAYOUT

/**
 * The first step of a layout where we;
 * - process adapter updates
 * - decide which animation should run
 * - save information about current views
 * - If necessary, run predictive layout and save its information
 */
private void dispatchLayoutStep1() {
    mState.assertLayoutStep(State.STEP_START);
    fillRemainingScrollValues(mState);
    mState.mIsMeasuring = false;
    startInterceptRequestLayout();
    mViewInfoStore.clear();
    onEnterLayoutOrScroll();
    processAdapterUpdatesAndSetAnimationFlags();
    saveFocusInfo();
    mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
    mItemsAddedOrRemoved = mItemsChanged = false;
    mState.mInPreLayout = mState.mRunPredictiveAnimations;
    mState.mItemCount = mAdapter.getItemCount();
    findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

    if (mState.mRunSimpleAnimations) {
        ... 
        // 找到?jīng)]有被remove的ItemView,保存OldViewHolder信息,準備預布局
    }
    if (mState.mRunPredictiveAnimations) {
        ...
        // 進行預布局
    } else {
        clearOldPositions();
    }
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    mState.mLayoutStep = State.STEP_LAYOUT;
}

可以看出蹬刷,dispatchLayoutStep1()方法田篇,并沒有真正進行條目的測量任務。

再來看dispatchLayoutStep2()方法

/**
 * The second layout step where we do the actual layout of the views for the final state.
 * This step might be run multiple times if necessary (e.g. measure).
 */
private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);

    mState.mStructureChanged = false;
    mPendingSavedState = null;

    // onLayoutChildren may have caused client code to disable item animations; re-check
    mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

在這個方法里面箍铭,主要測量方法為mLayout.onLayoutChildren(mRecycler, mState),實際測量條目的布局椎镣,可以看出诈火,實際測量方法委托給了LayoutManger,以LinearLayoutManger為例來看一下具體實現(xiàn):

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

    ...
    // 省略尋找錨點和判斷填充方向
    if (mAnchorInfo.mLayoutFromEnd) {
        firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
                : LayoutState.ITEM_DIRECTION_HEAD;
    } else {
        firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                : LayoutState.ITEM_DIRECTION_TAIL;
    }
    ...
    mLayoutState.mNoRecycleSpace = 0;
    if (mAnchorInfo.mLayoutFromEnd) {
        // 從下往上填充
        ...
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;
        if (mLayoutState.mAvailable > 0) {
            // end could not consume all. add more items towards start
            extraForStart = mLayoutState.mAvailable;
            updateLayoutStateToFillStart(firstElement, startOffset);
            mLayoutState.mExtraFillSpace = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
        }
    } else {
        // 從上往下填充状答,邏輯與上雷同
        ...
    }

    // changes may cause gaps on the UI, try to fix them.
    ...
    mLastStackFromEnd = mStackFromEnd;
}

這里實際填充條目的為fill()函數(shù),看一下fill函數(shù)的實現(xiàn)冷守。fill函數(shù)定義在具體的LayoutManger中,這里展示的為LinearLayoutManger中的fill函數(shù)惊科。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    final int start = layoutState.mAvailable;
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // 回收不可見條目
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        //recycleview還有剩余空間拍摇,并且還有條目的時候,計算條目的大小以及間距信息馆截,并且更新剩余空間
        layoutChunkResult.resetInternal();
        ...
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
                || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            remainingSpace -= layoutChunkResult.mConsumed;
        }
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            recycleByLayoutState(recycler, layoutState);
        }
    }
    return start - layoutState.mAvailable;
}

可以看出充活,fill函數(shù)除了測量空間大小之外,還進行了條目控件的回收工作蜡娶,看一下recycleByLayoutState的實現(xiàn)

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
    if (!layoutState.mRecycle || layoutState.mInfinite) {
        return;
    }
    int scrollingOffset = layoutState.mScrollingOffset;
    int noRecycleSpace = layoutState.mNoRecycleSpace;
    if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
        recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
    } else {
        recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
    }
}

以recycleViewsFromEnd為例看一下

private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
        int noRecycleSpace) {
    final int childCount = getChildCount();
    
    final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace;
    if (mShouldReverseLayout) {
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedStart(child) < limit
                    || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                // stop here
                recycleChildren(recycler, 0, i);
                return;
            }
        }
    } else {
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedStart(child) < limit
                    || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                // stop here
                recycleChildren(recycler, childCount - 1, i);
                return;
            }
        }
    }
}

回收工作核心調(diào)用了recycleChildren方法

public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
    final View view = getChildAt(index);
    removeViewAt(index);
    recycler.recycleView(view);
}

終于到了緩存回收的核心代碼recycler.recycleView(view)混卵,這里調(diào)用的是Recycler中的recycleView()方法,緩存的服用與回收窖张,都是在RecycleView的內(nèi)部類Recycler中控制的,回收的時候幕随,會清除view的動畫,標記等宿接。

public void recycleView(@NonNull View view) {
    ViewHolder holder = getChildViewHolderInt(view);
    if (holder.isTmpDetached()) {
        removeDetachedView(view, false);
    }
    if (holder.isScrap()) {
        holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
        holder.clearReturnedFromScrapFlag();
    }
    recycleViewHolderInternal(holder);
    
}

看下面這段代碼

if (holder.isScrap()) {
    holder.unScrap();
}
void unScrap() {
    mScrapContainer.unscrapView(this);
}

最終調(diào)用

void unscrapView(ViewHolder holder) {
    if (holder.mInChangeScrap) {
        mChangedScrap.remove(holder);
    } else {
        mAttachedScrap.remove(holder);
    }
    holder.mScrapContainer = null;
    holder.mInChangeScrap = false;
    holder.clearReturnedFromScrapFlag();
}

mAttachedScrap為第一級緩存赘淮,移除的時候辕录,先從mAttachedScrap中移除

二級緩存的核心代碼為recycleViewHolderInternal(),Recycler中的緩存的回收與復用梢卸,都是以ViewHolder為單位的走诞,看下recycleViewHolderInternal函數(shù)的實現(xiàn)。

void recycleViewHolderInternal(ViewHolder holder) {
    final boolean transientStatePreventsRecycling = holder
            .doesTransientStatePreventRecycling();
    @SuppressWarnings("unchecked")
    final boolean forceRecycle = mAdapter != null
            && transientStatePreventsRecycling
            && mAdapter.onFailedToRecycleView(holder);
    boolean cached = false;
    boolean recycled = false;
    if (DEBUG && mCachedViews.contains(holder)) {
        throw new IllegalArgumentException("cached view received recycle internal? "
                + holder + exceptionLabel());
    }
    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE
                | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
            // Retire oldest cached view
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                //mViewCacheMax 默認值為2
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            int targetCacheIndex = cachedViewSize;
            if (ALLOW_THREAD_GAP_WORK
                    && cachedViewSize > 0
                    && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                // when adding the view, skip past most recently prefetched views
                int cacheIndex = cachedViewSize - 1;
                while (cacheIndex >= 0) {
                    int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                    if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                        break;
                    }
                    cacheIndex--;
                }
                targetCacheIndex = cacheIndex + 1;
            }
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        ...
    }
    // even if the holder is not removed, we still call this method so that it is removed
    // from view holder lists.
    mViewInfoStore.removeViewHolder(holder);
    if (!cached && !recycled && transientStatePreventsRecycling) {
        holder.mOwnerRecyclerView = null;
    }
}

第二級緩存 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

        if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
            //mViewCacheMax 默認值為2
            recycleCachedViewAt(0);
            cachedViewSize--;
        }

這段代碼展示低剔,如果mCachedViews緩存的是2的話速梗,就把最早的一個移除,mCachedViews最大緩存為2

recycleCachedViewAt方法實現(xiàn)

void recycleCachedViewAt(int cachedViewIndex) {
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}

mCachedViews移除的襟齿,會被添加到四級緩存中

void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    clearNestedRecyclerViewIfNotNested(holder);
    View itemView = holder.itemView;
    if (mAccessibilityDelegate != null) {
        AccessibilityDelegateCompat itemDelegate = mAccessibilityDelegate.getItemDelegate();
        AccessibilityDelegateCompat originalDelegate = null;
        if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) {
            originalDelegate =
                    ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate)
                            .getAndRemoveOriginalDelegateForItem(itemView);
        }
        // Set the a11y delegate back to whatever the original delegate was.
        ViewCompat.setAccessibilityDelegate(itemView, originalDelegate);
    }
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);
    }
    holder.mOwnerRecyclerView = null;
    getRecycledViewPool().putRecycledView(holder);
}

RecycledViewPool mRecyclerPool;為四級緩存姻锁,為什么直接進入第四級緩存,因為猜欺,第三極緩存為用戶自定義的緩存位隶,一般情況下,客戶端不會使用

public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
        return;
    }
    if (DEBUG && scrapHeap.contains(scrap)) {
        throw new IllegalArgumentException("this scrap item already exists");
    }
    scrap.resetInternal();
    scrapHeap.add(scrap);
}

scrapHeap的add方法

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal方法為

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

這里默認的最大緩存數(shù)為10

到這里开皿,添加緩存的方法就完成了涧黄,那什么時候使用呢?

回到LinearLayoutManger的fill方法中

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    ...
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        ...
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        ...
    }
}

使用的地方在layoutChunk方法中

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler);
    if (view == null) {
        ...
        return;
    }
    
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    } else {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addDisappearingView(view);
        } else {
            addDisappearingView(view, 0);
        }
    }
    //以下為實際測量子條目
    measureChildWithMargins(view, 0, 0);
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    int left, top, right, bottom;
    if (mOrientation == VERTICAL) {
        if (isLayoutRTL()) {
            right = getWidth() - getPaddingRight();
            left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
        } else {
            left = getPaddingLeft();
            right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            bottom = layoutState.mOffset;
            top = layoutState.mOffset - result.mConsumed;
        } else {
            top = layoutState.mOffset;
            bottom = layoutState.mOffset + result.mConsumed;
        }
    } else {
        top = getPaddingTop();
        bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            right = layoutState.mOffset;
            left = layoutState.mOffset - result.mConsumed;
        } else {
            left = layoutState.mOffset;
            right = layoutState.mOffset + result.mConsumed;
        }
    }
    // We calculate everything with View's bounding box (which includes decor and margins)
    // To calculate correct layout position, we subtract margins.
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    if (DEBUG) {
        Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
    }
    // Consume the available space if the view is not removed OR changed
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}

獲取緩存的核心代碼為

View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

實際獲取地方為

final View view = recycler.getViewForPosition(mCurrentPosition);

最終調(diào)用了tryGetViewHolderForPositionByDeadline 方法

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    ViewHolder holder = null;
    // 0) 
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 1) Find by position from scrap/hidden list/cache
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
            if (!validateViewHolderForOffsetPosition(holder)) {
                // recycle holder (and unscrap if relevant) since it can't be used
                if (!dryRun) {
                    ...
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
       
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap/cache via stable ids, if exi
        ...
        }
        if (holder == null) { // fallback to pool
            holder = getRecycledViewPool().getRecycledView(type);
            ...
        }
        ...
    }
    ...

    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        // do not update unless we absolutely have to.
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        ...
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

    ...
    return holder;
}

以上就是查找緩存的地方

最后編輯于
?著作權(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é)果婚禮上沽讹,老公的妹妹穿的比我還像新娘般卑。我一直安慰自己,他們只是感情好爽雄,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布蝠检。 她就那樣靜靜地躺著,像睡著了一般挚瘟。 火紅的嫁衣襯著肌膚如雪叹谁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天乘盖,我揣著相機與錄音焰檩,去河邊找鬼。 笑死订框,一個胖子當著我的面吹牛析苫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播穿扳,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼衩侥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了矛物?” 一聲冷哼從身側(cè)響起茫死,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎履羞,沒想到半個月后峦萎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡吧雹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了涂身。 大學時的朋友給我發(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
  • 正文 我出身青樓,卻偏偏與公主長得像吏夯,于是被迫代替她去往敵國和親此蜈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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