EventBus3.1源碼分析

這篇文章將會(huì)為大家梳理一下EventBus的基本流程伸但,本人使用的版本號為3.1.1血筑,為了方便閱讀,文章中的源碼部分將省略部分有關(guān)異常捕獲與日志相關(guān)代碼赃绊。

使用示例

首先,按照官方的文檔來看看一個(gè)最簡單的EventBus示例是什么樣的:

第一步:定義消息實(shí)體類

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

第二步:使用注解創(chuàng)建訂閱方法

@Subscribe
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

第三步:注冊與注銷訂閱(注意與生命周期的綁定)

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

第四步:發(fā)送通知消息

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

這樣羡榴,一個(gè)簡單的Demo就完成了碧查,本文將以這個(gè)Demo為基礎(chǔ),分析訂閱事件從注冊到接收并執(zhí)行都是如何實(shí)現(xiàn)的校仑。

1.EventBus.getDefault().register(this);

EventBus.getDefault()

    static volatile EventBus defaultInstance;

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

獲取EventBus的單例對象么夫,很標(biāo)準(zhǔn)的單例模式,就不細(xì)說了肤视。

EventBus.register(Object subscriber)

    public void register(Object subscriber) {
        //獲取注冊訂閱的類的Class對象
        Class<?> subscriberClass = subscriber.getClass();
        //查找Class中帶有Subscribe注解的方法列表
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

SubscriberMethodFinder.findSubscriberMethods(Class<?> subscriberClass)

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        //METHOD_CACHE為ConcurrentHashMap<Class<?>, List<SubscriberMethod>>,作為方法解析的緩存使用
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        //如果取出的SubscriberMethod不為null涉枫,則說明該類已被加載過邢滑,那么跳過解析的步驟,直接返回上次解析的結(jié)果
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        //ignoreGeneratedIndex用來設(shè)置是否忽略使用Subscriber Index來幫助注解解析愿汰,默認(rèn)設(shè)置為false
        //Subscriber Index為EventBus3.0中出現(xiàn)的新特性困后,在build期間生成,以此增加注解解析的性能
        //關(guān)于Subscriber Index的更多信息可以參考官方文檔 http://greenrobot.org/eventbus/documentation/subscriber-index/
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            //如果類中沒有找到Subscriber注解的方法衬廷,拋出異常
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            //將Subscriber注解的方法放入緩存并返回
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

從這個(gè)方法可以看出EventBus會(huì)將注冊的訂閱事件以Class對象為key摇予,訂閱方法的Method對象為value存入METHOD_CACHE緩存中,避免同一個(gè)類多次注冊訂閱時(shí)重復(fù)解析的問題吗跋,提升解析的性能侧戴。

在注解解析方面,EventBus提供了傳統(tǒng)的反射解析與使用Subscriber Index兩種方式跌宛,下面將主要對反射解析方式分別進(jìn)行分析

SubscriberMethodFinder.findUsingReflection(Class<?> subscriberClass)

    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        //通過FindState對象池創(chuàng)建FindState對象
        FindState findState = prepareFindState();
        //初始化FindState對象
        findState.initForSubscriber(subscriberClass);
        
        while (findState.clazz != null) {
            //解析這個(gè)類中帶有Subscribe注解的方法
            findUsingReflectionInSingleClass(findState);
            //向父類進(jìn)行遍歷
            findState.moveToSuperclass();
        }
        //獲取解析出的方法酗宋,將findState重置并放回對象池中
        return getMethodsAndRelease(findState);
    }
    
    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            //getDeclaredMethods方法只會(huì)獲取到該類所定義的方法,而getMethods方法會(huì)獲取包括這個(gè)類的父類與接口中的所有方法
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            // 使用getMethods就已經(jīng)獲得了其父類的所有方法疆拘,所以將skipSuperClasses標(biāo)志位設(shè)置為true蜕猫,在后續(xù)過程中不對其父類進(jìn)行解析
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            //Subscribe注解的方法不應(yīng)為私有且不應(yīng)為抽象,靜態(tài)等
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                //Subscribe注解的方法應(yīng)有且只有一個(gè)參數(shù)
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        //調(diào)用checkAdd方法判斷是否將訂閱事件添加進(jìn)訂閱列表中
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                }
            }
        }
    }

在這段代碼中出現(xiàn)了一個(gè)FindState類對象哎迄,其中含有解析相關(guān)的配置回右,解析出的方法等等,整個(gè)解析的過程都是圍繞著這個(gè)對象進(jìn)行處理漱挚。

FindState.checkAdd

    final Map<Class, Object> anyMethodByEventType = new HashMap<>();
    final StringBuilder methodKeyBuilder = new StringBuilder(128);
    final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();

    boolean checkAdd(Method method, Class<?> eventType) {
        // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
        // Usually a subscriber doesn't have methods listening to the same event type.
        Object existing = anyMethodByEventType.put(eventType, method);
        // 第一級判斷翔烁,如果existing為null,則代表該eventType為首次添加棱烂,直接返回true
        if (existing == null) {
            return true;
        } else {
            // 對于這段代碼租漂,作者這么處理的用意我不是很明白,希望能有人能為我解答一下
            if (existing instanceof Method) {
                if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                    // Paranoia check
                    throw new IllegalStateException();
                }
                // Put any non-Method object to "consume" the existing Method
                // 該方法只為了將一個(gè)不是Method的對象放入,避免下一次再次進(jìn)入此邏輯
                anyMethodByEventType.put(eventType, this);
            }
            return checkAddWithMethodSignature(method, eventType);
        }
    }
        
    private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
        //使用方法名與參數(shù)類型名生成該方法簽名
        methodKeyBuilder.setLength(0);
        methodKeyBuilder.append(method.getName());
        methodKeyBuilder.append('>').append(eventType.getName());

        String methodKey = methodKeyBuilder.toString();
        Class<?> methodClass = method.getDeclaringClass();
        Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
        
        if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
            // Only add if not already found in a sub class
            // 如果有現(xiàn)方法的父類方法已注冊過事件,則繼續(xù)添加哩治,使用現(xiàn)方法進(jìn)行覆蓋
            return true;
        } else {
            // Revert the put, old class is further down the class hierarchy
            // 撤銷之前的put操作秃踩,并返回false
            subscriberClassByMethodKey.put(methodKey, methodClassOld);
            return false;
        }
    }

這部分代碼的作用主要有兩點(diǎn):

  1. 當(dāng)一個(gè)類重載了父類的一個(gè)訂閱方法,在向上級父類遍歷時(shí)跳過父類中的這個(gè)方法的訂閱业筏,也就是說以子類的訂閱方法為準(zhǔn)
  2. 允許一個(gè)類有多個(gè)方法名不同的方法對同個(gè)事件進(jìn)行訂閱

值得一提的是關(guān)于methodClassOld.isAssignableFrom(methodClass)這一句代碼憔杨,有文章提到因?yàn)橹暗拇a中會(huì)使用Class.getMethods()方法,會(huì)得到這個(gè)類以及父類的public方法蒜胖,所以這句代碼的結(jié)果可能為true消别,但是經(jīng)我測試,如果父類方法被子類重載台谢,那么getMethods方法得到的只會(huì)有子類的重載方法一個(gè)寻狂,父類的方法并不會(huì)出現(xiàn),所以從這方面來說朋沮,這句代碼依然只能是false蛇券。

EventBus.register(Object subscriber, SubscriberMethod subscriberMethod)

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            //如果該事件沒有注冊過,則將訂閱事件添加進(jìn)訂閱列表中
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //同一對象內(nèi)訂閱事件不能重復(fù)注冊
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        int size = subscriptions.size();
        //根據(jù)訂閱事件的優(yōu)先級將事件加入訂閱列表中
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

        //將參數(shù)類型以訂閱者對象為key樊拓,參數(shù)類型列表為value保存進(jìn)一個(gè)Map中
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        //對于一般的事件的注冊纠亚,到這里就已經(jīng)完成了
        //-----------------------------------------------------------------------------
        
        //針對粘性事件訂閱者的處理
        if (subscriberMethod.sticky) {
            //eventInheritance:是否支持事件繼承,默認(rèn)為true筋夏。當(dāng)為true時(shí)蒂胞,post一個(gè)事件A時(shí),若A是B的父類条篷,訂閱B的訂閱者也能接收到事件骗随。
            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>).
                //遍歷所有注冊的粘性事件進(jìn)行遍歷,如果一個(gè)事件為現(xiàn)事件的子類赴叹,則將該事件發(fā)送出去
                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 {
                //發(fā)送訂閱的粘性事件
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

到現(xiàn)在為止蚊锹,其實(shí)已經(jīng)可以總結(jié)出EventBus中訂閱者注冊的核心邏輯了,就是篩選出注冊的類中帶有Subscribe注解的方法稚瘾,然后將其解析為SubscriberMethod對象牡昆,以訂閱的事件類型為key,SubscriberMethod對象為value的形式存入subscribedEvents中摊欠。

對于粘性事件的發(fā)送丢烘,其實(shí)與之后的一般事件執(zhí)行邏輯相同,這里就不再深入了些椒。

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

EventBus.post(Object event)

    public void post(Object event) {
        //從ThreadLocal中取出PostingThreadState對象播瞳,PostingThreadState對象只是保存一些消息發(fā)送過程中需要的信息
        //使用ThreadLocal保證PostingThreadState對象在各線程間相互獨(dú)立,以此實(shí)現(xiàn)線程安全
        PostingThreadState postingState = currentPostingThreadState.get();
        //從PostingThreadState對象中取出當(dāng)前線程的消息隊(duì)列免糕,并將需要發(fā)送的消息加入隊(duì)列中
        List<Object> eventQueue = postingState.eventQueue;
        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 {
                //開始遍歷執(zhí)行消息隊(duì)列
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                //遍歷結(jié)束赢乓,重置PostingThreadState對象
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

在這段代碼中忧侧,isMainThread()方法可以用來判斷消息發(fā)送是否是在主線程中。其中的邏輯非常簡單牌芋,通過Looper類中的getMainLooper()獲取到主線程中的Looper對象蚓炬,然后再通過Looper類中的myLooper()方法獲取當(dāng)前線程的Looper對象,然后相互比較躺屁。

EventBus.postSingleEvent(Object event, PostingThreadState postingState)

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        //如果該消息支持事件繼承肯夏,則獲取該消息類型的所有父類與接口類型,并分別調(diào)用postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)方法
        if (eventInheritance) {
            //獲取消息類型的所有父類與接口
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            //對于ArrayList而言犀暑,使用基本的for循環(huán)實(shí)現(xiàn)遍歷效率更高
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        
        //如果該消息沒有訂閱者驯击,則根據(jù)配置輸出相應(yīng)的log或發(fā)送消息
        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));
            }
        }
    }

EventBus.postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)

    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            //從Map中將該消息的訂閱事件列表取出
            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;
    }

這個(gè)方法的主要作用就是獲得消息的訂閱事件列表,完成對于postingState中參數(shù)的賦值耐亏,其中對于所有的訂閱事件徊都,使用的其實(shí)是同一個(gè)PostingThreadState對象,只是對于不同的訂閱事件广辰,會(huì)改變其中的部分參數(shù)的值碟贾。

EventBus.postToSubscription(Subscription subscription, Object event, boolean isMainThread)

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

在默認(rèn)情況下,EventBus會(huì)在調(diào)用線程中直接執(zhí)行invokeSubscriber方法調(diào)用訂閱事件轨域,但當(dāng)調(diào)用線程與期望的執(zhí)行線程不一致或希望異步調(diào)用時(shí),使用Poster來進(jìn)行不同線程間的調(diào)度杀餐,每一個(gè)Poster中都會(huì)持有一個(gè)消息隊(duì)列干发,并在指定線程執(zhí)行。

EventBus.invokeSubscriber(Subscription subscription, Object event)

    void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            //如果捕獲到InvocationTargetException異常史翘,則根據(jù)配置打印Log或發(fā)送異常消息
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

這段代碼也就是訂閱事件執(zhí)行部分的最后一段代碼了枉长,可以看到邏輯非常的簡單,就是通過反射來執(zhí)行相應(yīng)的訂閱事件琼讽。

總結(jié)

在上文的分析中可以看出必峰,EventBus的主要邏輯非常簡單,核心流程就是注冊時(shí)先解析出帶有相應(yīng)注解的方法钻蹬,然后將其的Method對象與其所訂閱的消息類型綁定加入到一個(gè)集合中吼蚁,然后發(fā)送消息時(shí)獲取到消息類型所對應(yīng)的訂閱事件的Method對象,通過反射來調(diào)用執(zhí)行问欠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肝匆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子顺献,更是在濱河造成了極大的恐慌旗国,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件注整,死亡現(xiàn)場離奇詭異能曾,居然都是意外死亡度硝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門寿冕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕊程,“玉大人,你說我怎么就攤上這事蚂斤〈孓啵” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵曙蒸,是天一觀的道長捌治。 經(jīng)常有香客問我,道長纽窟,這世上最難降的妖魔是什么肖油? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮臂港,結(jié)果婚禮上森枪,老公的妹妹穿的比我還像新娘。我一直安慰自己审孽,他們只是感情好县袱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佑力,像睡著了一般式散。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上打颤,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天暴拄,我揣著相機(jī)與錄音,去河邊找鬼编饺。 笑死乖篷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的透且。 我是一名探鬼主播撕蔼,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秽誊!你這毒婦竟也來了罕邀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤养距,失蹤者是張志新(化名)和其女友劉穎诉探,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棍厌,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肾胯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年竖席,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敬肚。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毕荐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出艳馒,到底是詐尸還是另有隱情憎亚,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布弄慰,位于F島的核電站第美,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏陆爽。R本人自食惡果不足惜什往,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望慌闭。 院中可真熱鬧别威,春花似錦、人聲如沸驴剔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丧失。三九已至豺妓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間利花,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工载佳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炒事,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓蔫慧,卻偏偏與公主長得像挠乳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子姑躲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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

  • 原文鏈接:http://blog.csdn.net/u012810020/article/details/7005...
    tinyjoy閱讀 541評論 1 5
  • 本文分為以下幾個(gè)部分:創(chuàng)建睡扬、注冊、發(fā)送事件黍析、粘性事件來講解它的實(shí)現(xiàn)原理,本文使用Eventbus版本為3.1.1卖怜。...
    龍兒箏閱讀 310評論 0 1
  • 概述 關(guān)于EventBus3.x的用法,本文不再贅述阐枣,只分析其實(shí)現(xiàn)原理马靠,官方的流程圖: 訂閱流程 需要訂閱事件的對...
    悠嘻俠閱讀 721評論 0 51
  • 在我們開發(fā)過程中奄抽,相信應(yīng)該有很多人使用過EventBus 3.0,這個(gè)確實(shí)方便了我們甩鳄,少些了很多代碼逞度,這是個(gè)優(yōu)秀的...
    曾大穩(wěn)丶閱讀 182評論 2 1
  • 焦姣和大牛因?yàn)橐槐緯Y(jié)緣,查令十字街84號妙啃。 最初是因?yàn)檫@本書帶給他們的霉運(yùn)档泽,于是雙方都一氣之下把這本書寄回原址...
    沈鑫閱讀 363評論 0 1