EventBus 3.0

對(duì)于Android開(kāi)發(fā)老司機(jī)來(lái)說(shuō)肯定不會(huì)陌生,它是一個(gè)基于觀察者模式的事件發(fā)布/訂閱框架,開(kāi)發(fā)者可以通過(guò)極少的代碼去實(shí)現(xiàn)多個(gè)模塊之間的通信胚想,而不需要以層層傳遞接口的形式去單獨(dú)構(gòu)建通信橋梁。從而降低因多重回調(diào)導(dǎo)致的模塊間強(qiáng)耦合,同時(shí)避免產(chǎn)生大量?jī)?nèi)部類暇屋。它擁有使用方便,性能高洞辣,接入成本低和支持多線程的優(yōu)點(diǎn)撼泛,實(shí)乃模塊解耦、代碼重構(gòu)必備良藥爸吮。

image.png

作為Markus Junginger大神耗時(shí)4年打磨握玛、超過(guò)1億接入量、Github 9000+ star的明星級(jí)組件畜挥,分析EventBus的文章早已是數(shù)不勝數(shù)仔粥。在EventBus 3中引入了EventBusAnnotationProcessor(注解分析生成索引)技術(shù),大大提高了EventBus的運(yùn)行效率蟹但。而分析這個(gè)加速器的資料在網(wǎng)上很少躯泰,因此本文會(huì)把重點(diǎn)放在分析這個(gè)EventBus 3的新特性上,同時(shí)分享一些踩坑經(jīng)驗(yàn)华糖,并結(jié)合源碼分析及UML圖麦向,以直觀的形式和大家一起學(xué)習(xí)EventBus 3的用法及運(yùn)行原理。

使用EventBus

image.png

1.導(dǎo)入組件

// 打開(kāi)App的build.gradle客叉,在dependencies中添加最新的EventBus依賴:
compile 'org.greenrobot:eventbus:3.0.0'

如果不需要索引加速的話诵竭,就可以直接跳到第二步了。而要應(yīng)用最新的EventBusAnnotationProcessor則比較麻煩兼搏,因?yàn)樽⒔饨馕鲆蕾囉赼ndroid-apt-plugin卵慰。我們一步一步來(lái),首先在項(xiàng)目gradle的dependencies中引入apt編譯插件:

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

然后在App的build.gradle中應(yīng)用apt插件佛呻,并設(shè)置apt生成的索引的包名和類名:

apply plugin: 'com.neenbedankt.android-apt'
apt {
    arguments {
        eventBusIndex "org.greenrobot.eventbusperf.MyEventBusIndex"
    }
}

接著在App的dependencies中引入EventBusAnnotationProcessor:

apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
//或者使用源碼
apt project(':EventBusAnnotationProcessor')

此時(shí)需要我們先編譯一次裳朋,生成索引類。編譯成功之后吓著,就會(huì)發(fā)現(xiàn)在\ProjectName\app\build\generated\source\apt\PakageName\下看到通過(guò)注解分析生成的索引類鲤嫡,這樣我們便可以在初始化EventBus時(shí)應(yīng)用我們生成的索引了送挑。

2.初始化EventBus
EventBus默認(rèn)有一個(gè)單例,可以通過(guò)getDefault()獲取暖眼,也可以通過(guò)EventBus.builder()構(gòu)造自定義的EventBus惕耕,比如要應(yīng)用我們生成好的索引時(shí):

EventBus mEventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

如果想把自定義的設(shè)置應(yīng)用到EventBus默認(rèn)的單例中,則可以用installDefaultEventBus()方法:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

3.定義事件
所有能被實(shí)例化為Object的實(shí)例都可以作為事件罢荡。
在最新版的eventbus 3中如果用到了索引加速赡突,事件類的修飾符必須為public,不然編譯時(shí)會(huì)報(bào)錯(cuò):Subscriber method must be public区赵。

4.監(jiān)聽(tīng)事件
首先把作為訂閱事件的模塊通過(guò)EventBus注冊(cè)監(jiān)聽(tīng):

mEventBus.register(this);

在3.0之前惭缰,注冊(cè)監(jiān)聽(tīng)需要區(qū)分是否監(jiān)聽(tīng)黏性(sticky)事件,監(jiān)聽(tīng)EventBus事件的模塊需要實(shí)現(xiàn)以onEvent開(kāi)頭的方法笼才。如今改為在方法上添加注解的形式:

@Subscribe(threadMode = ThreadMode.POSTING, priority = 0, sticky = true)
public void handleEvent(DriverEvent event) {
    Log.d(TAG, event.info);
}

注解有三個(gè)參數(shù)漱受,threadMode為回調(diào)所在的線程,priority為優(yōu)先級(jí)骡送,sticky為是否接收黏性事件昂羡。調(diào)度單位從類細(xì)化到了方法,對(duì)方法的命名也沒(méi)有了要求摔踱,方便混淆代碼虐先。但注冊(cè)了監(jiān)聽(tīng)的模塊必須有一個(gè)標(biāo)注了Subscribe注解方法,不然在register時(shí)會(huì)拋出異常:
Subscriber class XXX and its super classes have no public methods with the @Subscribe annotation

5.發(fā)送事件
調(diào)用post或者postSticky即可:

mEventBus.post(new DriverEvent("magnet:?xt=urn:btih……"));

在實(shí)際項(xiàng)目的使用中派敷,register和unregister通常與Activity和Fragment的生命周期相關(guān)蛹批,ThreadMode.MainThread可以很好地解決Android的界面刷新必須在UI線程的問(wèn)題,不需要再回調(diào)后用Handler中轉(zhuǎn)(EventBus中已經(jīng)自動(dòng)用Handler做了處理)篮愉,黏性事件可以很好地解決post與register同時(shí)執(zhí)行時(shí)的異步問(wèn)題(這個(gè)在原理中會(huì)說(shuō)到)腐芍,事件的傳遞也沒(méi)有序列化與反序列化的性能消耗,足以滿足我們大部分情況下的模塊間通信需求试躏。

EventBus原理分析

image.png

1.訂閱注冊(cè)(register)

簡(jiǎn)單來(lái)說(shuō)就是:根據(jù)訂閱者的類來(lái)找回調(diào)方法猪勇,把訂閱者和回調(diào)方法封裝成關(guān)系,并保存到相應(yīng)的數(shù)據(jù)結(jié)構(gòu)中颠蕴,為隨后的事件分發(fā)做好準(zhǔn)備泣刹,最后處理黏性事件:

//3.0版本的注冊(cè)
EventBus.getDefault().register(this);
       
//2.x版本的注冊(cè)
EventBus.getDefault().register(this);
EventBus.getDefault().register(this, 100);
EventBus.getDefault().registerSticky(this, 100);
EventBus.getDefault().registerSticky(this);

可以看到2.x版本中有四種注冊(cè)方法,區(qū)分了普通注冊(cè)和粘性事件注冊(cè),并且在注冊(cè)時(shí)可以選擇接收事件的優(yōu)先級(jí),這里我們就不對(duì)2.x版本做過(guò)多的研究了,如果想研究可以參照此篇文章.由于3.0版本將粘性事件以及訂閱事件的優(yōu)先級(jí)換了一種更好的實(shí)現(xiàn)方式,所以3.0版本中的注冊(cè)就變得簡(jiǎn)單,只有一個(gè)register()
方法即可.

public void register(Object subscriber) {
    //首先獲得訂閱者的class對(duì)象
    Class<?> subscriberClass = subscriber.getClass();
    //通過(guò)subscriberMethodFinder來(lái)找到訂閱者訂閱了哪些事件.返回一個(gè)SubscriberMethod對(duì)象的List,SubscriberMethod
    //里包含了這個(gè)方法的Method對(duì)象,以及將來(lái)響應(yīng)訂閱是在哪個(gè)線程的ThreadMode,以及訂閱的事件類型eventType,以及訂閱的優(yōu)
    //先級(jí)priority,以及是否接收粘性sticky事件的boolean值.
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //訂閱
            subscribe(subscriber, subscriberMethod);
        }
    }
}

可以看到register()``方法很簡(jiǎn)潔,代碼里的注釋也很清楚了,我們可以看出通過(guò)subscriberMethodFinder.findSubscriberMethods(subscriberClass)方法就能返回一個(gè)SubscriberMethod的對(duì)象,而SubscriberMethod里包含了所有我們需要的接下來(lái)執(zhí)行subscribe()的信息.所以我們先去看看findSubscriberMethods()`是怎么實(shí)現(xiàn)的,然后我們?cè)偃リP(guān)注subscribe()。

SubscriberMethodFinder的實(shí)現(xiàn)

一句話來(lái)描述SubscriberMethodFinder類就是用來(lái)查找和緩存訂閱者響應(yīng)函數(shù)的信息的類犀被。所以我們首先要知道怎么能獲得訂閱者響應(yīng)函數(shù)的相關(guān)信息项玛。在3.0版本中,EventBus提供了一個(gè)EventBusAnnotationProcessor注解處理器來(lái)在編譯期通過(guò)讀取@Subscribe()注解并解析,處理其中所包含的信息,然后生成java類來(lái)保存所有訂閱者關(guān)于訂閱的信息,這樣就比在運(yùn)行時(shí)使用反射來(lái)獲得這些訂閱者的信息速度要快.我們可以參考EventBus項(xiàng)目里的EventBusPerformance這個(gè)例子,編譯后我們可以在build文件夾里找到這個(gè)類,MyEventBusIndex 類,當(dāng)然類名是可以自定義的.我們大致看一下生成的MyEventBusIndex
類是什么樣的:

/**
 * This class is generated by EventBus, do not edit.
 */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
                true, new SubscriberMethodInfo[]{
                new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
        }));

        putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[]{
                new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
        }));
    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

可以看出是使用一個(gè)靜態(tài)HashMap即:SUBSCRIBER_INDEX來(lái)保存訂閱類的信息,其中包括了訂閱類的class對(duì)象,是否需要檢查父類,以及訂閱方法的信息SubscriberMethodInfo的數(shù)組,SubscriberMethodInfo中又保存了:訂閱方法的方法名,訂閱的事件類型,觸發(fā)線程,是否接收sticky事件以及優(yōu)先級(jí)priority.這其中就保存了register()的所有需要的信息,如果再配置EventBus的時(shí)候通過(guò)EventBusBuilder配置:eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();來(lái)將編譯生成的MyEventBusIndex配置進(jìn)去,這樣就能在SubscriberMethodFinder類中直接查找出訂閱類的信息,就不需要再利用注解判斷了(也就是我們說(shuō)的渦輪引擎),當(dāng)然這種方法是作為EventBus的可選配置,SubscriberMethodFinder同樣提供了通過(guò)注解來(lái)獲得訂閱類信息的方法,下面我們就來(lái)看findSubscriberMethods()到底是如何實(shí)現(xiàn)的:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //先從METHOD_CACHE取看是否有緩存,key:保存訂閱類的類名,value:保存類中訂閱的方法數(shù)據(jù),
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    //是否忽略注解器生成的MyEventBusIndex類
    if (ignoreGeneratedIndex) {
        //利用反射來(lái)讀取訂閱類中的訂閱方法信息
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        //從注解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法信息
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        //保存進(jìn)METHOD_CACHE緩存
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

注釋很詳細(xì)我們就不在多說(shuō),由于篇幅原因我們就不在分析findUsingInfo()方法,其無(wú)非就是通過(guò)查找我們上面所說(shuō)的MyEventBusIndex類中的信息,來(lái)轉(zhuǎn)換成List<SubscriberMethod>從而獲得訂閱類的相關(guān)訂閱函數(shù)的各種信息.有興趣的可以自己研究看看,下面我們就來(lái)看findUsingReflection()方法是如何實(shí)現(xiàn)的:

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    //FindState 用來(lái)做訂閱方法的校驗(yàn)和保存
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        //通過(guò)反射來(lái)獲得訂閱方法信息
        findUsingReflectionInSingleClass(findState);
        //查找父類的訂閱方法
        findState.moveToSuperclass();
    }
    //獲取findState中的SubscriberMethod(也就是訂閱方法List)并返回
    return getMethodsAndRelease(findState);
}

這里通過(guò)FindState類來(lái)做訂閱方法的校驗(yàn)和保存,并通過(guò)FIND_STATE_POOL靜態(tài)數(shù)組來(lái)保存FindState對(duì)象,可以使FindState復(fù)用,避免重復(fù)創(chuàng)建過(guò)多的對(duì)象.最終是通過(guò)findUsingReflectionInSingleClass()來(lái)具體獲得相關(guān)訂閱方法的信息的:

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    //通過(guò)反射得到方法數(shù)組
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    //遍歷Method
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            //保證必須只有一個(gè)事件參數(shù)
            if (parameterTypes.length == 1) {
                //得到注解
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    //校驗(yàn)是否添加該方法
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        //實(shí)例化SubscriberMethod對(duì)象并添加
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException("@Subscribe method " + methodName +
                        "must have exactly 1 parameter but has " + parameterTypes.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

這里走完,我們訂閱類的所有SubscriberMethod都已經(jīng)被保存了,最后再通過(guò)getMethodsAndRelease()返回List<SubscriberMethod>至此,所有關(guān)于如何獲得訂閱類的訂閱方法信息即:SubscriberMethod對(duì)象就已經(jīng)完全分析完了,下面我們來(lái)看subscribe()是如何實(shí)現(xiàn)的.

subscribe()方法的實(shí)現(xiàn)

這里我們回到subscribe(subscriber, subscriberMethod);中去,通過(guò)這個(gè)方法,我們就完成了注冊(cè),下面看一下subscribe()的實(shí)現(xiàn):

//必須在同步代碼塊里調(diào)用
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //獲取訂閱的事件類型
    Class<?> eventType = subscriberMethod.eventType;
    //創(chuàng)建Subscription對(duì)象
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //從subscriptionsByEventType里檢查是否已經(jīng)添加過(guò)該Subscription,如果添加過(guò)就拋出異常
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        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)先級(jí)priority來(lái)添加Subscription對(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;
        }
    }
    //將訂閱者對(duì)象以及訂閱的事件保存到typesBySubscriber里.
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);
    //如果接收sticky事件,立即分發(fā)sticky事件
    if (subscriberMethod.sticky) {
        //eventInheritance 表示是否分發(fā)訂閱了響應(yīng)事件類父類事件的方法
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            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);
        }
    }
}

以上就是所有注冊(cè)過(guò)程,現(xiàn)在再來(lái)看這張圖就會(huì)特別清晰EventBus的register()過(guò)程了:

image.png

2.事件分發(fā)(post)

我們知道可以通過(guò)EventBus.getDefault().post("str");來(lái)發(fā)送一個(gè)事件,所以我們就從這行代碼開(kāi)始分析,首先看看post()方法是如何實(shí)現(xiàn)的:

public void post(Object event) {
    //得到當(dāng)前線程的Posting狀態(tài).
    PostingThreadState postingState = currentPostingThreadState.get();
    //獲取當(dāng)前線程的事件隊(duì)列
    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 {
            //一直發(fā)送
            while (!eventQueue.isEmpty()) {
                //發(fā)送單個(gè)事件
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

首先是通過(guò)currentPostingThreadState.get()方法來(lái)得到當(dāng)前線程PostingThreadState的對(duì)象,為什么是說(shuō)當(dāng)前線程我們來(lái)看看currentPostingThreadState的實(shí)現(xiàn):

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

currentPostingThreadState的實(shí)現(xiàn)是一個(gè)包含了PostingThreadStateThreadLocal對(duì)象,關(guān)于ThreadLocal張濤的這篇文章解釋的很好:ThreadLocal 是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過(guò)它可以在指定的線程中存儲(chǔ)數(shù)據(jù)弱判,而這段數(shù)據(jù)是不會(huì)與其他線程共享的。其內(nèi)部原理是通過(guò)生成一個(gè)它包裹的泛型對(duì)象的數(shù)組锥惋,在不同的線程會(huì)有不同的數(shù)組索引值昌腰,通過(guò)這樣就可以做到每個(gè)線程通過(guò)get() 方法獲取的時(shí)候开伏,取到的只能是自己線程所對(duì)應(yīng)的數(shù)據(jù)。 所以這里取到的就是每個(gè)線程的PostingThreadState狀態(tài).接下來(lái)我們來(lái)看postSingleEvent()方法:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    //是否觸發(fā)訂閱了該事件(eventClass)的父類,以及接口的類的響應(yīng)方法.
    if (eventInheritance) {
        //查找eventClass類所有的父類以及接口
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        //循環(huán)postSingleEventForEventType
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            //只要右邊有一個(gè)為true,subscriptionFound就為true
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        //post單個(gè)
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    //如果沒(méi)發(fā)現(xiàn)
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            //發(fā)送一個(gè)NoSubscriberEvent事件,如果我們需要處理這種狀態(tài),接收這個(gè)事件就可以了
            post(new NoSubscriberEvent(this, event));
        }
    }
}

跟著上面的代碼的注釋,我們可以很清楚的發(fā)現(xiàn)是在postSingleEventForEventType()方法里去進(jìn)行事件的分發(fā),代碼如下:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    //獲取訂閱了這個(gè)事件的Subscription列表.
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            //是否被中斷
            boolean aborted = false;
            try {
                //分發(fā)給訂閱者
                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;
}

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 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);
    }
}

總結(jié)上面的代碼就是,首先從subscriptionsByEventType里獲得所有訂閱了這個(gè)事件的Subscription列表,然后在通過(guò)postToSubscription()方法來(lái)分發(fā)事件,在postToSubscription()通過(guò)不同的threadMode在不同的線程里invoke()訂閱者的方法,ThreadMode共有四類:

  1. PostThread
    默認(rèn)的 ThreadMode遭商,表示在執(zhí)行 Post 操作的線程直接調(diào)用訂閱者的事件響應(yīng)方法固灵,不論該線程是否為主線程(UI 線程)。當(dāng)該線程為主線程時(shí)劫流,響應(yīng)方法中不能有耗時(shí)操作巫玻,否則有卡主線程的風(fēng)險(xiǎn)。適用場(chǎng)景:對(duì)于是否在主線程執(zhí)行無(wú)要求祠汇,但若 Post 線程為主線程仍秤,不能耗時(shí)的操作
  2. MainThread
    在主線程中執(zhí)行響應(yīng)方法可很。如果發(fā)布線程就是主線程诗力,則直接調(diào)用訂閱者的事件響應(yīng)方法,否則通過(guò)主線程的 Handler 發(fā)送消息在主線程中處理——調(diào)用訂閱者的事件響應(yīng)函數(shù)我抠。顯然苇本,MainThread類的方法也不能有耗時(shí)操作,以避免卡主線程菜拓。適用場(chǎng)景:必須在主線程執(zhí)行的操作瓣窄;
  3. BackgroundThread
    在后臺(tái)線程中執(zhí)行響應(yīng)方法。如果發(fā)布線程不是主線程纳鼎,則直接調(diào)用訂閱者的事件響應(yīng)函數(shù)俺夕,否則啟動(dòng)唯一的后臺(tái)線程去處理。由于后臺(tái)線程是唯一的喷橙,當(dāng)事件超過(guò)一個(gè)的時(shí)候啥么,它們會(huì)被放在隊(duì)列中依次執(zhí)行,因此該類響應(yīng)方法雖然沒(méi)有PostThread類和MainThread類方法對(duì)性能敏感贰逾,但最好不要有重度耗時(shí)的操作或太頻繁的輕度耗時(shí)操作悬荣,以造成其他操作等待。適用場(chǎng)景:操作輕微耗時(shí)且不會(huì)過(guò)于頻繁疙剑,即一般的耗時(shí)操作都可以放在這里氯迂;
  4. Async
    不論發(fā)布線程是否為主線程,都使用一個(gè)空閑線程來(lái)處理言缤。和BackgroundThread
    不同的是嚼蚀,Async類的所有線程是相互獨(dú)立的,因此不會(huì)出現(xiàn)卡線程的問(wèn)題管挟。適用場(chǎng)景:長(zhǎng)耗時(shí)操作轿曙,例如網(wǎng)絡(luò)訪問(wèn)

這里我們只來(lái)看看invokeSubscriber(subscription, event);是如何實(shí)現(xiàn)的,關(guān)于不同線程的Poster的使用

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);
    }
}

實(shí)際上就是通過(guò)反射調(diào)用了訂閱者的訂閱函數(shù)并把event對(duì)象作為參數(shù)傳入.至此post()流程就結(jié)束了,整體流程圖如下:

image.png

3.取消訂閱(unregister)

看完了上面的分析,解除注冊(cè)就相對(duì)容易了,解除注冊(cè)只要調(diào)用unregister()方法即可,實(shí)現(xiàn)如下:

public synchronized void unregister(Object subscriber) {
    //通過(guò)typesBySubscriber來(lái)取出這個(gè)subscriber訂閱者訂閱的事件類型,
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        //分別解除每個(gè)訂閱了的事件類型
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        //從typesBySubscriber移除subscriber
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

然后接著看unsubscribeByEventType()方法的實(shí)現(xiàn):

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //subscriptionsByEventType里拿出這個(gè)事件類型的訂閱者列表.
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        //取消訂閱
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

最終分別從typesBySubscribersubscriptions里分別移除訂閱者以及相關(guān)信息即可.
觀察者模式觀察者模式是對(duì)象的行為模式,又叫發(fā)布-訂閱(Publish/Subscribe)模式导帝、模型-視圖(Model/View)模式守谓、源-監(jiān)聽(tīng)器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對(duì)多的依賴關(guān)系您单,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象斋荞。這個(gè)主題對(duì)象在狀態(tài)上發(fā)生變化時(shí),會(huì)通知所有觀察者對(duì)象虐秦,使它們能夠自動(dòng)更新自己平酿。EventBus并不是標(biāo)準(zhǔn)的觀察者模式的實(shí)現(xiàn),但是它的整體就是一個(gè)發(fā)布/訂閱框架,也擁有觀察者模式的優(yōu)點(diǎn),比如:發(fā)布者和訂閱者的解耦.

4.拓展-注解

注解(Annotations)是一種元數(shù)據(jù)的格式,為程序提供數(shù)據(jù)悦陋,但又不是程序的一部分蜈彼。注解(Annotations)在代碼上不直接影響代碼操作。這怎么理解是數(shù)據(jù)又不是程序的一部分叨恨?我們可以將注解看成一種標(biāo)注柳刮,類似于注釋// 或 /* */,不是程序的一部分但為程序提供注釋痒钝,注解可以看作是提供數(shù)據(jù)秉颗。下面我們來(lái)看一下注解的用途:

  • 提供編譯信息:注解可以在編譯的時(shí)候檢查錯(cuò)誤和提示警告(java語(yǔ)言規(guī)范性看來(lái)和注解不無(wú)關(guān)系)
  • 編譯和部署時(shí)處理:軟件工具可以通過(guò)注解生成代碼、xml文件等(注入看來(lái)也很黑科技)
  • 運(yùn)行時(shí)處理:一些注解可以在運(yùn)行時(shí)使用

注解基礎(chǔ)
注解是什么格式呢送矩?

@Entity

這就是注解蚕甥,看起來(lái)很簡(jiǎn)單,通過(guò)設(shè)置(@)符號(hào)標(biāo)識(shí)栋荸,在跟一個(gè)注解的名稱菇怀。下面來(lái)看一下我們經(jīng)常用的Override注解。

@Override
void mySuperMethod() { ... }

再來(lái)看一下帶有參數(shù)的注解:

@Author(
   name = "Benjamin Franklin",
   date = "3/27/2003"
)
class MyClass() { ... }

or

@SuppressWarnings(value = "unchecked")
void myMethod() { ... }

如果按照上述value = "unchecked"是不是參數(shù)只能限定一個(gè)晌块,如果多個(gè)怎么辦:

@SuppressWarnings("unchecked")
void myMethod() { ... }

其實(shí)還有一種能夠傳入多個(gè)值(Java SE 8 release支持)爱沟,:

@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }

注解可以用在什么地方呢?注解可以用應(yīng)用在:聲明一個(gè)類匆背、變量呼伸、方法和程序元素。

聲明注解類型

如何做一個(gè)我們自己的注解钝尸?這里來(lái)看一個(gè)常見(jiàn)類:

public class Generation3List extends Generation2List {

   // Author: John Doe
   // Date: 3/17/2002
   // Current revision: 6
   // Last modified: 4/12/2004
   // By: Jane Doe
   // Reviewers: Alice, Bill, Cindy

   // class code goes here

}

這里使用相同的元數(shù)據(jù)括享,通過(guò)注解來(lái)實(shí)現(xiàn),我們必須定義注解類型珍促。語(yǔ)法如下:

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}

注解的定義非常類似接口铃辖,只是將接口的interface關(guān)鍵字替換為@interface。注解也是接口的一種形式猪叙。如何使用注解呢娇斩?注解的使用在聲明定義前使用:

@ClassPreamble (
   author = "John Doe",
   date = "3/17/2002",
   currentRevision = 6,
   lastModified = "4/12/2004",
   lastModifiedBy = "Jane Doe",
   // Note array notation
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {

// class code goes here

}

常用的注解類型
注解實(shí)際上被meta-annotations調(diào)用仁卷。幾種meta-annotationsjava.lang.annotation.類中。

  • @Retention 定義一種注解的存儲(chǔ)方式
  • -RetentionPolicy.SOURCE 注解以源碼級(jí)別存儲(chǔ)成洗,忽略編譯
  • -RetentionPolicy.CLASS 注解在編譯時(shí)保留五督,忽略Java Virtual Machine (JVM)
  • -RetentionPolicy.RUNTIME 運(yùn)行時(shí)注解
  • @Documented標(biāo)識(shí)注解可以使用Javadoc tool工具將其生成文檔
  • @Target標(biāo)識(shí)注解將要被應(yīng)用成哪種Java元素,其中包含有:
  • -ElementType.ANNOTATION_TYPE
  • -ElementType.CONSTRUCTOR
  • -ElementType.FIELD
  • -ElementType.LOCAL_VARIABLE
  • -ElementType.METHOD
  • -ElementType.PACKAGE
  • -ElementType.PARAMETER
  • -ElementType.TYPE 任意一種類形式
  • @Inherited標(biāo)識(shí)注解是否可以被子類集成(true或false瓶殃,默認(rèn)false),當(dāng)用戶查詢注解類型副签,該類沒(méi)有聲明注解類型遥椿,該類的父類查詢到該注解類型。該注解僅應(yīng)用在當(dāng)前聲明類
  • @RepeatableJava SE 8引入淆储,標(biāo)識(shí)注解可以用應(yīng)用多個(gè)相同的聲明冠场,具體參考:Repeating Annotations.

EventBus對(duì)注解的使用
先來(lái)看一下注解類聲明:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * If true, delivers the most recent sticky event (posted with
     * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
     */
    boolean sticky() default false;

    /** Subscriber priority to influence the order of event delivery.
     * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
     * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
     * delivery among subscribers with different {@link ThreadMode}s! */
    int priority() default 0;
}

注解支持文檔輸出,存在于運(yùn)行時(shí)本砰,對(duì)Java方法生效碴裙。在使用的時(shí)候如下代碼:

Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
    Class<?> eventType = parameterTypes[0];
    if (findState.checkAdd(method, eventType)) {
        ThreadMode threadMode = subscribeAnnotation.threadMode();
        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
        subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
    }
}

獲取方法聲明的注解,通過(guò)subscribeAnnotation.threadMode()拿到對(duì)應(yīng)的ThreadMode点额,通過(guò)subscribeAnnotation.priority()拿到聲明的優(yōu)先級(jí)舔株,通過(guò)subscribeAnnotation.sticky()拿到是否是粘性廣播。在方法前的聲明如下还棱,聲明非常簡(jiǎn)單:

@Subscribe(threadMode = ThreadMode.MAIN)

5.渦輪引擎-apt注入

apt注入可以重是一種編譯時(shí)注解载慈,如何在gradle中進(jìn)行動(dòng)態(tài)注入呢?android-apt是一個(gè)Gradle插件珍手,協(xié)助Android Studio處理annotation processors, 它有兩個(gè)目的:

  1. 允許配置只在編譯時(shí)作為注解處理器的依賴办铡,而不添加到最后的APK或library
  2. 設(shè)置源路徑,使注解處理器生成的代碼能被Android Studio正確的引用

在EventBus上琳要,主要使用注解處理器生成的代碼寡具,將所有注解的內(nèi)容獨(dú)立到org.greenrobot.eventbusperf.MyEventBusIndex類中,

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        try {
            // 獲取腳本中聲明變量eventBusIndex
            String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
            ....
            // 收集訂閱的聲明
            collectSubscribers(annotations, env, messager);
           // 檢查訂閱聲明
            checkForSubscribersToSkip(messager, indexPackage);

            if (!methodsByClass.isEmpty()) {
                // 將訂閱聲明寫入到索引文件中
                createInfoIndexFile(index);
            } else {
                messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
            }
            writerRoundDone = true;
        } catch (RuntimeException e) {
            // IntelliJ does not handle exceptions nicely, so log and print a message
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e);
        }
        return true;
    }

關(guān)鍵就是將索引到的所有符合要求的注解類稚补,寫入到索引文件中童叠,代碼如下所示:

    private void createInfoIndexFile(String index) {
        BufferedWriter writer = null;
        try {
            JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
            int period = index.lastIndexOf('.');
            String myPackage = period > 0 ? index.substring(0, period) : null;
            String clazz = index.substring(period + 1);
            writer = new BufferedWriter(sourceFile.openWriter());
            if (myPackage != null) {
                writer.write("package " + myPackage + ";\n\n");
            }
            writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
            writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
            writer.write("import java.util.HashMap;\n");
            writer.write("import java.util.Map;\n\n");
            writer.write("/** This class is generated by EventBus, do not edit. */\n");
            writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
            writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
            writer.write("    static {\n");
            writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
            writeIndexLines(writer, myPackage);
            writer.write("    }\n\n");
            writer.write("    private static void putIndex(SubscriberInfo info) {\n");
            writer.write("        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
            writer.write("    }\n\n");
            writer.write("    @Override\n");
            writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
            writer.write("        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
            writer.write("        if (info != null) {\n");
            writer.write("            return info;\n");
            writer.write("        } else {\n");
            writer.write("            return null;\n");
            writer.write("        }\n");
            writer.write("    }\n");
            writer.write("}\n");
        } catch (IOException e) {
            throw new RuntimeException("Could not write source for " + index, e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    //Silent
                }
            }
        }
    }

踩坑與經(jīng)驗(yàn)

1.混淆問(wèn)題
混淆作為版本發(fā)布必備的流程,經(jīng)常會(huì)鬧出很多奇奇怪怪的問(wèn)題孔厉,且不方便定位拯钻,尤其是EventBus這種依賴反射技術(shù)的庫(kù)。通常情況下都會(huì)把相關(guān)的類和回調(diào)方法都keep住撰豺,但這樣其實(shí)會(huì)留下被人反編譯后破解的后顧之憂粪般,所以我們的目標(biāo)是keep最少的代碼。
首先污桦,因?yàn)镋ventBus 3棄用了反射的方式去尋找回調(diào)方法亩歹,改用注解的方式。作者的意思是在混淆時(shí)就不用再keep住相應(yīng)的類和方法。但是我們?cè)谶\(yùn)行時(shí)小作,卻會(huì)報(bào)java.lang.NoSuchFieldError: No static field POSTING亭姥。網(wǎng)上給出的解決辦法是keep住所有eventbus相關(guān)的代碼:

-keep class de.greenrobot.** {*;}

其實(shí)我們仔細(xì)分析,可以看到是因?yàn)樵赟ubscriberMethodFinder的findUsingReflection方法中顾稀,在調(diào)用Method.getAnnotation()時(shí)獲取ThreadMode這個(gè)enum失敗了达罗,所以我們只需要keep住這個(gè)enum就可以了(如下)。

-keep public enum org.greenrobot.eventbus.ThreadMode { public static *; }

這樣就能正常編譯通過(guò)了静秆,但如果使用了索引加速粮揉,是不會(huì)有上面這個(gè)問(wèn)題的。因?yàn)樵谡曳椒〞r(shí)抚笔,調(diào)用的不是findUsingReflection扶认,而是findUsingInfo。但是使用了索引加速后殊橙,編譯后卻會(huì)報(bào)新的錯(cuò)誤:Could not find subscriber method in XXX Class. Maybe a missing ProGuard rule?

這就很好理解了辐宾,因?yàn)樯伤饕鼼eneratedSubscriberIndex是在代碼混淆之前進(jìn)行的,混淆之后類名和方法名都不一樣了(上面這個(gè)錯(cuò)誤是方法無(wú)法找到)膨蛮,得keep住所有被Subscribe注解標(biāo)注的方法:

-keepclassmembers class * {
    @de.greenrobot.event.Subscribe <methods>;
}

所以又倒退回了EventBus2.4時(shí)不能混淆onEvent開(kāi)頭的方法一樣的處境了叠纹。所以這里就得權(quán)衡一下利弊:使用了注解不用索引加速,則只需要keep住EventBus相關(guān)的代碼鸽疾,現(xiàn)有的代碼可以正常的進(jìn)行混淆吊洼。而使用了索引加速的話,則需要keep住相關(guān)的方法和類制肮。

2.跨進(jìn)程問(wèn)題
目前EventBus只支持跨線程冒窍,而不支持跨進(jìn)程。如果一個(gè)app的service起到了另一個(gè)進(jìn)程中豺鼻,那么注冊(cè)監(jiān)聽(tīng)的模塊則會(huì)收不到另一個(gè)進(jìn)程的EventBus發(fā)出的事件综液。這里可以考慮利用IPC做映射表,并在兩個(gè)進(jìn)程中各維護(hù)一個(gè)EventBus儒飒,不過(guò)這樣就要自己去維護(hù)register和unregister的關(guān)系谬莹,比較繁瑣,而且這種情況下通常用廣播會(huì)更加方便桩了,大家可以思考一下有沒(méi)有更優(yōu)的解決方案附帽。

3.事件環(huán)路問(wèn)題
在使用EventBus時(shí),通常我們會(huì)把兩個(gè)模塊相互監(jiān)聽(tīng)井誉,來(lái)達(dá)到一個(gè)相互回調(diào)通信的目的蕉扮。但這樣一旦出現(xiàn)死循環(huán),而且如果沒(méi)有相應(yīng)的日志信息颗圣,很難定位問(wèn)題喳钟。所以在使用EventBus的模塊屁使,如果在回調(diào)上有環(huán)路,而且回調(diào)方法復(fù)雜到了一定程度的話奔则,就要考慮把接收事件專門封裝成一個(gè)子模塊蛮寂,同時(shí)考慮避免出現(xiàn)事件環(huán)路。

老司機(jī)教你 “飆” EventBus 3
EventBus-3-0源碼分析
注解參考
android-apt 即將退出歷史舞臺(tái)
EventBus Documentation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末易茬,一起剝皮案震驚了整個(gè)濱河市酬蹋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抽莱,老刑警劉巖除嘹,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異岸蜗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)叠蝇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門璃岳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人悔捶,你說(shuō)我怎么就攤上這事铃慷。” “怎么了蜕该?”我有些...
    開(kāi)封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵犁柜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我堂淡,道長(zhǎng)馋缅,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任绢淀,我火速辦了婚禮萤悴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘皆的。我一直安慰自己覆履,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布费薄。 她就那樣靜靜地躺著硝全,像睡著了一般。 火紅的嫁衣襯著肌膚如雪楞抡。 梳的紋絲不亂的頭發(fā)上伟众,一...
    開(kāi)封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音拌倍,去河邊找鬼赂鲤。 笑死噪径,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的数初。 我是一名探鬼主播找爱,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泡孩!你這毒婦竟也來(lái)了车摄?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仑鸥,失蹤者是張志新(化名)和其女友劉穎吮播,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體眼俊,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡意狠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疮胖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片环戈。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖澎灸,靈堂內(nèi)的尸體忽然破棺而出院塞,到底是詐尸還是另有隱情,我是刑警寧澤性昭,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布拦止,位于F島的核電站,受9級(jí)特大地震影響糜颠,放射性物質(zhì)發(fā)生泄漏汹族。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一括蝠、第九天 我趴在偏房一處隱蔽的房頂上張望鞠抑。 院中可真熱鬧,春花似錦忌警、人聲如沸搁拙。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)箕速。三九已至,卻和暖如春朋譬,著一層夾襖步出監(jiān)牢的瞬間盐茎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工徙赢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留字柠,地道東北人探越。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像窑业,于是被迫代替她去往敵國(guó)和親钦幔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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