Listview 局部item刷新的機制

對于一個有限性列表,最長不大于10灶伊,做flutter的同事選擇了逐個控件生成疆前,自行構(gòu)建list的方式來實現(xiàn)。

I/flutter (29896): ************callApi complete:2019-04-08 15:26:58.294069
I/flutter (29896): ************ips buildView:2019-04-08 15:26:58.605058
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.606843
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.612228
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.614787
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.617380
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.620302
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.623194
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.625957
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.628533
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.631107
I/flutter (29896): ************IpsItem build:2019-04-08 15:26:58.633696

整個過程耗時倒是也不算長聘萨,但是其實更新的Item只??個竹椒,逐個生成明顯不太必要。
所以回去看了看RecyclerView是怎么考慮的單個數(shù)據(jù)更新米辐,應(yīng)該用的還是比較多的

mAdapter.notifyItemChanged(pos);

位于RecyclerView.Adapter

public final void notifyItemChanged(int position) {
    mObservable.notifyItemRangeChanged(position, 1);
}

其中mObservable是AdapterDataObservable(繼承自O(shè)bservable)的對象

public void notifyItemRangeChanged(int positionStart, int itemCount) {
    notifyItemRangeChanged(positionStart, itemCount, null);
}

public void notifyItemRangeChanged(int positionStart, int itemCount,
        @Nullable Object payload) {
    // since onItemRangeChanged() is implemented by the app, it could do anything, including
    // removing itself from {@link mObservers} - and that could cause problems if
    // an iterator is used on the ArrayList {@link mObservers}.
    // to avoid such problems, just march thru the list in the reverse order.
    for (int i = mObservers.size() - 1; i >= 0; i--) {
        mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
    }
}

mObservers.get(i)來自O(shè)bservable<AdapterDataObserver>胸完,所以最終走的是AdapterDataObserver.onItemRangeChanged

private class RecyclerViewDataObserver extends AdapterDataObserver {
    RecyclerViewDataObserver() {
    }

    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;

        processDataSetCompletelyChanged(true);
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }

    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    void triggerUpdateProcessor() {
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
}

好,我們繼續(xù)mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)

/**
 * @return True if updates should be processed.
 */
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    if (itemCount < 1) {
        return false;
    }
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
    mExistingUpdateTypes |= UpdateOp.UPDATE;
    return mPendingUpdates.size() == 1;
}

在mPendingUpdates添加了個UpdateOp.UPDATE事件翘贮。
triggerUpdateProcessor

void triggerUpdateProcessor() {
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}
static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16;
由于我沒有動過mHasFixedSize赊窥,所以它默認(rèn)是false的。
public void setHasFixedSize(boolean hasFixedSize) {
    mHasFixedSize = hasFixedSize;
}
也就是說整個方法最后走的是else, 即mAdapterUpdateDuringMeasure置為true狸页。

馬上我們在onMeasure里就見到它了锨能。

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    if (mLayout.isAutoMeasureEnabled()) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);

        /**
         * This specific call should be considered deprecated and replaced with
         * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
         * break existing third party code but all documentation directs developers to not
         * override {@link LayoutManager#onMeasure(int, int)} when
         * {@link LayoutManager#isAutoMeasureEnabled()} returns true.
         */
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

        final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (measureSpecModeIsExactly || mAdapter == null) {
            return;
        }

        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
        // consistency
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        dispatchLayoutStep2();

        // now we can get the width and height from the children.
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

        // if RecyclerView has non-exact width and height and if there is at least one child
        // which also has non-exact width & height, we have to re-measure.
        if (mLayout.shouldMeasureTwice()) {
            mLayout.setMeasureSpecs(
                    MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();
            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        }
    } else {
        if (mHasFixedSize) {
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            return;
        }
        // custom onMeasure
        // mAdapterUpdateDuringMeasure 這貨我們剛才整成true了
        if (mAdapterUpdateDuringMeasure) {
            startInterceptRequestLayout();
            onEnterLayoutOrScroll();
            processAdapterUpdatesAndSetAnimationFlags(); // 我們看看這個
            onExitLayoutOrScroll();

            if (mState.mRunPredictiveAnimations) {
                mState.mInPreLayout = true;
            } else {
                // consume remaining updates to provide a consistent state with the layout pass.
                mAdapterHelper.consumeUpdatesInOnePass();
                mState.mInPreLayout = false;
            }
            mAdapterUpdateDuringMeasure = false;
            stopInterceptRequestLayout(false);
        } else if (mState.mRunPredictiveAnimations) {
            // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
            // this means there is already an onMeasure() call performed to handle the pending
            // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
            // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
            // because getViewForPosition() will crash when LM uses a child to measure.
            setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
            return;
        }

        if (mAdapter != null) {
            mState.mItemCount = mAdapter.getItemCount();
        } else {
            mState.mItemCount = 0;
        }
        startInterceptRequestLayout();
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        stopInterceptRequestLayout(false);
        mState.mInPreLayout = false; // clear
    }
}
private void processAdapterUpdatesAndSetAnimationFlags() {
    if (mDataSetHasChangedAfterLayout) {
        // Processing these items have no value since data set changed unexpectedly.
        // Instead, we just reset it.
        mAdapterHelper.reset();
        if (mDispatchItemsChangedEvent) {
            mLayout.onItemsChanged(this);
        }
    }
    // simple animations are a subset of advanced animations (which will cause a
    // pre-layout step)
    // If layout supports predictive animations, pre-process to decide if we want to run them
    if (predictiveItemAnimationsEnabled()) {
        mAdapterHelper.preProcess();
    } else {
        mAdapterHelper.consumeUpdatesInOnePass();  // 走了這里
    }
    boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
    mState.mRunSimpleAnimations = mFirstLayoutComplete
            && mItemAnimator != null
            && (mDataSetHasChangedAfterLayout
            || animationTypeSupported
            || mLayout.mRequestedSimpleAnimations)
            && (!mDataSetHasChangedAfterLayout
            || mAdapter.hasStableIds());
    mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
            && animationTypeSupported
            && !mDataSetHasChangedAfterLayout
            && predictiveItemAnimationsEnabled();
}
private boolean predictiveItemAnimationsEnabled() {
    return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
}
mItemAnimator 默認(rèn)不為空 但是mLayout.supportsPredictiveItemAnimations()默認(rèn)是false
ItemAnimator mItemAnimator = new DefaultItemAnimator();

/**
 * @return true if this LayoutManager supports predictive item animations, false otherwise.
 */
public boolean supportsPredictiveItemAnimations() {
    return false;
}

經(jīng)上所述, 最后mAdapterHelper.consumeUpdatesInOnePass(), 記得咱們的事件是UpdateOp.UPDATE

AdapterHelper.java
/**
 * Skips pre-processing and applies all updates in one pass.
 */
void consumeUpdatesInOnePass() {
    // we still consume postponed updates (if there is) in case there was a pre-process call
    // w/o a matching consumePostponedUpdates.
    consumePostponedUpdates();
    final int count = mPendingUpdates.size();
    for (int i = 0; i < count; i++) {
        UpdateOp op = mPendingUpdates.get(i);
        switch (op.cmd) {
            case UpdateOp.ADD:
                mCallback.onDispatchSecondPass(op);
                mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
                break;
            case UpdateOp.REMOVE:
                mCallback.onDispatchSecondPass(op);
                mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
                break;
            case UpdateOp.UPDATE:
                mCallback.onDispatchSecondPass(op);
                mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
                break;
            case UpdateOp.MOVE:
                mCallback.onDispatchSecondPass(op);
                mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
                break;
        }
        if (mOnItemProcessedCallback != null) {
            mOnItemProcessedCallback.run();
        }
    }
    recycleUpdateOpsAndClearList(mPendingUpdates);
    mExistingUpdateTypes = 0;
}

callback肯定在RecyclerView里面

@Override
public void onDispatchSecondPass(AdapterHelper.UpdateOp op) {
    dispatchUpdate(op);
}

void dispatchUpdate(AdapterHelper.UpdateOp op) {
    switch (op.cmd) {
        case AdapterHelper.UpdateOp.ADD:
            mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
            break;
        case AdapterHelper.UpdateOp.REMOVE:
            mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
            break;
        case AdapterHelper.UpdateOp.UPDATE:
            mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
                    op.payload);
            break;
        case AdapterHelper.UpdateOp.MOVE:
            mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
            break;
    }
}

LayoutManager mLayout; layout 是layoutManager, onItemsUpdated也沒復(fù)寫好像沒做什么芍耘。
@Override
public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
    viewRangeUpdate(positionStart, itemCount, payload);
    mItemsChanged = true;
}

/**
 * Rebind existing views for the given range, or create as needed.
 *
 * @param positionStart Adapter position to start at
 * @param itemCount Number of views that must explicitly be rebound
 */
void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    final int positionEnd = positionStart + itemCount;

    for (int i = 0; i < childCount; i++) {
        final View child = mChildHelper.getUnfilteredChildAt(i);
        final ViewHolder holder = getChildViewHolderInt(child);
        if (holder == null || holder.shouldIgnore()) {
            continue;
        }
        if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
            // We re-bind these view holders after pre-processing is complete so that
            // ViewHolders have their final positions assigned.
            holder.addFlags(ViewHolder.FLAG_UPDATE);
            holder.addChangePayload(payload);
            // lp cannot be null since we get ViewHolder from it.
            ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; // 是getItemDecorInsetsForChild里會用到的 
        }
    }
    mRecycler.viewRangeUpdate(positionStart, itemCount);
}

flag設(shè)置完以后就requestLayout了址遇。關(guān)鍵點就是在哪里區(qū)分holder.addFlags(ViewHolder.FLAG_UPDATE);

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}
void dispatchLayout() {
    if (mAdapter == null) {
        Log.e(TAG, "No adapter attached; skipping layout");
        // leave the state in START
        return;
    }
    if (mLayout == null) {
        Log.e(TAG, "No layout manager attached; skipping layout");
        // leave the state in START
        return;
    }
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
            || mLayout.getHeight() != getHeight()) {
        // First 2 steps are done in onMeasure but looks like we have to run again due to
        // changed size.
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
}

最正常的時候绞愚,刷新走的路徑如下:

onMeasure 
Element 4 set.
onLayout 
onDraw 
onDraw

偶爾不正常的時候藻三,比如第一次調(diào)用卢厂,刷新走的路徑會是兩個Element被刷新了誓军,我還沒太細看辙谜,先記著:

onMeasure 
Element 1 set.
Element 5 set.
onLayout 
onDraw 
onDraw

還是從log和stack來看,這樣比較容易一把看完整個流程

@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
    Log.d(TAG, "Element " + position + " set.", new RuntimeException());
}
RecyclerView
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    Log.d(TAG, "onLayout ");
}

onBindViewHolder其實是super.onLayout調(diào)用的苍日。

at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6673)
    at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6714)
    at android.support.v7.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5647)
    at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5913)
    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5752)
    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5748)
    at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2232)
    at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1559)
    at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1519)
    at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:614)
    at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3812)
    at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3529)
    at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:4082)
    at com.mango.irene.recyclerviewdemo.MyRecyclerView.onLayout(MyRecyclerView.java:35)
    at android.view.View.layout(View.java:17702)
    at android.view.ViewGroup.layout(ViewGroup.java:5577)
    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1741)
    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1585)
    at android.widget.LinearLayout.onLayout(LinearLayout.java:1494)
    at android.view.View.layout(View.java:17702)
    at android.view.ViewGroup.layout(ViewGroup.java:5577)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
    at android.view.View.layout(View.java:17702)
    at android.view.ViewGroup.layout(ViewGroup.java:5577)
    at android.support.v7.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:443)
    at android.view.View.layout(View.java:17702)
    at android.view.ViewGroup.layout(ViewGroup.java:5577)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
    at android.view.View.layout(View.java:17702)
    at android.view.ViewGroup.layout(ViewGroup.java:5577)
    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1741)
    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1585)
    at android.widget.LinearLayout.onLayout(LinearLayout.java:1494)
    at android.view.View.layout(View.java:17702)
    at android.view.ViewGroup.layout(ViewGroup.java:5577)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
    at com.android.internal.policy.DecorView.onLayout(DecorView.java:729)
    at android.view.View.layout(View.java:17702)
    at android.view.ViewGroup.layout(ViewGroup.java:5577)
    at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2426)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2148)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1334)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6499)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:934)
    at android.view.Choreographer.doCallbacks(Choreographer.java:746)
    at android.view.Choreographer.doFrame(Choreographer.java:677)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:920)
    at android.os.Handler.handleCallback(Handler.java:754)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:163)
    at android.app.ActivityThread.main(ActivityThread.java:6401)

真正用到ViewHolder.FLAG_UPDATE是在RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5913)

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()) {
    if (DEBUG && holder.isRemoved()) {
        throw new IllegalStateException("Removed holder should be bound and it should"
                + " come here only in pre-layout. Holder: " + holder
                + exceptionLabel());
    }
    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
    bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}

這么多年也沒怎么寫過流程府喳,這邏輯還是有點混亂。
不過初略上看明白應(yīng)該問題不大乒省,繼續(xù)努力

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巧颈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子袖扛,更是在濱河造成了極大的恐慌砸泛,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛆封,死亡現(xiàn)場離奇詭異唇礁,居然都是意外死亡,警方通過查閱死者的電腦和手機惨篱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門盏筐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砸讳,你說我怎么就攤上這事琢融。” “怎么了簿寂?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵漾抬,是天一觀的道長。 經(jīng)常有香客問我常遂,道長纳令,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任克胳,我火速辦了婚禮平绩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘漠另。我一直安慰自己馒过,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布酗钞。 她就那樣靜靜地躺著腹忽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪砚作。 梳的紋絲不亂的頭發(fā)上窘奏,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音葫录,去河邊找鬼着裹。 笑死,一個胖子當(dāng)著我的面吹牛米同,可吹牛的內(nèi)容都是我干的骇扇。 我是一名探鬼主播摔竿,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼少孝!你這毒婦竟也來了继低?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤稍走,失蹤者是張志新(化名)和其女友劉穎袁翁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婿脸,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡粱胜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了狐树。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焙压。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖抑钟,靈堂內(nèi)的尸體忽然破棺而出涯曲,到底是詐尸還是另有隱情,我是刑警寧澤味赃,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布掀抹,位于F島的核電站虐拓,受9級特大地震影響心俗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蓉驹,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一城榛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧态兴,春花似錦狠持、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绍撞,卻和暖如春正勒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背傻铣。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工章贞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人非洲。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓鸭限,卻偏偏與公主長得像蜕径,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子败京,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

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