觀察者模式(觸發(fā)聯(lián)動(dòng))

0芯丧、提綱

目錄:
1稍计、舉例:發(fā)起登錄請(qǐng)求
2席赂、Android Adapter 相關(guān)源代碼分析
3援雇、EventBus 相關(guān)源代碼分析
4矛渴、觀察者模式總結(jié)

需要查看其它設(shè)計(jì)模式描述可以查看我的文章《設(shè)計(jì)模式開(kāi)篇》

1惫搏、舉例:發(fā)起登錄請(qǐng)求

現(xiàn)在假設(shè)有登錄接口(login)具温,需要傳入?yún)?shù)(username筐赔、password)茴丰。眾所周知的是網(wǎng)絡(luò)請(qǐng)求本身是耗時(shí)操作达皿,并且 android 不允許在UI 線(xiàn)程發(fā)起網(wǎng)絡(luò)請(qǐng)求天吓。

所以我們會(huì)另開(kāi)辟線(xiàn)程去執(zhí)行登錄操作峦椰,代碼看起來(lái)像下面:

// Thread.java
public class LoginThread extends Thread{

    public void run(){
        Result result = api.login("username","password");
    }
}

我們已經(jīng)獲得了登錄的結(jié)果Result,但是怎樣才能將 Result 告知給客戶(hù)端調(diào)用者呢汤功?

方案1:設(shè)置回調(diào)接口

通過(guò)將ICallback的實(shí)例傳給 Thread 對(duì)象物邑,這樣當(dāng) Thread 對(duì)象內(nèi)部獲取到 Result 實(shí)例時(shí)即可將結(jié)果回調(diào)出去冤竹。

public interface ICallback{
      void onCallback(Result result);
}

方案2:應(yīng)用觀察者模式

再獲取到Result 時(shí)茬射,向發(fā)布訂閱中心發(fā)送一條通知觀察者的事件在抛。由發(fā)布訂閱中心將事件(依據(jù)某種規(guī)則)發(fā)送給訂閱者。它與采用回調(diào)的方式相比最顯著的區(qū)別是:回調(diào)只能針對(duì)單個(gè)對(duì)象進(jìn)行刚梭,而觀察者可以通過(guò)觀察者中心觸發(fā)多個(gè)觀察者對(duì)象聯(lián)動(dòng)朴读。

觀察者模式

觀察者的行為其實(shí)也很好理解衅金,整個(gè)過(guò)程可以劃分為4個(gè)部分:
1、向注冊(cè)中心注冊(cè)(向花店訂購(gòu)了每周一束花的套餐)
2鉴吹、外部發(fā)送事件(每天送花人都會(huì)將花送到花店)
3惩琉、獲取訂閱對(duì)象(花店老板檢查到你本周的花還沒(méi)有配送瞒渠,于是將你列入待配送的清單)
4、通知訂閱對(duì)象(由送花員將花束送到你的家里)

理論的東西不講太多嫩痰,下面我們結(jié)合源代碼進(jìn)行分析始赎。

2、Android Adapter 相關(guān)源代碼分析

2.1魔招、Adapter

我們都知道 Adapter 意味著數(shù)據(jù)源五辽,往往數(shù)據(jù)源的改動(dòng)會(huì)影響著 UI 的變更。所以對(duì) Adapter 的分析自然是我們第一步要做的事情乡翅。

public interface Adapter {
    void registerDataSetObserver(DataSetObserver observer);
    void unregisterDataSetObserver(DataSetObserver observer);
    // 省略其他代碼
}

Adapter的接口規(guī)范中就已經(jīng)定義了注冊(cè)與反注冊(cè)DataSetObserver實(shí)例的方法蠕蚜。注冊(cè)DataSetObserver的目的悔橄,是為了在適配器內(nèi)的數(shù)據(jù)發(fā)生更改時(shí)進(jìn)行調(diào)用。

2.2挣柬、DataSetObserver

DataSetObserver是數(shù)據(jù)觀察員睛挚,它的代碼定義如下扎狱。它關(guān)注兩個(gè)維度的數(shù)據(jù)變更,數(shù)據(jù)發(fā)生改變 或者 數(shù)據(jù)失效床牧。

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

    public void onInvalidated() {
        // Do nothing
    }
}

2.3戈咳、ListAdapter

因?yàn)槲覀儾⒉淮蛩忝撾xListView分析抽象的玩意壕吹,所以我們回歸到實(shí)例ListAdapter耳贬。讓我們看看ListAdapter的實(shí)現(xiàn),ListAdapter繼承了接口Adapter并擴(kuò)充一些適用于List場(chǎng)景的接口方法顷蟆。

ListAdapter的類(lèi)圖

2.4帐偎、BaseAdapter

由于ListAdapter是接口削樊,所以我們?nèi)孕璨檎覍?shí)現(xiàn)了該接口的類(lèi)——即BaseAdapter

我們看到了DataSetObservable是被觀察的對(duì)象甸箱,是真正觸發(fā)觀察者對(duì)象聯(lián)動(dòng)的源頭迅脐。

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    // 定義了被觀察的對(duì)象仪际,即只要這個(gè)對(duì)象發(fā)生變更树碱。那么訂閱它的對(duì)象变秦,都有機(jī)會(huì)觸發(fā)行為蹦玫。
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    public void registerDataSetObserver(DataSetObserver observer) {
        // 將訂閱者注冊(cè)
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        // 取消注冊(cè)
        mDataSetObservable.unregisterObserver(observer);
    }

   public void notifyDataSetChanged() {
        // 通知訂閱者數(shù)據(jù)已變更
        mDataSetObservable.notifyChanged();
    }

   public void notifyDataSetInvalidated() {
        // 通知訂閱者數(shù)據(jù)已失效
        mDataSetObservable.notifyInvalidated();
    }
}

2.5、DataSetObservable

你可以將DataSetObservable理解為向訂閱對(duì)象觸發(fā)行為的實(shí)現(xiàn)挣输,它們可以選擇向誰(shuí)發(fā)送撩嚼、發(fā)送什么樣的事件挖帘。比如:DataSetObservable就是遍歷所有的訂閱者并向他們推送信息拇舀。

public class DataSetObservable extends Observable<DataSetObserver> {
    public void notifyChanged() {
        synchronized(mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    public void notifyInvalidated() {
        synchronized (mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onInvalidated();
            }
        }
    }
}

2.6骄崩、Observable

Observable<T>是所有訂閱中心的模板類(lèi)薄辅,它為提供不同的模板策略提供了抽象的實(shí)現(xiàn)长搀。

public abstract class Observable<T> {

    protected final ArrayList<T> mObservers = new ArrayList<T>();

    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

    public void unregisterObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            int index = mObservers.indexOf(observer);
            if (index == -1) {
                throw new IllegalStateException("Observer " + observer + " was not registered.");
            }
            mObservers.remove(index);
        }
    }

    public void unregisterAll() {
        synchronized(mObservers) {
            mObservers.clear();
        }
    }
}

2.7源请、總結(jié)

觀察者模式(又稱(chēng)發(fā)布/訂閱模式)相比享元或解釋器等模式谁尸,它的模式實(shí)現(xiàn)邏輯非常清晰纽甘。

有的同學(xué)可能對(duì)observer (訂閱者)與 observable(可供訂閱的對(duì)象)這兩個(gè)詞分不清楚悍赢,建議結(jié)合上文中貼出的圖再加以思考,應(yīng)該可以理解它們的差異皮胡。

3赏迟、EventBus 相關(guān)源代碼分析

在分析之前你要先對(duì) EventBus 有些了解锌杀,如果還不知道可以查看EventBus糕再。

// 1、注冊(cè)監(jiān)聽(tīng)
EventBus.getDefault().register(this);

// 2殴蹄、接收事件
public void onEvent(Event event) {
      //  省略細(xì)節(jié)代碼
}
// 3蒿柳、取消注冊(cè)監(jiān)聽(tīng)垒探,防止內(nèi)存泄露
EventBus.getDefault().unregister(this);

3.1圾叼、注冊(cè)&反注冊(cè)

register(this)目的捺癞,是為了將自身句柄注冊(cè)到發(fā)布訂閱中心中髓介,以便發(fā)布訂閱中心向this發(fā)送事件筋现。unregister(this)的目的是為了避免內(nèi)存泄露矾飞。

3.2、register()

// EventBus.java
public void register(Object subscriber) {
        register(subscriber, false, 0);
    }
private synchronized void register(Object subscriber, boolean sticky, int priority) {
  List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
  for (SubscriberMethod subscriberMethod : subscriberMethods) {
      subscribe(subscriber, subscriberMethod, sticky, priority);
  }
}

1豹绪、register方法有三個(gè)參數(shù):訂閱對(duì)象瞒津,是否粘性巷蚪,優(yōu)先級(jí)姻氨。
2肴焊、再調(diào)用注冊(cè)方法時(shí)功戚,首先會(huì)調(diào)用subscriberMethodFinder.findSubscriberMethods查找訂閱對(duì)象中的訂閱方法(即回調(diào)函數(shù))
3啸臀、然后再依次使用回調(diào)函數(shù)執(zhí)行訂閱乘粒。

所以EventBus真正的訂閱對(duì)象是回調(diào)函數(shù)。

3.3轧铁、SubscriberMethodFinder

// SubscriberMethodFinder.java
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        // STEP1:查找緩存旦棉,若有直接返回
        String key = subscriberClass.getName();
        List<SubscriberMethod> subscriberMethods;
        synchronized (methodCache) {
            subscriberMethods = methodCache.get(key);
        }
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        subscriberMethods = new ArrayList<SubscriberMethod>();
        Class<?> clazz = subscriberClass;
        HashSet<String> eventTypesFound = new HashSet<String>();
        StringBuilder methodKeyBuilder = new StringBuilder();

        while (clazz != null) {
            String name = clazz.getName();
            // STEP2:跳過(guò)系統(tǒng)類(lèi),有助于提高性能
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
                break;
            }

            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                String methodName = method.getName();
                // STEP3 :篩選出滿(mǎn)足約定的訂閱函數(shù)
                if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
                    int modifiers = method.getModifiers();
                    if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if (parameterTypes.length == 1) {
                            String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
                            ThreadMode threadMode;
                            if (modifierString.length() == 0) {
                                threadMode = ThreadMode.PostThread;
                            } else if (modifierString.equals("MainThread")) {
                                threadMode = ThreadMode.MainThread;
                            } else if (modifierString.equals("BackgroundThread")) {
                                threadMode = ThreadMode.BackgroundThread;
                            } else if (modifierString.equals("Async")) {
                                threadMode = ThreadMode.Async;
                            } else {
                                if (skipMethodVerificationForClasses.containsKey(clazz)) {
                                    continue;
                                } else {
                                    throw new EventBusException("Illegal onEvent method, check for typos: " + method);
                                }
                            }
                            Class<?> eventType = parameterTypes[0];
                            methodKeyBuilder.setLength(0);
                            methodKeyBuilder.append(methodName);
                            methodKeyBuilder.append('>').append(eventType.getName());
                            String methodKey = methodKeyBuilder.toString();
                            if (eventTypesFound.add(methodKey)) {
                                // Only add if not already found in a sub class
                                subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
                            }
                        }
                    } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
                        Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
                                + methodName);
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
            clazz = clazz.getSuperclass();
        }
        // STEP4:加入緩存
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
                    + ON_EVENT_METHOD_NAME);
        } else {
            synchronized (methodCache) {
                methodCache.put(key, subscriberMethods);
            }
            return subscriberMethods;
        }

}

這段方法有點(diǎn)長(zhǎng)但結(jié)構(gòu)卻比較清晰脸候,大概描述下面幾件事情:
1运沦、檢查緩存,若有則命中如無(wú)則往下執(zhí)行梯刚。
2亡资、檢查是否是系統(tǒng)類(lèi)向叉,若是直接跳過(guò)母谎。
3、檢查是否以onEvent作為首字符串幸斥。
4甲葬、檢查以onEvent作為首字符串的字符串后綴(value)懈贺。

4.1梭灿、value = "",則意味著使用ThreadMode.PostThread配乱。
4.2宪卿、value="MainThread",則意味著使用ThreadMode.MainThread西疤。
4.3代赁、value="BackgroundThread"兽掰,則意味著使用ThreadMode.BackgroundThread孽尽。
4.4、value="Async"瞻讽,則意味著使用ThreadMode.Async

5速勇、如果到此都沒(méi)有發(fā)現(xiàn)以onEvent作為首字符串坎拐,則會(huì)拋出異常哼勇。
6、如果檢查到以onEvent作為首字符串院溺,則會(huì)緩存結(jié)果。

3.4聋溜、執(zhí)行訂閱 subscribe

    // 必須在同步塊中調(diào)用
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
        Class<?> eventType = subscriberMethod.eventType;
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
        // STEP1:準(zhǔn)備容器
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<Subscription>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
             // STEP2:避免重復(fù)訂閱
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        // 自從 EventBus 2.2 開(kāi)始我們強(qiáng)制約定方法是 public 權(quán)限(或你通過(guò)注解的形式改變?cè)L問(wèn)權(quán)限)
        // subscriberMethod.method.setAccessible(true);

        // STEP3:檢查優(yōu)先級(jí)叭爱,將訂閱者放到合適的位置
        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }
        // STEP4:將訂閱方法與訂閱方法內(nèi)的參數(shù)做映射
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<Class<?>>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        // STEP5:檢查是否粘性事件
        if (sticky) {
            Object stickyEvent;
            synchronized (stickyEvents) {
                stickyEvent = stickyEvents.get(eventType);
            }
            if (stickyEvent != null) {
                postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
            }
        }
    }

subscribe方法的本質(zhì)是準(zhǔn)備好訂閱方法-->與訂閱方法的參數(shù)的映射關(guān)系买雾。

// 訂閱方法:onEvent
// 訂閱方法的參數(shù):Event
public void onEvent(Event event) {
    
}

3.5、post

到此位置該準(zhǔn)備好的都準(zhǔn)備好了嗤军,接下來(lái)就等待外部觸發(fā)事件了叙赚。

    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

1震叮、內(nèi)部準(zhǔn)備好List<Object> eventQueue用作事件的派發(fā)苇瓣。
2、如果事件未派發(fā)抽诉,則執(zhí)行派發(fā)奕短。并設(shè)置eventQueue的派發(fā)狀態(tài)為isPosting=true侦啸。
3丧枪、調(diào)用postSingleEvent(eventQueue.remove(0), postingState);執(zhí)行事件派發(fā)拧烦。
4、派發(fā)完成后齐佳,設(shè)置eventQueue的派發(fā)狀態(tài)為isPosting= false炼吴。

3.6疫衩、postSingleEvent

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        // STEP1:檢查事件的繼承性
        if (eventInheritance) {
            // STEP2:向上查找事件類(lèi)型
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            // STEP3:若非繼承性則直接派發(fā)
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        // STEP4:若未查找到事件童芹,則派發(fā)沒(méi)有訂閱該事件
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                Log.d(TAG, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

3.7、postToSubscription

   private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case PostThread:
                invokeSubscriber(subscription, event);
                break;
            case MainThread:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case BackgroundThread:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case Async:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

依據(jù)訂閱方法的特性(解析其后綴)能得到需要在哪個(gè)線(xiàn)程中去接收該回調(diào)。postToSubscription恰恰是將對(duì)應(yīng)的操作放到對(duì)應(yīng)線(xiàn)程的策略方法艇抠。雖然其本身并沒(méi)有什么神奇之處家淤,但通過(guò)層層封裝則會(huì)將客戶(hù)端調(diào)用簡(jiǎn)化到足夠神奇瑟由。

因?yàn)榫€(xiàn)程切換并不是本章的范疇歹苦,所以不展開(kāi)對(duì)每個(gè)線(xiàn)程的調(diào)用分析。當(dāng)前就以PostThread為根據(jù)進(jìn)行后續(xù)分析狠角。

3.8蚪腋、invokeSubscriber

    void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

我們觀察到最終是通過(guò)Methodinvoke方法屉凯,完成對(duì)訂閱方法(onEvent)的調(diào)用悠砚,并且傳入的參數(shù)event灌旧。

// Method.java
    public native Object invoke(Object receiver, Object... args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

3.9 EventBus 總結(jié)

經(jīng)過(guò)一大長(zhǎng)段的代碼分析,我們終于到了尾聲羡玛。
1、EventBus 通過(guò)onEventXXX()的方法或 以@Subscribe注解形式讳窟,約定接收回調(diào)的方法。
2谋右、外部調(diào)用post()方法改执,將參數(shù)event傳入到注冊(cè)中心EventBus坑雅。
3裹粤、內(nèi)部利用反射的 API遥诉,利用 Method反射方法method及參數(shù)event,最終能夠調(diào)用約定接收回調(diào)的方法霉翔。

4 終章

無(wú)論項(xiàng)目的大小债朵、復(fù)雜度如何猫缭,觀察者的主線(xiàn)索其實(shí)一直很清晰——發(fā)布/訂閱猜丹,這對(duì)于我們理解它真的很重要射窒。

觀察者模式的本質(zhì):觸發(fā)聯(lián)動(dòng)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市来吩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戚长,老刑警劉巖同廉,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迫肖,死亡現(xiàn)場(chǎng)離奇詭異攒驰,居然都是意外死亡讼育,警方通過(guò)查閱死者的電腦和手機(jī)奶段,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)痹籍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人棺克,你說(shuō)我怎么就攤上這事娜谊〗锛ィ” “怎么了芭商?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵铛楣,是天一觀的道長(zhǎng)簸州。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拓瞪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任祭埂,我火速辦了婚禮,結(jié)果婚禮上兵钮,老公的妹妹穿的比我還像新娘蛆橡。我一直安慰自己,他們只是感情好掘譬,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布泰演。 她就那樣靜靜地躺著,像睡著了一般葱轩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上靴拱,一...
    開(kāi)封第一講書(shū)人閱讀 52,584評(píng)論 1 312
  • 那天垃喊,我揣著相機(jī)與錄音,去河邊找鬼袜炕。 笑死本谜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的偎窘。 我是一名探鬼主播乌助,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼陌知!你這毒婦竟也來(lái)了他托?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仆葡,失蹤者是張志新(化名)和其女友劉穎赏参,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體浙芙,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡登刺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嗡呼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纸俭。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖南窗,靈堂內(nèi)的尸體忽然破棺而出揍很,到底是詐尸還是另有隱情郎楼,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布窒悔,位于F島的核電站呜袁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏简珠。R本人自食惡果不足惜阶界,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望聋庵。 院中可真熱鬧膘融,春花似錦、人聲如沸祭玉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脱货。三九已至岛都,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間振峻,已是汗流浹背臼疫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铺韧,地道東北人多矮。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像哈打,于是被迫代替她去往敵國(guó)和親塔逃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

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