第三方开源库源码のEventBus解析

EventBus的使用

  • 注册
EventBus.getDefault().register(context);
  • 定义subscriber
@Subscribe(sticky = false)
public void getSubMsg(MsgEvent msg){
    System.out.println("I got msg :"+msg);
}
  • 发送消息
EventBus.getDefault().post(new MsgEvent());
  • 发送黏性消息
EventBus.getDefault().postSticky(new MsgEvent());

流程图解

image-20201212173610341.png

EventBus的流程

getDefault

getDefault是一个获取EventBus单例的方法。EventBus内部采用了是双重校验锁的单例模式。

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

EventBus()构造方法中初始化了许多需要用到的变量,看名字是用到了Build模式。

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

public EventBus() {
    this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
    ...
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
}

- register(Object subscriber)

注册方法如下:

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}
  1. 获取传入的这个subscriber对象的Class对象。

  2. 通过findSubscriberMethods获取其内部的所有被@Subscribe标记过的Method

  3. 遍历这些方法,依次调用subscribe(subscriber, subscriberMethod);

subscriberMethod是内部的一个类,里面有method、threadMode、Class<?> eventType、boolean sticky等信息。在findSubscriberMethods中就已经都写进去了。

subscribe

subscribe分为两部分。

第一部分

前一部分对传入的subscriberMethod对象进行分类存储。

  1. 准确的说是以eventType为类别分开,存到一个个的List中。即Map< eventType , ArrayList<Subscription> >,也是下面代码中subscriptionsByEventType的类型;

    eventType@Subscribe标记的方法中传入的类型,一般是我们自定义的MsgEvent

    这整个方法都是在一个遍历中的,当前eventType对应的ArrayListnewSubscription

  2. newSubscription根据优先级排序。

  3. 换个维度再分类一次。即Map< Subscription, ArrayList<eventType> >(对比第一步,就是换了分类的维度),存储在subscribedEvents中。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
-----------------------------------1.以eventType分类--------------------------------------------------------
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    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);
        }
    }
-----------------------------------2.排序--------------------------------------------------------
    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;
        }
    }
------------------------------------3.以subscriber分类----------------------------------------------

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);
------------------------------------4.如果是黏性事件-------------------------------------------
    ...以下为第二部分代码...
}

第二部分

第二部分主要是对粘性事件的处理,代码紧接上一部分。(ps:只是为了看的清晰些而分开,并没有其他目的,其实代码是连在一起的。😀)。这里也可以先不关注,等到黏性事件解析的时候再看,没影响。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    ....
  ------------------------------------4.如果是黏性事件-------------------------------------------
    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);
        }
    }
}

stickyEvents是一个Map,Key为黏性post传递对象的Class,value为这个传递的对象。

  1. 如果是父类继承下来的,.............那么....emm...这里我也没搞懂,以后弄懂了再补上😂。反正还是会调到checkPostStickyEventToSubscription方法。
  2. 否则,直接调用checkPostStickyEventToSubscription方法。
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    if (stickyEvent != null) {
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}

postToSubscription中就是按照线程分别执行invoke方法。所以可以看到如果是粘性事件,那么直接把存放在stickyEvents中的方法给执行完事了。因为黏性事件post方法中传递的对象就放在stickyEvents中的。合情合理。

- post

post方法执行完以后,所有被@subscribe标记的方法都会被执行。又因为上文register中已经对所有标记过的方法分类好了,所以按常理,post中要做的事情就是遍历出这些方法,然后一一invoke就好了。

不过真实代码没有这么简单粗暴,我们来看下。

currentPostingThreadState是一个ThreadLocal,里面存了PostingThreadState类。

ThreadLocal是一个关于创建线程局部变量的类。

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

/** Posts the given event to the event bus. */
public void post(Object event) {
    -----------------------------------------1.登记-------------------------------------
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

    ----------------------------------2.安检,一次一个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()) {
   -------------------------------3.一切检查完成后,下一步-------------------------
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
  1. 刚进到方法里去,门口一个老大爷就要登记。于是,这些方法都被记录到了一个叫eventQueue的List中去。

  2. isPosting状态标记当前是否正在发送消息中,一开始为false,进去后改为true,避免两个事件同时进去执行了。记录线程,看有没有canceled(返回)。

  3. 做完一些常规检查,一切正常后执行postSingleEvent(eventQueue.remove(0), postingState);

postSingleEvent

不要忘了我们进来这个方法是为了干嘛,是要执行所有被标记过的eventType分类下的方法。所以要怎么做?

  1. 先把存储的eventTypes罗列出来,用lookupAllEventTypes方法。
  2. 再亮出自己的身份,和eventTypes一一对比,如果相同则postSingleEventForEventType。准确的说是先postSingleEventForEventType,然后返回结果,true表示匹配成功,false表示匹配失败。无论有没有成功都会接着下一次postSingleEventForEventType直到遍历完所有的eventType
  3. 遍历完了还是false,说明没有注册呗。错误处理~
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    if (eventInheritance) {
 ------------------------------------1.罗列出所有eventTypes---------------------------
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
  -------------------------2.直接执行方法,返回true表示信息发出去了----------------------------------
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
  --------------------------------3.错误处理-------------------------------------
    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

将一系列信息录入到postingState中,然后执行postToSubscription方法。不列源码了,直接上postToSubscription

postToSubscription

终于看到调用的方法了😂,按照不同的线程进行不同的invoke操作。

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

要么放入一个Poster中,要么直接执行invokeSubscriber方法。每个Poster也是一个Runnable,将event传入Poster后最终调用的也是invokeSubscriber方法,所以来看invokeSubscriber

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

没错,这正是我们所期盼的😂。和我们想的一样,最终是调用到了method.invoke,将这些标记过的方法一一执行。

- postSticky

粘性事件的处理。

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // Should be posted after it is putted, in case the subscriber wants to remove immediately
    post(event);
}

代码很简单,将eventClassevent存在stickyEvents中,然后和post一样就行了。

就这?为什么能产生黏性效果?

请回过头来看上文subscribe中的第二部分👆。

如果你记性还不错的话,还记得黏性事件会跳到一个叫checkPostStickyEventToSubscription的方法,然后跳转到了postToSubscription方法,那么恭喜你,postStickt的流程也走完了。

完结撒花~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容