Android之觀察者模式

寫在前面

以前聽說過一句話羔杨,說是** 自己寫過代碼總量沒有超過10w行談設(shè)計模式那都是耍流氓 **蝠嘉。我信了瑰妄,所以一直沒怎么系統(tǒng)的看已經(jīng)買了的《Android源碼設(shè)計模式》蕉鸳。最近有個小伙伴在群里問recyclerview怎么刷新數(shù)據(jù),以前大概也做過尉剩,流程也就是那么兩步:1.更新Adapter里數(shù)據(jù)集的引用真慢,讓他指向最新的數(shù)據(jù)集。2.調(diào)用Adapter的notifyDataSetChanged()來更新ui理茎。之后小伙伴又問了notifyDataSetChanged()到底如何更新ui的黑界,當時只是看出了一個觀察者模式,還有一些細節(jié)沒想明白皂林。而且講真的觀察者模式的應(yīng)用還是非常多的朗鸠,無論是Android還是最近很火的RxJava,其中都可以看到觀察者模式的身影础倍,所以決定這周把觀察者模式擼一遍烛占。

1、什么是觀察者模式

觀察者模式(Observer Pattern)定義了對象間一種一對多的依賴關(guān)系著隆,使得每當一個對象改變狀態(tài)扰楼,則所有依賴于它的對象都會得到通知并被自動更新呀癣,觀察者模式又叫做發(fā)布-訂閱(Publish/Subscribe)模式美浦。

定義總是這么精準而抽象,讓我們結(jié)合一些Android中的場景來簡單的理解一下:在Android最常用的點擊事件项栏,我們會通過設(shè)置控件的OnClickListener并傳入一個OnClickListener的實現(xiàn)類來回調(diào)點擊事件浦辨。這種我們便可以將之看做一個觀察者模式。我們的OnClickListener是觀察者沼沈,被觀察者是控件流酬,當有點擊事件的時候控件發(fā)布點擊事件,觀察者OnClickListener就會接收到點擊事件列另。當然了芽腾,說到底,就是回調(diào)页衙。

2摊滔、用回調(diào)寫一個簡單的觀察者模式

首先我們想一個生活中的場景阴绢,來湊個觀察者模式出來。平時我們要燒水吧艰躺,總要派個人看著呻袭,水燒開的時候把電源拔了裝水。在這個場景里水壺就是被觀察者(Observable)腺兴,觀察者(Observer)就是人左电。首先我們用以上我說的,那玩意就是回調(diào)页响,用回調(diào)來寫一個看看篓足。那我們先寫一個被觀察者“水壺”來看看:

public class Kettle<T> {
    Observer<T> o;

    /**
     * 發(fā)布信息
     */
    public void publishEvent(T t){
        if (o == null)
            throw new NullPointerException("you must regist Observer first!");
        notifyData(t);
    }

    /**
     * 通知訂閱者
     */
    public void notifyData(T t){
        o.receiverEvent(t);
    }

    /**
     * 注冊一個觀察者
     */
    public void registObserver(Observer<T> o){
        this.o = o;
    }

    /**
     * 在你需要的時候調(diào)用這個方法,防止內(nèi)存泄露
     */
    public void unregistObserver(){
        this.o = null;
    }
}

首先我們這個水壺是被觀察者闰蚕,內(nèi)部肯定要維護一個觀察者的引用纷纫,或者一個觀察者隊列的引用,方便我們進行回調(diào)陪腌,當然更多的事我們盡量不要通過Observer這個東西來做辱魁,在這個觀察者模式中我希望Observer僅僅作為一個純粹的回調(diào)。因為觀察模式本身的特性之一就是解耦诗鸭,如果你要通過Observer干更多的事無疑會加重Observable和Observer之間的耦合染簇。更多信息可以看代碼,我注釋已經(jīng)寫得很詳細了强岸。

接下來看看上面提到的那個Observer我是咋寫的:

public interface Observer<T> {
    void receiverEvent(T t);
}

很簡單的一個接口锻弓,寫上泛型,嗯蝌箍,順便練習一下泛型...只有一個方法青灼,用來回調(diào)。有接口那我們肯定要有實現(xiàn)類妓盲,我這個場景里說了杂拨,人是觀察者,于是我寫了一個Observer的實現(xiàn)類:

public abstract class People implements Observer<String> {
    @Override
    public void receiverEvent(String s) {
        System.out.println(s);
        dealWithEvent();
    }

    /**
     * 交給用戶去處理事件
     */
    public abstract void dealWithEvent();
}

我把這個People設(shè)計為一個抽象類悯衬,這樣我可以在接收到這個事件的時候做一些簡單的處理(把他打印出來……)弹沽,然后再把具體的邏輯交給這個抽象類的子類來做,我這邏輯比較簡單筋粗,就沒傳什么參數(shù)進去了策橘。最后免不了跑起來看看了~

public class Test {
    public static void main(String[] args){
        //水壺
        Kettle<String> kettle = new Kettle<>();
        
        People people = new People() {
            @Override
            public void dealWithEvent() {
                System.out.println("People:拔電源裝水了~");
            }
        };

        //注冊觀察者
        kettle.registObserver(people);
        //在一定條件下調(diào)用此方法發(fā)布事件
        kettle.publishEvent("Kettle:水燒開了!再不拔電源我要炸了娜亿!");
    }
}
運行結(jié)果

這里實現(xiàn)了一個加單的觀察者模式丽已,觀察者也只能注冊一個,不過例子么买决,簡單的才容易看懂嘛~接下來看一下Java util里自帶的Observable和Observer沛婴,看一下別人的套路~

3辰斋、Java中的觀察者模式

在Java的util包里也有Observable和Observer那么這倆兄弟跟我們上面自己實現(xiàn)的有啥不同呢?首先還是那個水壺的例子瘸味,有了上面的基礎(chǔ)宫仗,我就直接把所有的類和測試代碼甩上來了,相信以各位看官的實力都是小case:

public class HelloWorld {
    public static native String sayHello(String name);

    public static void main(String[] args) {
        //被觀察者
        Kettle kettle = new Kettle();
        //觀察者
        PeopleLookKettle people = new PeopleLookKettle();

        kettle.addObserver(people);
        kettle.notifyPeople("kettle:水燒開了!再不拔電源我要炸了!!");

    }
}

public class Kettle extends Observable {
    public void notifyPeople(String str){
        System.out.println("kettle:我是水壺~");
        setChanged();
        notifyObservers(str);
    }
}

public class PeopleLookKettle implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println((String) arg);
        System.out.println("People:拔電源裝水~");
    }
}

之后還是看一下運行結(jié)果

運行結(jié)果

代碼上完了旁仿,那么這里實現(xiàn)的觀察模式又是個什么套路呢藕夫?不比比直接看源碼,先從簡單的Observer看起:

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

我去枯冈,這跟我上面的設(shè)計不符啊...這貨怎么把Observable傳過來了...這只是設(shè)計類和接口的一些設(shè)計理念不一樣毅贮,作為jdk他需要考慮各種兼容性和安全性的問題,所以不可能像我們客戶端程序員一樣尘奏,很多時候?qū)懙亩挤浅H涡蕴踩臁O炔怀赌敲炊啵覀円吹氖翘茁穨這玩意是個接口炫加,就像我說的那樣瑰煎,做個回調(diào)就行了,剩下的都交給實現(xiàn)類來操心俗孝。

看完了Observer我們再來看看Observable:

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector<>();
    }

    /**
     * Adds an observer to the set of observers for this object, provided
     * that it is not the same as some observer already in the set.
     * The order in which notifications will be delivered to multiple
     * observers is not specified. See the class comment.
     *
     * @param   o   an observer to be added.
     * @throws NullPointerException   if the parameter o is null.
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * Deletes an observer from the set of observers of this object.
     * Passing <CODE>null</CODE> to this method will have no effect.
     * @param   o   the observer to be deleted.
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    /**
     * If this object has changed, as indicated by the
     * <code>hasChanged</code> method, then notify all of its observers
     * and then call the <code>clearChanged</code> method to
     * indicate that this object has no longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and <code>null</code>. In other
     * words, this method is equivalent to:
     * <blockquote><tt>
     * notifyObservers(null)</tt></blockquote>
     *
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#hasChanged()
     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * If this object has changed, as indicated by the
     * <code>hasChanged</code> method, then notify all of its observers
     * and then call the <code>clearChanged</code> method to indicate
     * that this object has no longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and the <code>arg</code> argument.
     *
     * @param   arg   any object.
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#hasChanged()
     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    /**
     * Clears the observer list so that this object no longer has any observers.
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * Marks this <tt>Observable</tt> object as having been changed; the
     * <tt>hasChanged</tt> method will now return <tt>true</tt>.
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * Indicates that this object has no longer changed, or that it has
     * already notified all of its observers of its most recent change,
     * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.
     * This method is called automatically by the
     * <code>notifyObservers</code> methods.
     *
     * @see     java.util.Observable#notifyObservers()
     * @see     java.util.Observable#notifyObservers(java.lang.Object)
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * Tests if this object has changed.
     *
     * @return  <code>true</code> if and only if the <code>setChanged</code>
     *          method has been called more recently than the
     *          <code>clearChanged</code> method on this object;
     *          <code>false</code> otherwise.
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#setChanged()
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     *
     * @return  the number of observers of this object.
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}

可以看出Observable內(nèi)部使用了一個Vector來維護訂閱的Observer酒甸,關(guān)于Vector這里不做更多的了解,在這就把他當做一個普通的Observer容器就行了赋铝。讓我們看看和這個容器有關(guān)的套路插勤,為了防止各位產(chǎn)生代碼疲勞,我特意貼心的給各位截了個圖23333333:

套路

添加和刪除Observer就是在容器obs里做增刪操作革骨,這套路很簡單农尖,不過為了線程安全加了個synchronized。之后看一下重點良哲,通知Observers時調(diào)用的notifyObservers()盛卡,notifyObservers()最終會調(diào)用他自身帶參的重載方法,看下代碼:

    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

先除去那大段的注釋不看臂外,這里就是拿到Observable內(nèi)部維護的Observer容器窟扑,然后遍歷回調(diào)這些Observer的update方法以實現(xiàn)讓所有Observer收到通知。但是后面那段代碼被執(zhí)行是有條件的漏健,就是Observable內(nèi)部的changed字段為true才會執(zhí)行,而這個字段只有通過setChanged()方法來將其值置為true橘霎。但是在上面的源碼中我們可以發(fā)現(xiàn)這個方法是protected修飾的蔫浆,所以不通過特殊手段的話,我們只有通過繼承才能來調(diào)用這個方法了姐叁。所以在我以上的實現(xiàn)代碼中是有一個繼承于Observable的類的瓦盛。

回頭再看看那段注釋(自己的爛翻譯...有錯請指出...):
我們不想讓Observer在持有他自己的監(jiān)聽時在回調(diào)任意代碼洗显。抽取這段代碼存儲存儲Observer需要同步的狀態(tài),但是并不通知這些Observer原环。任何潛在的競爭條件可能會導致的最壞情況是:

  • 新添加的Observer將會錯過一個正在進行的通知
  • 最近被解除注冊的Observer可能會錯誤的同步一個他不關(guān)心的玩意

這些東西說實話挠唆,我只是有一點點想法,并不能說的很清楚嘱吗。我覺得是多線程情況下這段代碼需要加上一個同步鎖玄组,不然可能會引發(fā)他注釋里寫的那兩點糟糕的情況。我接觸的多線程還是有點少的谒麦,所以這段我就先這么翻著俄讹,而且這對我們理解觀察者模式的套路并沒有非常大的影響。

分析完了Java中的觀察者模式绕德,接下來回到文章最前面提到的那個問題患膛,RecyclerView中的ui更新到底是咋回事。

4耻蛇、RecyclerView中的ui更新

其實要弄清楚這個首先得看Adapter踪蹬,因為Adapter才是掌控數(shù)據(jù)集的那個。那么讓我們來看一下RecyclerView.Adapter的nonotifyDataSetChanged()方法

        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

繼續(xù)追蹤這個源碼臣咖,看看咋回事

    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            // since onChanged() 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).onChanged();
            }
        }

        //其余方法省略
    }

可以看到notifyChanged()這個方法里就兩行代碼延曙,經(jīng)過上面的一番學習,我閉著眼睛也能猜到mObservers是一個Observer的集合亡哄,通過遍歷的去調(diào)用onChanged枝缔,然后這個onChanged是回調(diào)。既然知道這一點蚊惯,那么我們就需要在RecyclerView中找到Observer的具體實現(xiàn)類愿卸,不過在此之前我們找孩子之前得先找他爸~很簡單,追蹤onChanged()的源碼截型,看看到底是誰的方法趴荸。

    public static abstract class AdapterDataObserver {
        public void onChanged() {
            // Do nothing
        }

        public void onItemRangeChanged(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            // fallback to onItemRangeChanged(positionStart, itemCount) if app
            // does not override this method.
            onItemRangeChanged(positionStart, itemCount);
        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeRemoved(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            // do nothing
        }
    }

找到了,都是空方法宦焦,你可能回說這還不是接口发钝,沒事,接口能做的抽象類也能做波闹,我們只要找這個抽象類的孩子就行了酝豪。雞賊的我果斷ctrl+f輸入了我的查找:

嘿嘿嘿,我好雞賊
說了我很雞賊

看一下他的源碼:

    private class RecyclerViewDataObserver extends AdapterDataObserver {
        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            if (mAdapter.hasStableIds()) {
                // TODO Determine what actually changed.
                // This is more important to implement now since this callback will disable all
                // animations because we cannot rely on positions.
                mState.mStructureChanged = true;
                setDataSetChangedAfterLayout();
            } else {
                mState.mStructureChanged = true;
                setDataSetChangedAfterLayout();
            }
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }
    
    //省略部分代碼...

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

這里簡單的看一下第一行代碼是檢查recyclerview精堕,如果有錯就會拋異常孵淘。之后第一個if,不看了歹篓,默認是false瘫证,那么就看第二個條件內(nèi)的代碼揉阎。第一個是存儲一個狀態(tài),第二個是我們要看到的東西了背捌,點進去看下源碼:

    private void setDataSetChangedAfterLayout() {
        //省略部分代碼
        mDataSetHasChangedAfterLayout = true;
        final int childCount = mChildHelper.getUnfilteredChildCount();
        for (int i = 0; i < childCount; i++) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore()) {
                holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
            }
        }
        mRecycler.setAdapterPositionsAsUnknown();
    }

可以看到它會遍歷ViewHolder毙籽,然后給holder添加flag:** FLAG_ADAPTER_POSITION_UNKNOWN**這個flag會讓viewholder重新綁定到recyclerview上以確定自己的position,最后一個方法會讓緩存的viewholder也打上上面提到的flag毡庆。

最后再回顧一下(如果有紕漏敬請指出坑赡,因為我這源碼也沒非常仔細的閱讀):

  • 在想要更新RecyclerView的界面時,我們通常會先更新數(shù)據(jù)源(List之類的)扭仁,然后調(diào)用Adapter的notifyDataSetChanged()方法

  • 在RecyclerView內(nèi)部notifyDataSetChanged()方法調(diào)用了mObservable.notifyChanged();而mObservable是一個被觀察者垮衷。

  • 在RecyclerView內(nèi)部找到mObservable的真實類型,發(fā)現(xiàn)是RecyclerViewDataObserver乖坠,尋找notifyChanged()時會調(diào)用的onChanged()方法搀突。

  • 發(fā)現(xiàn)onChanged()方法最終會給viewholder設(shè)置flag,讓他們重新綁定到RecyclerView上熊泵,在重新綁定的過程中無疑是會在onBindViewHolder里重新設(shè)置數(shù)據(jù)的仰迁,而數(shù)據(jù)源我們已經(jīng)更新過了,新的數(shù)據(jù)就會被顯示到界面上顽分,以上就是這整個流程了徐许。

5、最后的一點思考

說實話最近在寫東西的時候經(jīng)常用回調(diào)卒蘸,因為一些工具類或者dialog雌隅、window之類的,在自己自定義的時候通常需要回調(diào)把點擊事件傳出來缸沃,不然感覺傳view設(shè)置點擊什么的感覺也挺麻煩的恰起,不如我里面邏輯處理好,就把點擊事件傳出來就好了趾牧。但是寫到后面我這又是用的MVP检盼,activity里各種回調(diào)滿天飛,不過怎么說呢翘单,我自己寫的吨枉,我看起來邏輯還是很清晰的。如果是后面來人接手呢哄芜?雖然我注釋寫的都很清晰了貌亭,但是他在閱讀代碼的時候不得不深入我的工具類或者dialog window里去看我這個回調(diào)到底干了什么,所以這種方便自己麻煩別人的東西忠烛,我現(xiàn)在在想到底是算好的代碼風格還是差的属提,有點糊涂。

好了美尸,也挺久沒發(fā)文了冤议,而且干了七天,相信大家都很累了师坎,休息休息了恕酸,祝各位有一個好的周末。

最后安利一下這個主播胯陋,小緣蕊温,安靜聽歌的感覺不錯

參考資料:
《Android源碼設(shè)計模式》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市遏乔,隨后出現(xiàn)的幾起案子义矛,更是在濱河造成了極大的恐慌,老刑警劉巖盟萨,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凉翻,死亡現(xiàn)場離奇詭異,居然都是意外死亡捻激,警方通過查閱死者的電腦和手機制轰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胞谭,“玉大人垃杖,你說我怎么就攤上這事≌梢伲” “怎么了调俘?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旺垒。 經(jīng)常有香客問我彩库,道長,這世上最難降的妖魔是什么袖牙? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任侧巨,我火速辦了婚禮,結(jié)果婚禮上鞭达,老公的妹妹穿的比我還像新娘司忱。我一直安慰自己,他們只是感情好畴蹭,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布坦仍。 她就那樣靜靜地躺著,像睡著了一般叨襟。 火紅的嫁衣襯著肌膚如雪繁扎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音梳玫,去河邊找鬼爹梁。 笑死,一個胖子當著我的面吹牛提澎,可吹牛的內(nèi)容都是我干的姚垃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼盼忌,長吁一口氣:“原來是場噩夢啊……” “哼积糯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谦纱,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤看成,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跨嘉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體川慌,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年偿荷,在試婚紗的時候發(fā)現(xiàn)自己被綠了窘游。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡跳纳,死狀恐怖忍饰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寺庄,我是刑警寧澤艾蓝,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站斗塘,受9級特大地震影響赢织,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜馍盟,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一于置、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贞岭,春花似錦八毯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芯侥,卻和暖如春泊交,著一層夾襖步出監(jiān)牢的瞬間乳讥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工廓俭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留云石,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓白指,卻偏偏與公主長得像留晚,于是被迫代替她去往敵國和親酵紫。 傳聞我的和親對象是個殘疾皇子告嘲,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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