EventBus使用及其源码解析

什么是EventBus?

  • EventBus是一种用于Android的发布/订阅事件总线。它有很多优点:简化应用组件间的通信;解耦事件的发送者和接收者;避免复杂和容易出错的依赖和生命周期的问题;很快,专门为高性能优化过等等。
  • EventBus采取的是订阅者/发布者的模式,发布者通过EventBus发布事件,订阅者通过EventBus订阅事件。当发布者发布事件时,订阅该事件的订阅者的事件处理方法将被调用。

EventBus的使用

刚才说过,EventBus采用的是订阅者/发布者模式,也就是说,我们要先建立一个事件,然后再想发送相关信息的组件创建发布者,然后在要接收信息的组件创建订阅者并且获取信息。大致分如下四步:

创建一个发送事件类

public class EventMessage {
    String account;
    public EventMessage(String account) {
        this.account = account;
    }
    public String getAccount() {
        return account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
}

在其中写出要传递的信息,在加入get/set方法即可。

定义订阅者方法

定义一个事件处理方法(也称为订阅者方法),当指定类型事件被发布时,此方法会被调用。EventBus使用@Subscribe注解来定义订阅者方法。方法名可以是任意合法方法名,参数类型为订阅事件的类型。

@Subscribe(threadMode = ThreadMode.POSTING,sticky = true)
public void showEventMessage(EventMessage message){
    textView.setText(message.getAccount());
}

注册和注销订阅事件

@Override
    protected void onStart(Bundle savedInstanceState) {
        super.onStart();
        EventBus.getDefault().register(this);
    }

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

通常在onStart方法中注册订阅者,在onStop方法中注销订阅者。

发送消息

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

EventBus的源码导读

Subscribe注解的使用

在刚才的使用中我们得知,在每一个订阅者方法上面都要加上Subscribe注释并传入参数,那么我们看一下Subscribe注解的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    // 线程的模式,默认为POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;
    // 是否坚持粘性事件,默认不支持
    boolean sticky() default false;
    // 一个优先级标识,默认为0
    int priority() default 0;
}

上面的threadMode是存放了一个线程模式,会根据这个线程模式,来处理订阅者方法被调用所在的线程:

public enum ThreadMode {
    // 在当前的线程中直接处理。
    POSTING,
    // 在主线程中发送事件,则直接处理,但是要求处理量小;在子线程中,就通过Handler发送后,再由主线程处理
    MAIN,
    // 总是入队列进行等待,通过Handler发送事件后,再由主线程处理
    MAIN_ORDERED,
    // 在主线程中发送事件,入队列依次处理;在子线程中发送事件,则直接处理。
    BACKGROUND,
    // 总是入队列等待,通过线程池异步处理。
    ASYNC
}
  • ThreadMode.POSTING 订阅者方法将在发布事件所在的线程中被调用。这是 默认的线程模式。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。这种线程模式意味着最少的性能开销,因为它避免了线程的切换。因此,对于不要求是主线程并且耗时很短的简单任务推荐使用该模式。使用该模式的订阅者方法应该快速返回,以避免阻塞发布事件的线程,这可能是主线程。
  • ThreadMode.MAIN订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。如果发布事件的线程是主线程,那么该模式的订阅者方法将被直接调用。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
  • ThreadMode.MAIN_ORDERED 订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。事件将先进入队列然后才发送给订阅者,所以发布事件的调用将立即返回。这使得事件的处理保持严格的串行顺序。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
  • ThreadMode.BACKGROUND 订阅者方法将在后台线程中被调用。如果发布事件的线程不是主线程,那么订阅者方法将直接在该线程中被调用。如果发布事件的线程是主线程,那么将使用一个单独的后台线程,该线程将按顺序发送所有的事件。使用该模式的订阅者方法应该快速返回,以避免阻塞后台线程。
  • ThreadMode.ASYNC 订阅者方法将在一个单独的线程中被调用。因此,发布事件的调用将立即返回。如果订阅者方法的执行需要一些时间,例如网络访问,那么就应该使用该模式。避免触发大量的长时间运行的订阅者方法,以限制并发线程的数量。EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程。

说完了注解和线程模式,下面我们就针对EventBus的注册注销和发送事件处理事件的源码进行分析。

两个全局变量map

进行源码分析之前,我要先说EventBus的两个全局变量,typeBySubscriber和subscriptionsByEventType。

// Map构成的类,使用时Key对应Activity,value对应type
// 存储的是Activity下对应的所有事件类型
private final Map<Object, List<Class<?>>> typesBySubscriber;
// Map构成的类,使用时Key对应type,value对应订阅集合
// 存储的是对应的事件类型下的subscriptions
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

也就是说

  • typeBySubscriber是一个以订阅者(activity范围)为key,以该订阅者所订阅的所有事件类型组成的集合为value
  • subscriptionsByEventType是一个以订阅事件类型为key,以所有订阅该事件类型的订阅者的集合为value。

说完这两个全局变量,我们对源码会有更好的理解能力。

注册流程

EventBus.getDefault()

为什么我们调用EventBus.getDefault()可以完成不同activity的操作?比如post,register,unregister?

是因为EventBus.getDefault()这个方法调用的是DCL的单例模式:

static volatile EventBus defaultInstance;

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

众多activity只持有一个eventBus对象。

register(this)

我们调用的是EventBus.getDefault().register(this)来注册的,所以第一步一定是进入register的源码:

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        // 就是当前的Activity中寻找到带有@Subscribe注解的方法集合
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 线程同步方式完成订阅事件的绑定
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod); // 1 -->
            }
        }
    }

这个方法的工作只有两步:

  • 第一步:调用findSubscriberMethods(subscriberClass)方法,去获取当前activity中所有带有@Subscribe注解的方法集合,也就是获取当前订阅者(activity范围)所有的订阅方法即订阅事件类型。
  • 第二步:调用subscribe(subscriber, subscriberMethod)方法,传入当前订阅者,和其包含的所有订阅方法。

接下来我们看是如何获取的订阅方法。

findSubscriberMethods()

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        // 先是从缓存中取,有则直接返回,无则寻找
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        // 因为我们一般使用的都是getDefault()来进行一个注册,所以ignoreGeneratedIndex默认为false。去查看EventBusBuilder即可,会看到未赋值,也就是默认值的false
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            // 默认调用的方法,将订阅的方法进行收集
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            // 对查询到的数据进行一个缓存
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

这个方法的逻辑就是:先去缓存里面找,有的话直接获取返回,没有的话调用findUsingInfo(subscriberClass)方法进行继续查询,然后查询到将其缓存到内存中。

接下来又到了findUsingInfo方法。

findUsingInfo()方法

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                // 一般进入这个方法,通过反射的方式判断方法使用是否满足一些基本条件
                // 是否是public修饰,是否为一个参数,是否为静态,是否为抽象类
                findUsingReflectionInSingleClass(findState);
            }
            // 用于跳过对一些Android SDK的查询操作
            // 可以防止一些ClassNotFoundException的出现
            findState.moveToSuperclass();
        }
        // 对遍历获得的方法进行重构
        // 对findState进行回收
        return getMethodsAndRelease(findState);
    }

这个方法里面就是通过反射来获取所有的方法,并且判断方法是否满足一些基本条件。

至此,我们就拿到了当前订阅者包含的所有订阅方法。然后就到了register()方法中的最后一步:subscribe()

subscribe()方法

作为注册的最后一个方法,直接上代码

//传入参数,订阅者(activity范围)和订阅者所包含的所有订阅方法
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        // 找到对应订阅方法订阅的事件类型
        Class<?> eventType = subscriberMethod.eventType;
        // 根据subscriber和事件类型创建一个subscription对象
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // 通过事件类型,拿到subscription订阅事件集合
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // 如果subscriptionsByEventType中并不存在对应的eventType,就创建并存储
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                ......
        }
        // 将新的订阅事件subscription根据优先级放入订阅事件集合subscriptions中,也就是简介完成了在subscriptionsByEventType的添加
        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;
            }
        }
        // 以订阅者为key,获取它订阅的所有事件类型的集合
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        //向集合中加入当前新订阅的事件类型,即完成了在typesBySubscriber中的添加
        subscribedEvents.add(eventType);
        // 对粘性事件的一个具体操作
        if (subscriberMethod.sticky) {
           // 。。。。。。
        }
    }

这个方法中,我们主要是对之前说过的两个变量typesBySubscriber、subscriptionsByEventType进行操作,把订阅者和订阅事件类型放入前者,把订阅事件类型和订阅事件放在后者,订阅事件中包含订阅者和其对应的订阅方法。对于粘性事件的处理,我们一会会说。

现在就完成了注册(把订阅者,订阅方法,订阅事件类型保存了起来),下面我们就开始看注销的源码。

注销流程

注销的流程相对简单,注册的流程是向两个全局map中存放数据,注销的流程自然就是在两个全局map中删除数据。

注销的代码是:

EventBus.getDefault().unregister(this);

所以我们先看以下unregister()方法干了些什么。

unregister()

//这个方法传入的参数是订阅者(activity范围)
public synchronized void unregister(Object subscriber) {
        // 根据订阅者,获取他所订阅的所有的订阅事件类型type
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            // 循环遍历,删去对应的数据
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType); 
            }
            //数据删除之后,最后在typesBySubscriber中把此订阅者移除
            typesBySubscriber.remove(subscriber);
        }
    }

这个方法逻辑总共有三步:

  • 第一步:获取此订阅者所订阅的所有事件类型
  • 第二步:根据类型,去删除对应数据,盲目猜测是去全局map subscriptionsByEventType中删除相应数据。
  • 第三步:数据都删除了,最后在typesBySubscriber中把此订阅者移除。

逻辑说完了,我们看一下删除相应数据的unsubscribeByEventType(subscriber, eventType)方法具体做了什么。

unsubscribeByEventType(subscriber, eventType)

//这个方法传入参数是订阅者和订阅事件类型
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    	//通过订阅事件类型去找到订阅其类型的所有订阅事件
        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--;
                }
            }
        }
    }

这个方法主要是根据订阅事件类型,删除subscriptionsByEventType中所有的当前订阅者的订阅事件。

经过上面两个方法,我们就成功的清除了两个全局map中关于当前订阅者的所有订阅事件,至此我们就完成了注销的所有工作。

小结

img

发送和处理事件

发送事件的方法是:

EventBus.getDefault().post(new MessageEvent("MessageEvent"));

所以我们想了解发送事件,就要先看一下post这个方法:

post(event)

public void post(Object event) { 
        // PostingThreadState是存储了事件队列,线程模式等等信息的类,其是一个PostingThreadState 类型的ThreadLocal
        PostingThreadState postingState = currentPostingThreadState.get();
        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 {
                // 循环着发送事件
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

这个方法当中,主要是获取到PostingThreadState的对象,其是一个ThreadLocal,存储了发送的事件队列,线程模式等等存储信息的类,然后将待发送事件放入事件队列中,然后调用postSingleEvent(eventQueue.remove(0), postingState)循环发送事件

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

这个方法的逻辑就是,获取到发送事件所对应的类,然后通过lookupAllEventTypes获取eventClass的所有父类。然后通过轮循,对所有的类及父类进行发送事件只要有一个类,有被订阅,则subscriptionFound为true。接下来又调用了postSingleEventForEventType方法:

postSingleEventForEventType()

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // 获取事件对应的subscriptions集合
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            // 通过轮训的方式对事件进行处理
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted;
                try {
                    // 对事件真正的做出处理
                    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;
    }

这个方法中,我们通过订阅事件类型,在全局的2个map其中的subscriptionsByEventType中找到所有的订阅事件集合,然后进行遍历,调用postToSubscription(subscription, event, postingState.isMainThread)方法,根据不同的线程模式来处理:

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

从这个方法开始就进入了处理事件的阶段,如果在当前线程可以处理,最后都会调用invokeSubscriber()方法处理。如果不是在当前线程处理,则就会调用Handler进行线程间通信,去调用我们创建的订阅方法。这里就不讲Handler的实现方式了,就只看一下当前线程可以运行的情况,所以我们直接看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()方法来对我们的方法进行处理,这里也就直接执行了我们订阅者的相对应的定义的方法了。

粘性事件的发送

粘性事件发送的代码:

EventBus.getDefault().postSticky(new MessageEvent("MessageEvent"));

我们看一下postSticky方法:

postSticky()

public void postSticky(Object event) {
        synchronized (stickyEvents) {
            // 对应每次的粘性事件,保证我们最后获取时拿到的都是最新的
            stickyEvents.put(event.getClass(), event);
        }
        // 用锁机制存放后再发送,防止直接消费
        post(event);
    }

通过对事件上锁,然后调用的还是一个post()方法,但是这里我们需要想起的是我们之前尚未分析的subscribe()中针对粘性事件做出处理的方法。

subscribe()—粘性事件的处理

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); // 1 -->
                    }
                }
            } else {
                //根据eventType,从stickyEvents列表中获取特定的事件
                Object stickyEvent = stickyEvents.get(eventType);
                //分发事件
                checkPostStickyEventToSubscription(newSubscription, stickyEvent); // 1 -->
            }
        }

这个方法中,会找到列表中所有的粘性事件,遍历找到与当前订阅的事件类型相同的粘性时间,然后发送。最后发送事件调用的是checkPostStickyEventToSubscription方法:

checkPostStickyEventToSubscription()

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            // 这个方法在之前已经做过了分析
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }

最后发现最后调用的还是postToSubscription方法。

小结

img

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值