美文网首页
【源码学习】EventBus源码学习

【源码学习】EventBus源码学习

作者: 蜗牛是不是牛 | 来源:发表于2022-05-27 15:29 被阅读0次

一、注册

EventBus.getDefault().register(this);

public void register(Object subscriber) {
    //获取订阅者类信息
    Class<?> subscriberClass = subscriber.getClass();
    //获取当前订阅者的所有@Subscribe注解的方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //循环订阅每个方法
            subscribe(subscriber, subscriberMethod);
        }
    }
}

其中的SubscriberMethod是用来包装方法信息的实体类,包含方法、运行线程、接收的事件类型、优先级、是否粘性事件

public class SubscriberMethod {
    final Method method;//方法
    final ThreadMode threadMode;//运行线程
    final Class<?> eventType;//事件的类型
    final int priority;//优先级
    final boolean sticky;//是否粘性事件
    /** Used for efficient comparison */
    String methodString;
    ...
}

public class EventBus {
    //订阅者队列,key为Event类信息,value的item为订阅者和该订阅者下的所有订阅方法
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    //后续准备取消的事件队列
    private final Map<Object, List<Class<?>>> typesBySubscriber;
    //粘性事件队列
    private final Map<Class<?>, Object> stickyEvents;

    // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
            //获取Event事件类型
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //获取该Event事件类型的所有订阅者信息,比如有多个Activity的方法注册了该Event事件
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
                //如果说没有订阅者订阅了这个Event,就把他添加到订阅者队列
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
                //如果订阅者队列里面已有该Event的订阅者们,并且新增加的订阅者也包含在里面,就抛出异常提示已近订阅
                //比如在MainActivity里面连续调用两次register方法
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

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

                //获取当前订阅者的所有事件队列,后续取消订阅要用到
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
                
                //下面是粘性事件的处理,暂时不看,后续再分析
        if (subscriberMethod.sticky) {
            ...
        }
    }
    ...
 }

这个方法做的是:

  • 把Event事件类型的Subscription存入到订阅队列里面,Subscription是一个实体类,包含订阅者和订阅方法

  • 按照priority的大小插入,优先级大的插在队列前面

  • 获取当前订阅者的所有订阅方法,存储在typesBySubscriber中,后面unRegistre取消订阅要用到

  • 如果是粘性事件,这个后续再看具体的实现

二、发送Event

EventBus.getDefault().post(new XXXEvent());

public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    //step 1: 把要发送的Event存入队列
    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 {
                //step 2:循环发送队列里的Event
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

接着调用postSingleEvent方法

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    if (eventInheritance) {
        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);
    }
    ...
}

接着调用postSingleEventForEventType方法

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
            //step 1:从订阅者队列获取该Event事件的所有订阅者
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
            //step 2: 遍历所有订阅者
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                    //step 3:再调用postToSubscription方法传入了是否为主线程,便于线程的切换
                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;
}

再调动postToSubscription方法,传入了是否为主线程的入参isMainThread,下面就进入了接收Event事件的代码了

总接一下发送事件:

  • 从订阅者队列获取该Event事件的所有订阅者
  • 遍历订阅者
  • 根据订阅者所在的线程postToSubscription方法触发接收事件的代码逻辑

三、接收Event

接着来看如何实现接收Event事件的postToSubscription方法

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

例如我们在MainActivity中添加了如下事件订阅方法

@Subscribe(threadMode = ThreadMode.MAIN)
public void onLoginStateChange(LoginStateChangeEvent event) {
    if (event.status.equals(LoginStateChangeEvent.LOGIN)) {
        getTicketInfo();
    }
}

通过注解指定了threadMode为ThreadMode.Main也就是表明我们的订阅方法是在主线程响应的,关于枚举类ThreadMode定义了如下几种类型,含义分别如下

public enum ThreadMode {
    POSTING,
    MAIN,
    MAIN_ORDERED,
    BACKGROUND,
    ASYNC
}

  • POSTING

    消息订阅者将与发送者所处在同一线程中。这是默认值。该模式避免了线程切换所带来的开销。这是处理简单事务所推荐的模式。如果发布线程是主线程,使用该模式必须立即返回,以避免阻塞主线程。

  • MAIN

    消息订阅者将在Android主线程(即UI线程)中被调用。 如果发布线程是主线程,事件处理方法将直接调用。 使用此模式的事件处 理必须快速返回以避免阻塞主线程。

  • BACKGROUND

    消息订阅者将在后台线程中被调用。如果发布线程不是在主线程。事件处理方法将被直接调用而不需要切换线程。如果发布线程是在主线程,EventBus使用了单一的后台线程有序地传递所有事件。 使用此模式的事件处理必须快速返回而避免阻塞后台线程。

  • ASYNC

    事件处理方法将在单独的一个线程中调用。这个线程往往独立于发布线程和主线程(即UI线程)。发布事件不需要等待事件处理方法。如果事件处理方法比较耗时,则需要使用该模式。例如:用于网络访问。 避免触发大量数据长时间运行的异步处理程序方法同时限制并发线程的数量。EventBus使用线程池来高效地复用线程,完成异步事件处理通知。

  • MAIN_ORDERED

    (该模式在3.1.1版本之后才支持)订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。事件将先进入队列然后才发送给订阅者,所以发布事件的调用将立即返回。这使得事件的处理保持严格的串行顺序。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。

四、取消注册

取消事件比较简单,一般在onStop调用unregister方法

EventBus.getDefault().unregister(this);

public synchronized void unregister(Object subscriber) {
        //从typesBySubscriber获取当前订阅者所有的订阅者信息
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
            //遍历取消绑定
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        //从订阅者队列取当前Event事件的所有订阅者
    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--;
            }
        }
    }
}

  • 获取当前订阅者订阅的所有事件类型。

  • 遍历事件队列,解除事件注册。

  • 移除事件订阅者。

五、粘性事件

发送一个粘性事件,通过postSticky方法,把粘性事件存储到了stickyEvents集合里面,在和发送普通事件一样调用了post方法

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

粘性事件的触发,在注册时候调用subscribe方法时去执行的

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //前面的内容已经分析过了
    ...
    //如果是粘性事件
    if (subscriberMethod.sticky) {
        if (eventInheritance) {
            //获取所有的粘性事件并且遍历
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                //判断是否为对应Event类型的粘性事件
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    //执行checkPostStickyEventToSubscription方法
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    if (stickyEvent != null) {
        //又执行到了第三点讲到的事件的分发响应线程调度的postToSubscription()方法
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}

总结

EventBus源码还是比较简单,内部通过三个Map集合存储所有事件和订阅者信息

  • Map<Class<?>, CopyOnWriteArrayList> subscriptionsByEventType存储所有的Event和其对应的订阅者
  • Map<Object, List<Class<?>>> typesBySubscriber存储unRegister时要清除的订阅者
  • Map<Class<?>, Object> stickyEvents存储粘性Event和其对应的订阅者的信息

相关文章

网友评论

      本文标题:【源码学习】EventBus源码学习

      本文链接:https://www.haomeiwen.com/subject/hlomprtx.html