Android開源框架之EventBus

EventBus是目前比較常用的一個消息總線,用于應(yīng)用程序內(nèi)部的通信茅诱。今天我們就來聊聊EventBus的相關(guān)內(nèi)容。
本文的要點(diǎn)如下:

  • 概述
  • 使用方法
  • 粘性事件
  • 源碼分析
    • 注冊過程
    • 事件的發(fā)布
  • 總結(jié)

概述

EventBus是一個Android端優(yōu)化的publish/subscribe消息總線肩钠,簡化了應(yīng)用程序內(nèi)各組件間挡鞍、組件與后臺線程間的通信。
在沒使用EventBus之前售躁,我們開發(fā)中也會發(fā)布訂閱模式的機(jī)制來處理一些問題坞淮,如果需要用到的地方較多,則每一處需要用到事件的發(fā)布訂閱的地方陪捷,都需要實(shí)現(xiàn)一整套發(fā)布訂閱的機(jī)制回窘,比如定義Listener接口,定義Notification Center/Observable市袖,定義事件類啡直,定義注冊監(jiān)聽者的方法、移除監(jiān)聽者的方法苍碟、發(fā)布事件的方法等酒觅。我們不得不寫許多重復(fù)的冗余的代碼來實(shí)現(xiàn)我們的設(shè)計目的。
既然有重復(fù)冗余微峰,那么就一定可以將整套機(jī)制實(shí)現(xiàn)成一個框架舷丹,來實(shí)現(xiàn)高復(fù)用,減少代碼量蜓肆,提高效率颜凯,EventBus便是如此。

作為一個消息總線仗扬,EventBus主要有三個組成部分:

  1. 事件(Event):可以是任意類型的對象症概。通過事件的發(fā)布者將事件進(jìn)行傳遞。
  2. 事件訂閱者(Subscriber):接收特定的事件厉颤。
  3. 事件發(fā)布者(Publisher):用于通知 Subscriber 有事件發(fā)生穴豫。可以在任意線程任意位置發(fā)送事件逼友。

使用方法

1.添加依賴:

    //eventbus
    implementation 'org.greenrobot:eventbus:3.1.1'

2.定義事件類

public class MyEvent {
    public String name;

    public MyEvent(String name) {
        this.name = name;
    }
}

要發(fā)送事件精肃,我們就要有對應(yīng)的事件類,沒有具體的要求帜乞,可以根據(jù)需求進(jìn)行自定義司抱。

3.注冊/注銷事件

    //注冊事件
    @OnClick(R.id.registevent)
    public void setRegistevent(){
        EventBus.getDefault().register(this);
    }
    
    //處理事件
    @Subscribe(threadMode = ThreadMode.MAIN, priority = 100)
    public void getMyEvent(MyEvent event){
        receive_event.setText(event.name);
    }

    //注銷事件
    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

訂閱部分一般分為兩步,首先通過register(this)來表示該訂閱者進(jìn)行了訂閱黎烈,然后通過Subscribe注解习柠,getMyEvent(MyEvent event)方法表示指定對事件MyEvent的訂閱匀谣。
注意:在注冊之后一定要用unregister(this)注銷事件,以免內(nèi)存泄露资溃。

注解Subscribe中的參數(shù)threadMode 表示線程模式武翎,有四種選項(xiàng):

  1. ThreadMode.POSTING:事件的處理在和事件的發(fā)送在相同的進(jìn)程,所以事件處理時間不應(yīng)太長溶锭,不然影響事件的發(fā)送線程宝恶。
  2. ThreadMode.MAIN:事件的處理會在UI線程中執(zhí)行。事件處理時間不能太長趴捅,這個不用說了垫毙,長了會ANR的。
  3. ThreadMode.BACKGROUND:如果事件是在UI線程中發(fā)布出來的拱绑,那么事件處理就會在子線程中運(yùn)行综芥,如果事件本來就是子線程中發(fā)布出來的,那么事件處理直接在該子線程中執(zhí)行猎拨。所有待處理事件會被加到一個隊(duì)列中膀藐,由對應(yīng)線程依次處理這些事件,如果某個事件處理時間太長红省,會阻塞后面的事件的派發(fā)或處理消请。
  4. ThreadMode.Async:事件處理會在單獨(dú)的線程中執(zhí)行,主要用于在后臺線程中執(zhí)行耗時操作类腮,每個事件會開啟一個線程臊泰。

注解Subscribe中的參數(shù)priority 表示事件優(yōu)先級,事件的優(yōu)先級類似廣播的優(yōu)先級蚜枢,優(yōu)先級越高優(yōu)先獲得消息缸逃。

另外,EventBus允許對事件進(jìn)行截斷:

EventBus.getDefault().cancelEventDelivery(event);

4.發(fā)送事件

    @OnClick(R.id.bt_sendevent)
    public void sendEvent(){
        EventBus.getDefault().post(new MyEvent("0123"));
    }

發(fā)送事件就比較簡單了厂抽,我們可以在代碼的任何位置發(fā)布事件需频,當(dāng)前所有事件類型與發(fā)布的事件類型匹配的訂閱者都將收到它。

粘性事件

EventBus除了普通事件也支持粘性事件筷凤。
粘性事件:訂閱在發(fā)布事件之后昭殉,但同樣可以收到事件(類似于粘性廣播)。訂閱/解除訂閱和普通事件一樣藐守,但是處理訂閱的方法有所不同挪丢,需要注解中添加sticky = true。 用法如下:

@Subscribe(priority = 100,sticky = true)
    public void getMyEvent(MyEvent event){
        receive_event.setText(event.name);
    }

發(fā)送事件:

EventBus.getDefault().postSticky(new MyEvent("這是一條粘性事件"));

移除粘性事件:

//移除某種特定類型的粘性事件
EventBus.getDefault().removeStickyEvent(MyEvent.class);
//移除所有粘性事件
EventBus.getDefault().removeAllStickyEvents();

源碼分析

從用法中不難看出卢厂,獲取EventBus實(shí)例用到的是getDefault方法乾蓬,那么我們來看看源碼:

static volatile EventBus defaultInstance;
 public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

很熟悉,有木有慎恒?單例模式啊任内,標(biāo)準(zhǔn)的雙重檢驗(yàn)鎖的寫法撵渡。不過有個問題,EventBus的構(gòu)造函數(shù)是public而不是private死嗦,似乎違背了單例模式的原則趋距,但其實(shí)是由EventBus的功能決定的,EventBus 是可以創(chuàng)造出很多條總線的越除,在不同的總線之間是不能傳遞事件的(不能通信)棚品。這樣的設(shè)計是為后面可以創(chuàng)建不同的總線。

接著來看EventBus的構(gòu)造函數(shù):

  public EventBus() {
            this(DEFAULT_BUILDER);
  }

  EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
  }

EventBus類提供了一個public的構(gòu)造函數(shù)廊敌,可以用默認(rèn)的EventBusBuilder配置來構(gòu)造EventBus對象。

注冊過程:
還是回過頭來看register方法:

public void register(Object subscriber) {
        //通過反射獲取注冊的對象的類型
        Class<?> subscriberClass = subscriber.getClass();
        //獲取注冊的對象的訂閱方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

首先通過反射獲取注冊的對象的類型门怪,然后通過subscriberMethodFinder實(shí)例的findSubscriberMethods方法來獲取該觀察者類型中的所有訂閱方法骡澈,最后通過subscribe方法將所有的訂閱方法分別進(jìn)行訂閱

下面我們先看下查找訂閱者的方法:
SubscriberMethod類封裝了訂閱方法(使用@Subscribe注解的方法)類型的信息掷空。

public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;
    //省略代碼
}

從代碼中我們不難看出肋殴,實(shí)際上該類就是通過幾個字段來存儲@Subscribe注解中指定的類型信息,以及一個方法的類型變量坦弟。

SubscriberMethodFinder類中的findSubscriberMethods方法:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        //首先從緩存當(dāng)中嘗試找該訂閱者的訂閱方法
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            //找到直接返回
            return subscriberMethods;
        }

        //當(dāng)緩存中沒有找到該觀察者的訂閱方法的時候使用下面的方法獲取方法信息
        if (ignoreGeneratedIndex) {
            //ignoreGeneratedIndex參數(shù)表示是否忽略注解器生成的MyEventBusIndex护锤,默認(rèn)為false
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            //獲取方法信息
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        //沒獲取到,拋出異常
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            //獲取到了酿傍,加緩存烙懦,返回
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

可以看到,邏輯并不難理解赤炒,就是先找緩存氯析,如果緩存中沒有再獲取,獲取后加緩存然后返回莺褒。

那么我們來具體看看獲取方法信息的關(guān)鍵方法:findUsingInfo()

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        //過FindState對象用來存儲找到的方法信息
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        //從當(dāng)前類開始遍歷該類的所有父類
        while (findState.clazz != null) {
            //獲取訂閱者信息
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                //如果使用了MyEventBusIndex掩缓,將會進(jìn)入到這里并獲取訂閱方法信息
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                //未使用MyEventBusIndex將會進(jìn)入這里使用反射獲取方法信息
                findUsingReflectionInSingleClass(findState);
            }
            //將findState.clazz設(shè)置為當(dāng)前的findState.clazz的父類,有點(diǎn)類似于鏈表的遍歷
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

其中關(guān)鍵的方法是findUsingReflectionInSingleClass()遵岩,對當(dāng)前類中聲明的所有方法進(jìn)行校驗(yàn)你辣,并將符合要求的方法的信息封裝成一個SubscriberMethod對象添加到了FindState對象中的列表中。

獲取到訂閱方法后尘执,我們回到subscribe()方法:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        //將所有的觀察者和訂閱方法封裝成一個Subscription對象
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //嘗試從緩存中根據(jù)事件類型來獲取所有的Subscription對象
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            //指定的事件類型沒有對應(yīng)的觀察對象的時候
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }
        //根據(jù)新加入的方法的優(yōu)先級決定插入到隊(duì)列中的位置
        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }
        //從“訂閱者-事件類型”列表中嘗試獲取該訂閱者對應(yīng)的所有事件類型
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        
        //黏性事件
        if (subscriberMethod.sticky) {
            if (eventInheritance) {
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

代碼比較長舍哄,我們來理一下邏輯:
首先,將傳進(jìn)來的觀察者和訂閱方法封裝成一個Subscription對象誊锭,這里才算是真正的訂閱蠢熄。
然后,從CopyOnWriteArrayList中根據(jù)事件類型來獲取所有的Subscription對象組成炉旷,根據(jù)新加入的方法的優(yōu)先級決定插入到隊(duì)列中的哪個位置签孔。
然后叉讥,獲取指定的觀察者對應(yīng)的全部的觀察事件類型,這里也是通過一個哈希表來維護(hù)這種映射關(guān)系的饥追。
最后图仓,根據(jù)當(dāng)前的訂閱方法是否是黏性的,來決定是否將當(dāng)前緩存中的信息發(fā)送給新訂閱的方法但绕。

至此救崔,注冊過程就基本結(jié)束了,注銷的邏輯比較比較簡單捏顺,基本上就是注冊操作反過來六孵,刪除緩存中的訂閱方法。

事件的發(fā)布:
發(fā)布事件的流程是從post方法開始的:

 private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };

final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }

public void post(Object event) {
        //獲取當(dāng)前線程的狀態(tài)信息
        PostingThreadState postingState = currentPostingThreadState.get();
        //獲取當(dāng)前事件隊(duì)列
        List<Object> eventQueue = postingState.eventQueue;
        //將當(dāng)前要發(fā)送的事件加入到隊(duì)列中
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                    //隊(duì)列不空就一直循環(huán)發(fā)送事件
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                //恢復(fù)當(dāng)前線程的信息
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }

邏輯并不復(fù)雜幅骄,首先從currentPostingThreadState中取出當(dāng)前線程的狀態(tài)信息劫窒。currentPostingThreadState是ThreadLocal類型的對象,其中保存者PostingThreadState類型的對象拆座,里面儲存了當(dāng)前線程對應(yīng)的事件列表和線程的狀態(tài)信息等主巍。
然后,將當(dāng)前要發(fā)送的事件加入到隊(duì)列eventQueue中挪凑。
最后孕索,通過循環(huán)調(diào)用postSingleEvent()方法取出事件并進(jìn)行發(fā)布

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        if (eventInheritance) {
            //eventInheritance為ture會查找該事件的所有父類
            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 {
            //處理事件
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        //找不到事件會拋異常
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

可以看到最終使用的是postSingleEventForEventType()方法對每個事件類型進(jìn)行處理躏碳。

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            //獲取指定的事件對應(yīng)的所有的觀察對象
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            //遍歷觀察對象搞旭,并最終執(zhí)行事件的分發(fā)操作
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }

這里的邏輯就非常清晰了,通過傳入的事件類型到緩存中取尋找它對應(yīng)的全部的Subscription菇绵,然后對得到的Subscription列表進(jìn)行遍歷选脊,并依次調(diào)用postToSubscription方法執(zhí)行事件的發(fā)布操作。

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                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);
        }
    }

終于脸甘,我們看到了在訂閱消息時設(shè)置的ThreadMode參數(shù)恳啥,switch語句中會根據(jù)當(dāng)前的線程狀態(tài)和訂閱方法指定的ThreadMode信息來決定合適觸發(fā)方法
這里的invokeSubscriber會在當(dāng)前線程中立即調(diào)用反射來觸發(fā)指定的觀察者的訂閱方法丹诀。
如果當(dāng)前線程不符合ThreadMode參數(shù)中的信息钝的,會根據(jù)具體的情況將事件加入到不同的隊(duì)列中進(jìn)行處理。
mainThreadPoster最終繼承自Handler铆遭,當(dāng)調(diào)用它的enqueue方法的時候硝桩,它會發(fā)送一個事件并在它自身的handleMessage方法中從隊(duì)列中取值并進(jìn)行處理,從而達(dá)到在主線程中分發(fā)事件的目的枚荣。
backgroundPoster實(shí)現(xiàn)了Runnable接口碗脊,它會在調(diào)用enqueue方法的時候,拿到EventBus的ExecutorService實(shí)例橄妆,并使用它來執(zhí)行自己衙伶。在它的run方法中會從隊(duì)列中循環(huán)取值來進(jìn)行執(zhí)行祈坠。

至此,事件的發(fā)布過程也基本結(jié)束矢劲。

總結(jié)

EventBus是一個不錯的開源消息總線庫赦拘,使用了諸如單例模式、觀察者模式芬沉、builder模式等設(shè)計模式躺同。它簡化了應(yīng)用程序內(nèi)各組件間、組件與后臺線程間的通信丸逸,解耦了事件的發(fā)送者和接收者蹋艺,避免了復(fù)雜的、易于出錯的依賴及生命周期問題黄刚,甚至完全可以代替Intent捎谨,boardcast等Android傳統(tǒng)的方法在Fragment,Activity隘击,Service以及不同線程之間傳遞數(shù)據(jù)⊙忻可以使我們的代碼更加簡潔埋同、健壯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棵红,一起剝皮案震驚了整個濱河市凶赁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌逆甜,老刑警劉巖虱肄,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異交煞,居然都是意外死亡咏窿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門素征,熙熙樓的掌柜王于貴愁眉苦臉地迎上來集嵌,“玉大人,你說我怎么就攤上這事御毅「罚” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵端蛆,是天一觀的道長凤粗。 經(jīng)常有香客問我,道長今豆,這世上最難降的妖魔是什么嫌拣? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任柔袁,我火速辦了婚禮,結(jié)果婚禮上亭罪,老公的妹妹穿的比我還像新娘瘦馍。我一直安慰自己,他們只是感情好应役,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布情组。 她就那樣靜靜地躺著,像睡著了一般箩祥。 火紅的嫁衣襯著肌膚如雪院崇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天袍祖,我揣著相機(jī)與錄音底瓣,去河邊找鬼。 笑死蕉陋,一個胖子當(dāng)著我的面吹牛捐凭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凳鬓,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茁肠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缩举?” 一聲冷哼從身側(cè)響起垦梆,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仅孩,沒想到半個月后托猩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辽慕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年京腥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溅蛉。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡绞旅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出温艇,到底是詐尸還是另有隱情因悲,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布勺爱,位于F島的核電站晃琳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卫旱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一人灼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顾翼,春花似錦投放、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拜姿,卻和暖如春烙样,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕊肥。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工谒获, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壁却。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓批狱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親展东。 傳聞我的和親對象是個殘疾皇子赔硫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

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