RecyclerView源碼解析

復(fù)用和回收

復(fù)用的好處:
避免為表項(xiàng)視圖綁定數(shù)據(jù)沪蓬,創(chuàng)建表項(xiàng)視圖畸冲。

子item的繪制交給LayoutManager去處理址晕。

fill

LinearLayoutManager#fill
作用:回收和復(fù)用萍悴。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    ...
    // 當(dāng)前的方向上是否還有多余的空間填充item
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    // 當(dāng)剩余空間> 0時(shí)春弥,繼續(xù)填充更多表項(xiàng)
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        // 通過View循環(huán)虫碉,來對(duì)條目進(jìn)行一條條復(fù)用贾惦,填充剩余空間
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
    
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            // 從剩余空間中扣除新表項(xiàng)占用像素值
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            if (layoutState.mAvailable < 0) {
                // 在limit上追加新表項(xiàng)所占像素值
                // 回收哪些項(xiàng)目是根據(jù)limit線走的,手指向上滑敦捧,底部填充元素须板,limit線會(huì)下移,在這根線上面的條目會(huì)被回收绞惦。
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            // 回收
            recycleByLayoutState(recycler, layoutState);
        }
    }
    return start - layoutState.mAvailable;
}

回收

LinearLayoutManager#recycleByLayoutState

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
    if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
         // 從列表頭回收
        recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
    } else {
        // 從列表尾回收
        recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
    }
}

LinearLayoutManager#recycleViewsFromStart

private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
    //從頭開始遍歷 LinearLayoutManager逼纸,以找出應(yīng)該會(huì)回收的表項(xiàng)
    final int childCount = getChildCount();
    // 是否反轉(zhuǎn)布局,就是布局上從上往下填充還是從下往上填充
    if (mShouldReverseLayout) {
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            // 當(dāng)某表項(xiàng)底部位于limit隱形線之后時(shí)济蝉,回收它以上的所有表項(xiàng)
            // limit是列表中隱形的線
            if (mOrientationHelper.getDecoratedEnd(child) > limit
                    || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                //回收索引為末尾到i-1的表項(xiàng)
                recycleChildren(recycler, childCount - 1, i);
                return;
            }
        }
    } else {
         //回收索引為0到i-1的表項(xiàng)
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedEnd(child) > limit
                    || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                recycleChildren(recycler, 0, i);
                return;
            }
        }
    }
}

“從列表頭回收表項(xiàng)”所對(duì)應(yīng)的場景是:手指上滑杰刽,列表向上滾動(dòng),新的表項(xiàng)逐個(gè)插入到列表尾部王滤,列表頭部的表項(xiàng)逐個(gè)被回收贺嫂。

復(fù)用

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    // 1. 通過緩存池中獲取下個(gè)條目
    View view = layoutState.next(recycler);
   
    // 2. 將列表中的一項(xiàng)添加進(jìn)RecyclerView
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    // 3. 測量該視圖
    measureChildWithMargins(view, 0, 0);
    // 4. 獲取填充視圖需要消耗的像素值
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    // 5. 布局表項(xiàng)
    // 確定表項(xiàng)上下左右四個(gè)點(diǎn)相對(duì)于RecyclerView的位置
    layoutDecoratedWithMargins(view, left, top, right, bottom);
}

LinearLayoutManager#next

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

RecyclerView#tryGetViewHolderForPositionByDeadline

復(fù)用機(jī)制代碼

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    // 復(fù)用的對(duì)象是ViewHolder
    // 在布局之前
    if (mState.isPreLayout()) {
        // 1. 通過id或者position從mChangedScrap緩存找到對(duì)應(yīng)的緩存
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    if (holder == null) {
        // 2. 通過position從mAttachedScrap或二級(jí)回收緩存中獲取ViewHolder
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
            if (!validateViewHolderForOffsetPosition(holder)) {
                if (!dryRun) {
                    holder.addFlags(ViewHolder.FLAG_INVALID);
                    if (holder.isScrap()) {
                        removeDetachedView(holder.itemView, false);
                        holder.unScrap();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);

        final int type = mAdapter.getItemViewType(offsetPosition);
        // 3. 通過id從mAttachedScrap或二級(jí)回收緩存中獲取ViewHolder
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
        if (holder == null && mViewCacheExtension != null) {
            // 4. 從自定義緩存中獲取ViewHolder
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
            }
        }
        if (holder == null) { // fallback to pool
            // 5. 從緩存池中取ViewHolder
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                    invalidateDisplayListInt(holder);
                }
            }
        }
        if (holder == null) {
            long start = getNanoTime();
            // 6.所有緩存都沒命中,就需要?jiǎng)?chuàng)建ViewHolder
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            if (ALLOW_THREAD_GAP_WORK) {
                // only bother finding nested RV if prefetching
                RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                if (innerView != null) {
                    holder.mNestedRecyclerView = new WeakReference<>(innerView);
                }
            }

            long end = getNanoTime();
            mRecyclerPool.factorInCreateTime(type, end - start);
            if (DEBUG) {
                Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
            }
        }
    }

    if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
        holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
        if (mState.mRunSimpleAnimations) {
            int changeFlags = ItemAnimator
                    .buildAdapterChangeFlagsForAnimations(holder);
            changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
            final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                    holder, changeFlags, holder.getUnmodifiedPayloads());
            recordAnimationInfoIfBouncedHiddenView(holder, info);
        }
    }

    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        //獲得ViewHolder后雁乡,綁定視圖數(shù)據(jù)
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    ...
    return holder;
}

總結(jié):
RecyclerView在滾動(dòng)發(fā)生之前第喳,會(huì)根據(jù)預(yù)計(jì)滾動(dòng)位移大小來決定需要向列表中填充多少新的表項(xiàng)。在填充表項(xiàng)的同時(shí)踱稍,也會(huì)回收表項(xiàng)曲饱,回收的依據(jù)是limit隱形線。
limit隱形線是RecyclerView在滾動(dòng)發(fā)生之前根據(jù)滾動(dòng)位移計(jì)算出來的一條線珠月,它是決定哪些表項(xiàng)該被回收的重要依據(jù)扩淀。它可以理解為:隱形線當(dāng)前所在位置,在滾動(dòng)完成后會(huì)和列表頂部重疊啤挎。
limit隱形線的初始值=列表當(dāng)前可見表項(xiàng)的底部到列表底部的距離驻谆,即列表在不填充新表項(xiàng)時(shí),可以滑動(dòng)的最大距離。每一個(gè)新填充表項(xiàng)消耗的像素值都會(huì)被追加到limit值之上胜臊,即limit隱形線會(huì)隨著新表項(xiàng)的填充而不斷地下移勺卢。
觸發(fā)回收邏輯時(shí),會(huì)遍歷當(dāng)前所有表項(xiàng)象对,若某表項(xiàng)的底部位于limit隱形線下方黑忱,則該表項(xiàng)上方的所有表項(xiàng)都會(huì)被回收。

四級(jí)緩存

// detach調(diào)用
// 復(fù)用的時(shí)候不需要調(diào)用bindViewHolder重新綁定數(shù)據(jù)织盼,狀態(tài)和數(shù)據(jù)不會(huì)被重置的
// 保存原封不動(dòng)的ViewHolder
// 生命周期兩次布局
// 位置一致才能復(fù)用
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
// 發(fā)生變化的ViewHolder
// 生命周期只有預(yù)布局
ArrayList<ViewHolder> mChangedScrap = null;

// remove調(diào)用
// 可通過setItemCacheSize調(diào)整杨何,默認(rèn)大小為2
// 上下滑動(dòng)酱塔,被滑出去的ViewHolder緩存
// 如果超過限制沥邻,會(huì)把最老的item移除到RecycledViewPool中。

// mCachedViews中緩存的ViewHolder只能復(fù)用于指定位置羊娃,不需要調(diào)用bindViewHolder重新綁定數(shù)據(jù)
// 應(yīng)用場景列表回滾
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

// 自定義拓展View緩存
private ViewCacheExtension mViewCacheExtension;

// RecycledViewPool中的ViewHolder存儲(chǔ)在SparseArray中唐全,并且按viewType分類存儲(chǔ)
// 同一類型的ViewHolder存放在ArrayList 中,且默認(rèn)最多存儲(chǔ)5個(gè)蕊玷。
// mCachedViews緩存放不下的時(shí)候邮利,才會(huì)把緩存放進(jìn)mRecyclerPool,里面的緩存都是需要重新綁定數(shù)據(jù)的垃帅。
// 從mRecyclerPool中取出的ViewHolder只能復(fù)用于相同viewType的表項(xiàng)延届。
RecycledViewPool mRecyclerPool;

最差情況:重新創(chuàng)建ViewHolder綁定數(shù)據(jù)
次好情況:復(fù)用ViewHolder需要重新綁定數(shù)據(jù)
最好情況:復(fù)用ViewHolder不需要重新綁定數(shù)據(jù)

談?wù)刴ChangedScrap

生命周期只有預(yù)布局的時(shí)候。
mChangedScrap的調(diào)用場景是notifyItemChanged和notifyItemRangeChanged贸诚,只有發(fā)生變化的ViewHolder才會(huì)放入到mChangedScrap中方庭。
mChangedScrap緩存中的ViewHolder是需要調(diào)用onBindViewHolder方法重新綁定數(shù)據(jù)的。

淺談幾種更新RecyclerView的區(qū)別

notifyItemInserted


需要重新布局酱固,A械念、B、C都可以從mAttachedScrap緩存拿出來直接使用运悲,不需要綁定龄减,a需要?jiǎng)?chuàng)建對(duì)應(yīng)的ViewHolder重新綁定,添加進(jìn)一級(jí)緩存班眯。

注意如果是A希停,B,C署隘,D移除B宠能,B還是在mAttachedScrap緩存,只不過FLAG是REMOVE定踱。

notifyDataSetChanged


代表數(shù)據(jù)全面發(fā)生變化棍潘,屏幕上的內(nèi)容標(biāo)為無效,屏幕上的元素全部緩存到四級(jí)緩存RecycledViewPool,屏幕上的元素都需要重新綁定。

notifyItemChanged


A被添加了FLAG_UPDATE亦歉,在scrapView(View view)中不滿足!holder.isUpdated()所以會(huì)被放入到mChangedScrap恤浪,然后在緩存復(fù)用時(shí)B、C肴楷、D都可以直接使用水由,A因?yàn)楸恍薷牧怂孕枰匦陆壎ㄒ幌隆?br> 也就是說:notifyItemChanged將屏幕上的元素保存到一級(jí)緩存中,有更改的保存到mChangedScrap中并且需要重新綁定赛蔫,沒有變化的保存到mAttachedScrap中砂客。

重點(diǎn)類

Recycler:管理復(fù)用
LayoutManager:管理布局

detach和remove

一個(gè)View只是暫時(shí)被清除掉,稍后立刻就要用到呵恢,使用detach鞠值。它會(huì)被緩存進(jìn)scrapCache的區(qū)域。
一個(gè)View不再顯示在屏幕上渗钉,需要被清除掉彤恶,并且下次再顯示它的時(shí)機(jī)目前未知,使用remove鳄橘。它會(huì)被以viewType分組声离,緩存進(jìn)RecyclerViewPool里。

scrap view的生命周期

在將表項(xiàng)一個(gè)個(gè)填充到列表之前會(huì)先將其先回收到mAttachedScrap中瘫怜,回收數(shù)據(jù)的來源是LayoutManager的孩子术徊,而LayoutManager的孩子都是屏幕上可見的或即將可見的表項(xiàng)。
RecyclerView布局的最后一步鲸湃,清除scrap view赠涮。
mAttachedScrap生命周期起始于RecyclerView布局開始,終止于RecyclerView布局結(jié)束唤锉。

RecyclerView的動(dòng)畫

列表中有兩個(gè)表項(xiàng)(1世囊、2),刪除2窿祥,此時(shí)3會(huì)從屏幕底部平滑地移入并占據(jù)原來2的位置株憾。
為了實(shí)現(xiàn)該效果,RecyclerView的策略是:為動(dòng)畫前的表項(xiàng)先執(zhí)行一次pre-layout晒衩,將不可見的表項(xiàng)3也加載到布局中嗤瞎,形成一張布局快照(1、2听系、3)贝奇。再為動(dòng)畫后的表項(xiàng)執(zhí)行一次post-layout,同樣形成一張布局快照(1靠胜、3)掉瞳。比對(duì)兩張快照中表項(xiàng)3的位置毕源,就知道表項(xiàng)3該如何做動(dòng)畫了,表項(xiàng)2做消失動(dòng)畫陕习,當(dāng)動(dòng)畫結(jié)束后霎褐,item2的ViewHolder會(huì)被回收。

RecyclerView為了實(shí)現(xiàn)表項(xiàng)動(dòng)畫该镣,進(jìn)行了2次布局(預(yù)布局+后布局)冻璃,在源碼上表現(xiàn)為LayoutManager.onLayoutChildren()被調(diào)用2次。
預(yù)布局的過程始于RecyclerView.dispatchLayoutStep1()损合,終于RecyclerView.dispatchLayoutStep2()省艳。

在每次向RecyclerView填充表項(xiàng)之前都會(huì)先清空LayoutManager中現(xiàn)存表項(xiàng),將它們detach并同時(shí)緩存入mAttachedScrap列表中嫁审。在緊接著的填充表項(xiàng)階段跋炕,就立馬從mAttachedScrap中取出剛被 detach的表項(xiàng)并重新attach它們。

pre-layout
LinearLayoutManager#onLayoutChildren

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ...
    // 在填充表項(xiàng)之前會(huì)遍歷所有子表項(xiàng)土居,并逐個(gè)回收
    detachAndScrapAttachedViews(recycler);
    ...
    // 填充表項(xiàng)
    fill()
}

post-layout
因?yàn)長ayoutManager中現(xiàn)有表項(xiàng)1枣购、2嬉探、3擦耀,所以scrap完成后,mAttachedScrap中存有表項(xiàng)1涩堤、2眷蜓、3的ViewHolder實(shí)例(position依次為0、0胎围、1吁系,被移除表項(xiàng)的position會(huì)被置0)。分別填充position位置為0和1的表項(xiàng)白魂。為2的位置緩存就不會(huì)命中汽纤。
緩存命中規(guī)則:position相同,并且表項(xiàng)沒被移除福荸。

為什么這么設(shè)計(jì):
為了確定動(dòng)畫的種類和起終點(diǎn)蕴坪,需要比對(duì)動(dòng)畫前和動(dòng)畫后的兩張“表項(xiàng)快照”,不然只知道最終位置不知道起始位置敬锐。
為了獲得兩張快照背传,就得布局兩次,分別是預(yù)布局和后布局(布局即是往列表中填充表項(xiàng))台夺,
為了讓兩次布局互不影響径玖,就不得不在每次布局前先清除上一次布局的內(nèi)容(就好比先清除畫布,重新作畫)颤介,
但是兩次布局中所需的某些表項(xiàng)大概率是一摸一樣的梳星,若在清除畫布時(shí)赞赖,把表項(xiàng)的所有信息都一并清除,那重新作畫時(shí)就會(huì)花費(fèi)更多時(shí)間(重新創(chuàng)建 ViewHolder 并綁定數(shù)據(jù))冤灾,
RecyclerView 采取了用空間換時(shí)間的做法:在清除畫布時(shí)把表項(xiàng)緩存在scrap結(jié)構(gòu)中薯定,以便在填充表項(xiàng)可以命中緩存,以縮短填充表項(xiàng)耗時(shí)瞳购。

整體總結(jié)

  • Recycler有4個(gè)層次用于緩存ViewHolder對(duì)象话侄,優(yōu)先級(jí)從高到底依次為ArrayList<ViewHolder> mAttachedScrap肉渴、ArrayList<ViewHolder> mCachedViews杨赤、ViewCacheExtension mViewCacheExtensionRecycledViewPool mRecyclerPool呐籽。如果四層緩存都未命中盏浇,則重新創(chuàng)建并綁定ViewHolder對(duì)象变丧。
  • 緩存性能:
    都不需要重新創(chuàng)建ViewHolder,只有RecycledViewPool绢掰,mChangedScrap需要重新綁定數(shù)據(jù)痒蓬。
  • 緩存容量:
    mAttachedScrap:沒有大小限制,但最多包含屏幕可見表項(xiàng)滴劲。
    mCachedViews:默認(rèn)大小限制為2攻晒,放不下時(shí),按照先進(jìn)先出原則將最先進(jìn)入的ViewHolder存入回收池以騰出空間班挖。
    mRecyclerPool:對(duì)ViewHolder按viewType分類存儲(chǔ)(通過SparseArray)鲁捏,同類ViewHolder存儲(chǔ)在默認(rèn)大小為5的ArrayList中。
  • 緩存用途:
    mAttachedScrap:用于布局過程中屏幕可見表項(xiàng)的回收和復(fù)用萧芙。
    mCachedViews:用于移出屏幕表項(xiàng)的回收和復(fù)用给梅,且只能用于指定位置的表項(xiàng),有點(diǎn)像“回收池預(yù)備隊(duì)列”双揪,即總是先回收到mCachedViews动羽,當(dāng)它放不下的時(shí)候,按照先進(jìn)先出原則將最先進(jìn)入的ViewHolder存入回收池渔期。
    mRecyclerPool:用于移出屏幕表項(xiàng)的回收和復(fù)用运吓,且只能用于指定viewType的表項(xiàng)
  • 緩存結(jié)構(gòu):
    mAttachedScrap:ArrayList<ViewHolder>
    mCachedViews:ArrayList<ViewHolder>
    mRecyclerPool:對(duì)ViewHolder按viewType分類存儲(chǔ)在SparseArray<ScrapData>中,同類ViewHolder存儲(chǔ)在ScrapData中的ArrayList中

參考
RecyclerView 源碼分析2-緩存機(jī)制圖解
RecyclerView 面試題 | 哪些情況下表項(xiàng)會(huì)被回收到緩存池擎场?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末羽德,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子迅办,更是在濱河造成了極大的恐慌宅静,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件站欺,死亡現(xiàn)場離奇詭異姨夹,居然都是意外死亡纤垂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門磷账,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峭沦,“玉大人,你說我怎么就攤上這事逃糟『鹩悖” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵绰咽,是天一觀的道長菇肃。 經(jīng)常有香客問我,道長取募,這世上最難降的妖魔是什么琐谤? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮玩敏,結(jié)果婚禮上斗忌,老公的妹妹穿的比我還像新娘。我一直安慰自己旺聚,他們只是感情好织阳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翻屈,像睡著了一般陈哑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上伸眶,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音刽宪,去河邊找鬼厘贼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛圣拄,可吹牛的內(nèi)容都是我干的嘴秸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庇谆,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼岳掐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起饭耳,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤串述,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寞肖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纲酗,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衰腌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了觅赊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片右蕊。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吮螺,靈堂內(nèi)的尸體忽然破棺而出饶囚,到底是詐尸還是另有隱情,我是刑警寧澤鸠补,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布坯约,位于F島的核電站,受9級(jí)特大地震影響莫鸭,放射性物質(zhì)發(fā)生泄漏闹丐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一被因、第九天 我趴在偏房一處隱蔽的房頂上張望卿拴。 院中可真熱鬧,春花似錦梨与、人聲如沸堕花。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缘挽。三九已至,卻和暖如春呻粹,著一層夾襖步出監(jiān)牢的瞬間壕曼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工等浊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腮郊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓筹燕,卻偏偏與公主長得像轧飞,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撒踪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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