EventBus設(shè)計(jì)與實(shí)現(xiàn)分析——特性介紹


EventBus是一個(gè) 發(fā)布/訂閱 模式的消息總線庫(kù)晰搀,它簡(jiǎn)化了應(yīng)用程序內(nèi)各組件間蕉拢、組件與后臺(tái)線程間的通信,解耦了事件的發(fā)送者和接收者刻两,避免了復(fù)雜的增蹭、易于出錯(cuò)的依賴(lài)及生命周期問(wèn)題,可以使我們的代碼更加簡(jiǎn)潔磅摹、健壯滋迈。

在不使用EventBus的情況下,我們也可能會(huì)使用諸如 Observable/Observer 這樣得一些機(jī)制來(lái)處理事件的監(jiān)聽(tīng)/發(fā)布户誓。如果在我們的應(yīng)用程序中饼灿,有許多地方需要使用事件的監(jiān)聽(tīng)/發(fā)布,則我們應(yīng)用程序的整個(gè)結(jié)構(gòu)可能就會(huì)像下面這個(gè)樣子:


每一處需要用到事件的監(jiān)聽(tīng)/發(fā)布的地方帝美,都需要實(shí)現(xiàn)一整套監(jiān)聽(tīng)/發(fā)布的機(jī)制碍彭,比如定義Listener接口,定義Notification Center/Observable悼潭,定義事件類(lèi)庇忌,定義注冊(cè)監(jiān)聽(tīng)者的方法、移除監(jiān)聽(tīng)者的方法舰褪、發(fā)布事件的方法等皆疹。我們不得不寫(xiě)許多繁瑣的,甚至常常是重復(fù)的冗余的代碼來(lái)實(shí)現(xiàn)我們的設(shè)計(jì)目的占拍。

引入EventBus庫(kù)之后略就,事件的監(jiān)聽(tīng)/發(fā)布將變得非常簡(jiǎn)單捎迫,我們應(yīng)用程序的結(jié)構(gòu)也將更加簡(jiǎn)潔,會(huì)如下面這樣:


我們可以將事件監(jiān)聽(tīng)者的管理残制,注冊(cè)監(jiān)聽(tīng)者立砸、移除監(jiān)聽(tīng)者,事件發(fā)布等方法等都交給EventBus來(lái)完成初茶,而只定義事件類(lèi)颗祝,實(shí)現(xiàn)事件處理方法即可。

在使用Observable/Observer來(lái)實(shí)現(xiàn)事件的監(jiān)聽(tīng)/發(fā)布時(shí)恼布,監(jiān)聽(tīng)者和事件發(fā)布者之間的關(guān)聯(lián)關(guān)系非常明晰螺戳,事件發(fā)布者找到事件的監(jiān)聽(tīng)者從不成為問(wèn)題。引入EventBus之后折汞,它會(huì)統(tǒng)一管理所有對(duì)不同類(lèi)型事件感興趣的監(jiān)聽(tīng)者倔幼,則事件發(fā)布者在發(fā)布事件時(shí),能夠準(zhǔn)確高效地找到對(duì)事件感興趣的監(jiān)聽(tīng)者將成為重要的問(wèn)題爽待。后面我們就通過(guò)對(duì)EventBus代碼實(shí)現(xiàn)的分析损同,來(lái)獲取這樣一些重要問(wèn)題的答案,同時(shí)來(lái)欣賞EventBus得設(shè)計(jì)鸟款。

EventBus的基本使用

在具體分析EventBus的代碼之前膏燃,我們先來(lái)了解一下EventBus的用法。想要使用EventBus何什,首先需要在我們應(yīng)用程序的Gradle腳本中添加對(duì)于EventBus的依賴(lài)组哩,以當(dāng)前最新版3.0.0為例:

compile 'org.greenrobot:eventbus:3.0.0'

然后,我們需要定義事件处渣。事件是POJO(plain old Java object)伶贰,并沒(méi)有特殊的要求:

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

其次,準(zhǔn)備訂閱者罐栈,訂閱者實(shí)現(xiàn)事件處理方法(也稱(chēng)為“訂閱者方法”)黍衙。當(dāng)事件被發(fā)布時(shí),這些方法會(huì)被調(diào)用悠瞬。在EventBus 3中们豌,它們通過(guò) @Subscribe annotation進(jìn)行聲明,方法名可以自由地進(jìn)行選擇浅妆。而在EventBus 2中,則是通過(guò)命名模式來(lái)聲明監(jiān)聽(tīng)者方法的障癌。

// This method will be called when a MessageEvent is posted (in the UI thread for Toast)
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

// This method will be called when a SomeOtherEvent is posted
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
    doSomethingWith(event);
}

訂閱者需要向EventBus注冊(cè)和注銷(xiāo)它自己凌外。只有當(dāng)訂閱者注冊(cè)了,它們才能收到事件涛浙。在Android中康辑,Activities和Fragments通常根據(jù)它們的生命周期來(lái)進(jìn)行綁定:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
   EventBus.getDefault().unregister(this);
    super.onStop();
}

最后即是事件源發(fā)布事件摄欲。我們可以在代碼的任何位置發(fā)布事件,當(dāng)前所有事件類(lèi)型與發(fā)布的事件類(lèi)型匹配的訂閱者都將收到它疮薇。

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

EventBus 更多特性說(shuō)明

了解了EventBus的基本用法之后胸墙,我們?cè)賮?lái)了解一下它提供的一些高級(jí)特性,以方便我們后續(xù)理解它的設(shè)計(jì)決策按咒。

Sticky事件

某些事件攜帶的信息在事件被發(fā)布之后依然有價(jià)值迟隅。比如,一個(gè)指示初始化過(guò)程完成的事件信號(hào)励七≈窍或者如果你有傳感器或位置數(shù)據(jù),你想要獲取它們的最新值的情況掠抬。不是實(shí)現(xiàn)自己的緩存吼野,而是使用sticky事件。EventBus將在內(nèi)存中保存最新的某一類(lèi)型的sticky事件两波。Sticky事件可以被發(fā)送給訂閱者瞳步,也可以顯式地查詢(xún)。從而使你可以不需要特別的邏輯來(lái)考慮已經(jīng)可用的數(shù)據(jù)腰奋。這與Android中的sticky broadcast有些類(lèi)似单起。

比如,一個(gè)sticky事件在一段時(shí)間之前被拋出:

EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));

現(xiàn)在氛堕,sticky事件被拋出后的某個(gè)時(shí)刻馏臭,一個(gè)新的Activity啟動(dòng)了。在注冊(cè)階段所有的sticky訂閱者方法將立即獲得之前拋出的sticky事件:

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent event) {
        // UI updates must run on MainThread
        textField.setText(event.message);
    }

    @Override
    public void onStop() {
        EventBus.getDefault().unregister(this);
        super.onStop();
    }

正如前面看到的讼稚,最新的sticky事件會(huì)在注冊(cè)時(shí)立即自動(dòng)地被傳遞給匹配的訂閱者括儒。但有時(shí)手動(dòng)地去檢查sticky事件可能會(huì)更方便一些。有時(shí)也可能需要移除(消費(fèi))sticky事件以便于它不會(huì)再被傳遞锐想。比如:

MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
    // "Consume" the sticky event
    EventBus.getDefault().removeStickyEvent(stickyEvent);
    // Now do something with it
}

removeStickyEvent方法是重載的:當(dāng)你傳遞class時(shí)帮寻,它將返回持有的之前的sticky事件。使用如下的變體赠摇,我們可以改進(jìn)前面的例子:

MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
    // Now do something with it
}

優(yōu)先級(jí)及事件取消

盡管使用EventBus的大多數(shù)場(chǎng)景不需要優(yōu)先級(jí)或事件的取消固逗,但在某些特殊的場(chǎng)景下它們還是很方便的。比如藕帜,在app處于前臺(tái)時(shí)一個(gè)事件可能觸發(fā)某些UI邏輯烫罩,而如果app當(dāng)前對(duì)用戶(hù)不可見(jiàn)則需要有不同的響應(yīng)。

我們可以定制訂閱者的優(yōu)先級(jí)洽故”丛埽可以通過(guò)在注冊(cè)期間給訂閱者提供一個(gè)優(yōu)先級(jí)來(lái)改變事件傳遞的順序。

@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
…
}

在相同的傳遞線程(ThreadMode)內(nèi)时甚,高優(yōu)先級(jí)的訂閱者將先于低優(yōu)先級(jí)的訂閱者收到事件隘弊。默認(rèn)的優(yōu)先級(jí)是0哈踱。優(yōu)先級(jí)不影響處于不同ThreadModes中的訂閱者的事件傳遞順序。

我們還可以取消事件的傳遞梨熙。我們可以在一個(gè)訂閱者的事件處理方法中通過(guò)調(diào)用cancelEventDelivery(Object event)來(lái)取消事件的傳遞進(jìn)程开镣。所有后續(xù)的事件傳遞將被取消:后面的訂閱者將無(wú)法接收到事件。

// Called in the same thread (default)
@Subscribe
public void onEvent(MessageEvent event){
// Process the event
…

EventBus.getDefault().cancelEventDelivery(event) ;
}

事件通常由高優(yōu)先級(jí)的訂閱者取消咽扇。取消被限制只能在發(fā)布線程ThreadMode.PostThread的事件處理方法中進(jìn)行邪财。

傳遞線程

EventBus可以幫忙處理線程:事件可以在不同于拋出事件的線程中傳遞。一個(gè)常見(jiàn)的使用場(chǎng)景是處理UI變化肌割。在Android中卧蜓,UI變化必須在UI(主)線程中完成。另一方面把敞,網(wǎng)絡(luò)弥奸,或任何耗時(shí)任務(wù),必須不運(yùn)行在主線程奋早。EventBus幫忙處理了那些任務(wù)并與UI線程同步(不需要深入了解線程事務(wù)盛霎,使用AsyncTask即可,等等)耽装。

ThreadMode: POSTING

訂閱者將在與拋出事件相同的線程中被調(diào)用愤炸。這是默認(rèn)的方式。事件傳遞是同步完成的掉奄,事件傳遞完成時(shí)规个,所有的訂閱者將已經(jīng)被調(diào)用一次了。這個(gè)ThreadMode意味著最小的開(kāi)銷(xiāo)姓建,因?yàn)樗耆苊饬司€程的切換诞仓。對(duì)于已知耗時(shí)非常短的簡(jiǎn)單的不需要請(qǐng)求主線程的任務(wù),這是建議采用的模式速兔。使用這個(gè)模式的事件處理器應(yīng)該迅速返回以避免阻塞發(fā)布事件的線程墅拭,而后者可能是主線程。比如:

// Called in the same thread (default)

@Subscribe(threadMode = ThreadMode.POSTING) // ThreadMode is optional here
public void onMessage(MessageEvent event) {
    log(event.message);
}

ThreadMode: MAIN

訂閱者將在Android的主線程中被調(diào)用(有時(shí)被稱(chēng)為UI線程)涣狗。如果發(fā)布事件的線程是主線程谍婉,事件處理器方法將會(huì)直接被調(diào)用(如同ThreadMode.POSTING中描述的那樣)。事件處理器使用這個(gè)模式必須快速地返回以避免阻塞主線程镀钓。比如:

// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
    textField.setText(event.message);
}

ThreadMode: BACKGROUND

訂閱者將在一個(gè)后臺(tái)線程中被調(diào)用穗熬。如果發(fā)布事件的線程不是主線程,事件處理器方法將直接在發(fā)布的線程中被調(diào)用丁溅。如果發(fā)布線程是主線程死陆,EventBus使用一個(gè)單獨(dú)的后臺(tái)線程,它將順序地傳遞它所有的事件唧瘾。使用這個(gè)模式的事件處理器應(yīng)該嘗試快速返回以避免阻塞后臺(tái)線程措译。

// Called in the background thread
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
    saveToDisk(event.message);
}

ThreadMode: ASYNC

事件處理器方法在另外一個(gè)線程中被調(diào)用。這總是獨(dú)立于發(fā)布事件的線程和主線程饰序。發(fā)布線程從不等待使用這一模式的事件處理器领虹。對(duì)于執(zhí)行比較耗時(shí)的事件處理器方法應(yīng)該使用這個(gè)模式,比如對(duì)于網(wǎng)絡(luò)訪問(wèn)求豫。避免同時(shí)大量地觸發(fā)長(zhǎng)時(shí)間運(yùn)行的異步處理器方法而限制并發(fā)線程的數(shù)量塌衰。EventBus使用了一個(gè)線程池以有效地復(fù)用已完成異步事件處理器通知的線程。

// Called in a separate thread
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
    backend.send(event.message);
}

EventBus對(duì)象的創(chuàng)建

大體了解了EventBus提供的特性之后蝠嘉,我們來(lái)分析EventBus的設(shè)計(jì)與實(shí)現(xiàn)最疆。我們從EventBus對(duì)象的創(chuàng)建開(kāi)始。EventBus對(duì)象需要用EventBusBuilder來(lái)創(chuàng)建蚤告。以EventBus提供的default EventBus對(duì)象為例努酸,我們來(lái)看一下創(chuàng)建的過(guò)程。

應(yīng)用程序可以通過(guò)EventBus.getDefault()來(lái)獲取默認(rèn)的EventBus對(duì)象:

    /** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

雙重加鎖檢查手法來(lái)保證對(duì)EventBus.getDefault()調(diào)用的線程安全杜恰。來(lái)看EventBus的構(gòu)造函數(shù):

    public static EventBusBuilder builder() {
        return new EventBusBuilder();
    }
......
    /**
     * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
     * central bus, consider {@link #getDefault()}.
     */
    public EventBus() {
        this(DEFAULT_BUILDER);
    }

    EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();

        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
        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類(lèi)提供了一個(gè)public的構(gòu)造函數(shù)获诈,可以以默認(rèn)的配置來(lái)構(gòu)造EventBus對(duì)象。EventBusBuilder是繁雜的EventBus構(gòu)造時(shí)所需配置信息的容器心褐。至于利用EventBusBuilder進(jìn)行配置的配置項(xiàng)具體的含義舔涎,暫時(shí)先不詳述。

package org.greenrobot.eventbus;

import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Creates EventBus instances with custom parameters and also allows to install a custom default EventBus instance.
 * Create a new builder using {@link EventBus#builder()}.
 */
public class EventBusBuilder {
    private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

    boolean logSubscriberExceptions = true;
    boolean logNoSubscriberMessages = true;
    boolean sendSubscriberExceptionEvent = true;
    boolean sendNoSubscriberEvent = true;
    boolean throwSubscriberException;
    boolean eventInheritance = true;
    boolean ignoreGeneratedIndex;
    boolean strictMethodVerification;
    ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
    List<Class<?>> skipMethodVerificationForClasses;
    List<SubscriberInfoIndex> subscriberInfoIndexes;

    EventBusBuilder() {
    }

    /** Default: true */
    public EventBusBuilder logSubscriberExceptions(boolean logSubscriberExceptions) {
        this.logSubscriberExceptions = logSubscriberExceptions;
        return this;
    }

    /** Default: true */
    public EventBusBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages) {
        this.logNoSubscriberMessages = logNoSubscriberMessages;
        return this;
    }

    /** Default: true */
    public EventBusBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent) {
        this.sendSubscriberExceptionEvent = sendSubscriberExceptionEvent;
        return this;
    }

    /** Default: true */
    public EventBusBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent) {
        this.sendNoSubscriberEvent = sendNoSubscriberEvent;
        return this;
    }

    /**
     * Fails if an subscriber throws an exception (default: false).
     * <p/>
     * Tip: Use this with BuildConfig.DEBUG to let the app crash in DEBUG mode (only). This way, you won't miss
     * exceptions during development.
     */
    public EventBusBuilder throwSubscriberException(boolean throwSubscriberException) {
        this.throwSubscriberException = throwSubscriberException;
        return this;
    }

    /**
     * By default, EventBus considers the event class hierarchy (subscribers to super classes will be notified).
     * Switching this feature off will improve posting of events. For simple event classes extending Object directly,
     * we measured a speed up of 20% for event posting. For more complex event hierarchies, the speed up should be
     * >20%.
     * <p/>
     * However, keep in mind that event posting usually consumes just a small proportion of CPU time inside an app,
     * unless it is posting at high rates, e.g. hundreds/thousands of events per second.
     */
    public EventBusBuilder eventInheritance(boolean eventInheritance) {
        this.eventInheritance = eventInheritance;
        return this;
    }


    /**
     * Provide a custom thread pool to EventBus used for async and background event delivery. This is an advanced
     * setting to that can break things: ensure the given ExecutorService won't get stuck to avoid undefined behavior.
     */
    public EventBusBuilder executorService(ExecutorService executorService) {
        this.executorService = executorService;
        return this;
    }

    /**
     * Method name verification is done for methods starting with onEvent to avoid typos; using this method you can
     * exclude subscriber classes from this check. Also disables checks for method modifiers (public, not static nor
     * abstract).
     */
    public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) {
        if (skipMethodVerificationForClasses == null) {
            skipMethodVerificationForClasses = new ArrayList<>();
        }
        skipMethodVerificationForClasses.add(clazz);
        return this;
    }

    /** Forces the use of reflection even if there's a generated index (default: false). */
    public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
        this.ignoreGeneratedIndex = ignoreGeneratedIndex;
        return this;
    }

    /** Enables strict method verification (default: false). */
    public EventBusBuilder strictMethodVerification(boolean strictMethodVerification) {
        this.strictMethodVerification = strictMethodVerification;
        return this;
    }

    /** Adds an index generated by EventBus' annotation preprocessor. */
    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if(subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

    /**
     * Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders' values. Must be
     * done only once before the first usage of the default EventBus.
     *
     * @throws EventBusException if there's already a default EventBus instance in place
     */
    public EventBus installDefaultEventBus() {
        synchronized (EventBus.class) {
            if (EventBus.defaultInstance != null) {
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }

    /** Builds an EventBus based on the current configuration. */
    public EventBus build() {
        return new EventBus(this);
    }

}

這是一個(gè)蠻常規(guī)的Builder逗爹。我們看到了自定義可配置項(xiàng)創(chuàng)建EventBus對(duì)象的方法亡嫌,也就是通過(guò)EventBusBuilder.build()方法。

EventBusBuilder還提供了一個(gè)方法installDefaultEventBus()可以讓我們?cè)趧?chuàng)建自定義EventBus對(duì)象時(shí)掘而,將該對(duì)象設(shè)置為default EventBus對(duì)象挟冠。

總結(jié)一下EventBus對(duì)象創(chuàng)建的方式:

  1. 通過(guò)EventBus的public不帶參數(shù)構(gòu)造函數(shù),創(chuàng)建默認(rèn)配置的EventBus對(duì)象镣屹。EventBus庫(kù)提供的default EventBus對(duì)象的創(chuàng)建方式圃郊。
  2. 通過(guò)EventBusBuilder創(chuàng)建自定義配置的EventBus對(duì)象。這種方式的對(duì)象可以通過(guò)EventBusBuilder.installDefaultEventBus()在對(duì)象被創(chuàng)建的同時(shí)設(shè)置為default EventBus對(duì)象女蜈。

由此可見(jiàn)持舆,引入EventBus庫(kù)之后,我們應(yīng)用程序的結(jié)構(gòu)實(shí)際上將如下面這樣:


EventBus所提供的特性就先介紹到這里伪窖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逸寓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子覆山,更是在濱河造成了極大的恐慌竹伸,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異勋篓,居然都是意外死亡吧享,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)譬嚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)钢颂,“玉大人,你說(shuō)我怎么就攤上這事拜银∈獗蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵尼桶,是天一觀的道長(zhǎng)操灿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)泵督,這世上最難降的妖魔是什么趾盐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任男杈,我火速辦了婚禮越妈,結(jié)果婚禮上驰徊,老公的妹妹穿的比我還像新娘穆桂。我一直安慰自己革砸,他們只是感情好吨岭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布休溶。 她就那樣靜靜地躺著府阀,像睡著了一般漩仙。 火紅的嫁衣襯著肌膚如雪搓茬。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天队他,我揣著相機(jī)與錄音卷仑,去河邊找鬼。 笑死麸折,一個(gè)胖子當(dāng)著我的面吹牛锡凝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播垢啼,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼窜锯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了芭析?” 一聲冷哼從身側(cè)響起锚扎,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馁启,沒(méi)想到半個(gè)月后驾孔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年翠勉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妖啥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡眉菱,死狀恐怖迹栓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俭缓,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布酥郭,位于F島的核電站华坦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏不从。R本人自食惡果不足惜惜姐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望椿息。 院中可真熱鬧歹袁,春花似錦、人聲如沸寝优。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乏矾。三九已至孟抗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钻心,已是汗流浹背凄硼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捷沸,地道東北人摊沉。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像痒给,于是被迫代替她去往敵國(guó)和親说墨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • EventBus源碼分析(一) EventBus官方介紹為一個(gè)為Android系統(tǒng)優(yōu)化的事件訂閱總線侈玄,它不僅可以很...
    蕉下孤客閱讀 4,007評(píng)論 4 42
  • 我每周會(huì)寫(xiě)一篇源代碼分析的文章,以后也可能會(huì)有其他主題.如果你喜歡我寫(xiě)的文章的話,歡迎關(guān)注我的新浪微博@達(dá)達(dá)達(dá)達(dá)s...
    SkyKai閱讀 24,927評(píng)論 23 184
  • 對(duì)于Android開(kāi)發(fā)老司機(jī)來(lái)說(shuō)肯定不會(huì)陌生婉刀,它是一個(gè)基于觀察者模式的事件發(fā)布/訂閱框架,開(kāi)發(fā)者可以通過(guò)極少的代碼...
    飛揚(yáng)小米閱讀 1,475評(píng)論 0 50
  • 前面在 EventBus設(shè)計(jì)與實(shí)現(xiàn)分析——特性介紹中介紹了EventBus的基本用法序仙,及其提供的大多數(shù)特性的用法突颊;...
    hanpfei閱讀 1,002評(píng)論 4 12
  • EventBus 是一個(gè)Android端優(yōu)化的 publish/subscribe 消息總線,簡(jiǎn)化了應(yīng)用程序各個(gè)組...
    王世軍Steven閱讀 1,852評(píng)論 4 21