轻量级Android事件派发框架——EventBus源码全解析

Hello,All,我是来自58同城的一名Android开发工程师,在58集团从事Android SDK的开发工作。

关注我,即刻解锁全部干货推文

PS:关注,私信我,帮你内推58,常年招聘前端,移动端,后端,算法。

也欢迎关注我的公众号,在这里可以找到我,同时,这里会不定期地推送一些时下最热门的技术文章和互联网行业工作心路历程

 


 

12k视觉-客车品牌后期修图部分集锦_摄...

 

说到当今Android开发领域,最火的事件派发框架,EventBus绝对是黑科技满满,C位出道。

在经历了历史上2个比较大的版本迭代后,它以其轻巧、简单、无侵入性的使用方式,吸引了越来越多的移动端开发者,并将其囊括进了他们的APP开发框架当中。

正好赶在清明假期,抽时间来研究了一下框架的源码,顺带也熟悉了一下Annotation注解的使用方式。

不得不说,注解对于Java所带来的优势太大了。注解,可以简单的理解为描述数据的数据,在这样的理论基础之上,通过对外暴露的注解接口,我们在内部处理经过注解后的方法,使其达到一定的数据配置和一些自动化处理过程。并且,我曾经考虑过,是否可以将其引入到正在维护的公司代码中呢?

正好前段时间打算把公司代码中的部分业务逻辑进行优化,涉及到了事件传递部分,自然就想到了EventBus,通过阅读EventBus源码,我也再次领略到了其设计的精巧和Annotation给我们带来解耦之方便和其强大之处。

接下来,就让我们走进源码,来看看EventBus为何如此之精巧,如此之强大吧!

以下内容基于EventBus 3.2.0版本进行解析。

源码讲解部分总共分三个部分进行,与我们正常的开发和使用顺序一致:

 

  1. EventBus.java类的结构分析
  2. EventBus订阅注册过程
  3. 事件派发过程
  4. EventBus取消订阅过程

 

  1. Eventbus.java的结构分析
EventBus(EventBusBuilder builder) {
    logger = builder.getLogger();
    subscriptionsByEventType = new HashMap<>();
    typesBySubscriber = new HashMap<>();
    stickyEvents = new ConcurrentHashMap<>();
    mainThreadSupport = builder.getMainThreadSupport();
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    executorService = builder.executorService;
}

通过EventBus的构造函数我们可以很容易地看出它的结构构成,可以看到,在他的构造函数里,采用了EventBusBuilder携带参数的方式,将前置的配置参数进行了传递,当然,在默认情况下,EventBus已经帮我们配置好了,不需要我们主动调用默认的构造函数。

而在默认的EventBus实例的获取过程中,也是采用了单例的设计模式,并且,这里还贴心地采用了线程安全的双重检查单例模式,不吹不黑,在这块考虑的还是挺周到的。

 

2.EventBus订阅注册过程:

/**
 * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
 * are no longer interested in receiving events.
 * <p/>
 * Subscribers have event handling methods that must be annotated by {@link Subscribe}.
 * The {@link Subscribe} annotation also allows configuration like {@link
 * ThreadMode} and priority.
 */
public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

让我们从register函数开始。

当在我们的代码中调用了register函数后,在函数内部,首先是获取到传进来的subscriber的Class对象,接着,将这个Class对象通过一个SubscriberMethodsFinder对象的findSubScriberMethods方法,来找到加了@Subscribe标签的方法,并将其转化为了一个Java对象,这个转化的过程和我们正常的注解解析过程一致。如果对注解的运行原理不懂的同学,可以关注公众号并回复 anno ,有一篇纯正的专家级Annotation使用及原理讲解发给你,相信看过之后一定会帮助你在注解使用和原理分析这块获得暴力提升

好,让我们来看看findSubScriberMethods里面都做了些什么:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    。。。 

else {
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

首先,我们可以看到,先是做了一个判重的检查,如果订阅的这个类之前已经参与过订阅,那么就不回再次添加进监听队列了,然后,通过订阅类来寻找到了加注Subscriber标签的方法,在这里,经过我实测,调用的是第一个方法:findUsingReflection(subscribeClass)方法,在这个方法里,内部调用了一个findUsingReflectionInSingleClass(findState)

这样一个方法,这个看似平淡无奇,但作者也将其性能做到了极致,通过注释,我们能够看到,作者在获取注册类的注解方法时,是通过findState.clazz.getDeclaredMethods();

这样的方式来执行的,并且简单粗暴地解释了这么做的原因:

// This is faster than getMethods, especially when subscribers are fat classes like Activities

在这之后,就将这个订阅方法的集合作为一个对象进行了返回,返回到了我们的register方法里。

此时,resiger已经拿到了subscriber类的订阅方法集合,接下来,就是把这些集合进行统一的存储,管理,这个动作是由subscribe方法来做的


// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    。。。
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    }
。。。
    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) {
        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);
        }
    }
}

可以看到,这里是将注册进来的事件都通过subscriptionsByEventType来进行了管理,并且这里使用了CopyOnWriteArrayList容器,好,让我们记住这个贯穿于EventBus始终的对象“subscriptionsByEventType”。

 

你以为这就完了?No No,更精彩的还在后面,还记得stickyEvent吗?没错,就是每次面试都会问起来的黏性事件,好像很神奇是吧?为什么在一个没有启动起来的注册接收者里面仍然能够接收到呢?

让我们把目光聚焦到这里:

if (subscriberMethod.sticky) {
    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);
    }
}

可以看到,这一块是在subscribe中专门用来处理黏性事件的逻辑。

如果检测到当前注册的方法是黏性事件方法,就走到这块的处理逻辑,最终是通过

checkPostStickyEventToSubscription(newSubscription, stickyEvent);并且在内部调用postToSubscription(newSubscription, stickyEvent, isMainThread());来进行事件派发的

 

3.事件派发过程

事件派发过程让我们从post方法开始。

/** Posts the given event to the event bus. */
public void post(Object event) {
    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;
        }
    }
}

这里我们能够看到,在函数开始的地方先是通过一个currentPostingThreadState来get了一个当先线程对象的post状态变量
那么,这个currentPostingThreadState是什么呢,让我们到定义处去看一看

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

可以看到,这里使用了ThreadLocal,ThreadLocal???是否有一种似曾相识的感觉呢?

没错,在我们的Handler机制当中也使用了它!

ThreadLocal并不是一个Thread,而是用来存储每个Thread模型中变量的,而且是和其他线程隔离的,只有本线程内能够访问到。

所以,这里get过来的PostingState变量就是属于当前线程的。

接着,我们看到,通过调用eventQueue来获取到了一个时间队列,也就是当前线程的事件队列他主要是用来进行事件派发的,我们可以看到,在方法的第三行,就是将刚刚传进来的时间模型给添加进了eventQueue里面。

接着,就来到了真正的事件派发过程postSingleEvent(eventQueue.remove(0), postingState);

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

在这里,先是根据eventType找到了对应的加了subscribe标签的class,然后,在内部调用了postToSubscription(subscription, event, postingState.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);
    }
}

这个方法里使用了switch语句根据注册时的ThreadMode进行不同的处理,这里先对几种不同的threadMode进行一个解析:

通过ThreadMode枚举类,我们可以看到, EventBus 3.2.0一共有5种模式:

POSTING:派发线程和订阅线程在同一个线程模式。同时也是默认的派发模式。

MAIN:在主线程进行派发,同时也是具有Android特色的派发方式,因为在Java中只有 Android具有主线程模式。

MAIN_ORDERED:具有顺序的主线程派发模式。

BACKGROUND:后台派发模式。如果当前的派发线程在非主线程,则直接采用当前线程进行派发。如果当前线程在主线程,则进行一次线程切换,单独开一个线程进行子线程派发。

ASYNC:异步派发模式。不论当前是在主线程还是在子线程,总是会单独开辟一个线程进行事件的派发,但是不用担心,EventBus会帮你使用线程池来优化线程使用体验,这一点,在注释里面也是明确注明了的。

 

值得注意的是:

在POSTING这个case中,使用了反射的方式直接通过method调用了注册方法,并且调用的线程和post的线程也是一致的。

而在MAIN 这个case中,也是Eventbus的一大精彩看点:主子线程的切换。

case MAIN:
    if (isMainThread) {
        invokeSubscriber(subscription, event);
    } else {
        mainThreadPoster.enqueue(subscription, event);
    }
    break;

我们可以看到,在这个case中先是通过传入的标志位来判断是否当前在主线程,如果在主线程则直接invoke注册方法。如果在非主线程,则通过HandlerPoster的enqueue方法将当前的事件进行入队。

public void enqueue(Subscription subscription, Object event) {
    PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
    synchronized (this) {
        queue.enqueue(pendingPost);
        if (!handlerActive) {
            handlerActive = true;
            if (!sendMessage(obtainMessage())) {
                throw new EventBusException("Could not send handler message");
            }
        }
    }
}

在HandlerPoster的enqueue方法中,EventBus的团队为了极致的性能体验,并没有采用JAVA SDK 中的LinkedList等链表容器,而是手动的实现了名为queue的PendingPostQueue单向链表。

接下来,我们可以看到,通过sendMessage发起了一次Handler消息传递流程,并且使用了obtainMessage来获取到了一个空的Message对象来发送出去。

到了handleMessage之后,此时已经切换为了主线程处理模式,这里依旧是使用了eventbus的invokeSubscriber方法来反射式地进行了注册方法的调用。

看到这里,相信你一定有个疑问,到底是如何切换到主线程的Handler的?

让我们切换到HandlerPoster对象的构造方法位置,来看看是谁调用了他。

/**
 * Interface to the "main" thread, which can be whatever you like. Typically on Android, Android's main thread is used.
 */
public interface MainThreadSupport {
    boolean isMainThread();
    Poster createPoster(EventBus eventBus);
    class AndroidHandlerMainThreadSupport implements MainThreadSupport {
        private final Looper looper;
        public AndroidHandlerMainThreadSupport(Looper looper) {
            this.looper = looper;
        }
        @Override
        public boolean isMainThread() {
            return looper == Looper.myLooper();
        }
        @Override
        public Poster createPoster(EventBus eventBus) {
            return new HandlerPoster(eventBus, looper, 10);
        }
    }

}

可以看到,在这里使用了一个接口的内部类AndroidHandlerMainThreadSupport来进行了主线程的切换动作,并且在构造方法当中还传入了一个looper对象。

那么问题来了,这个looper是何方神圣?

通过FindUsage我们继续向上寻找,发现其实是在这里:

MainThreadSupport getMainThreadSupport() {
    if (mainThreadSupport != null) {
        return mainThreadSupport;
    } else if (AndroidLogger.isAndroidLogAvailable()) {
        Object looperOrNull = getAndroidMainLooperOrNull();
        return looperOrNull == null ? null :
                new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
    } else {
        return null;
    }
}

在这里get了一个MainLooper,并且向下传递,最后,HandlerPoster也就获取到了主线程的looper,并且经过他构造的Handler也自然就成为了主线程的Handler,多么精巧的设计!

 

4.EventBus解注册过程

在经过了注册,使用之后,我们还要记得把注册对象进行释放,也就是我们的解注册过程,这个过程相对就容易很多了,相当于是注册过程的逆过程,这里就不再进行详述了。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EventBusAndroid中一款非常实用的事件布/订阅框架,它可以帮助开者更方便地进行组件间通信,减少代码耦合度,提高代码复用性。在使用 EventBus 过程中,回调函数是非常重要的组成部分,下面我们来一起学习一下如何使用 EventBus 进行回调。 首先,在使用 EventBus 进行回调之前,需要先进行 EventBus 的注册和注销操作。在 Activity 或 Fragment 中注册 EventBus 的代码如下: ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 注册 EventBus EventBus.getDefault().register(this); } ``` 在 Activity 或 Fragment 销毁时,需要进行注销操作,代码如下: ```java @Override protected void onDestroy() { super.onDestroy(); // 注销 EventBus EventBus.getDefault().unregister(this); } ``` 接下来,我们来看一下如何使用 EventBus 进行回调。假设我们有一个 Activity A,需要从另一个 Activity B 中获取数据并进行处理,在 Activity A 中,我们可以定义一个事件,如下: ```java public class DataEvent { private String data; public DataEvent(String data) { this.data = data; } public String getData() { return data; } } ``` 在 Activity B 中,我们可以通过 EventBus 送该事件,并传递数据,如下: ```java EventBus.getDefault().post(new DataEvent("Hello EventBus!")); ``` 在 Activity A 中,我们需要定义一个方法来接收该事件,并进行相应的处理,代码如下: ```java @Subscribe(threadMode = ThreadMode.MAIN) public void onDataEvent(DataEvent event) { String data = event.getData(); // 处理数据 } ``` 在这个方法上使用了 @Subscribe 注解,表示该方法用来接收事件,threadMode = ThreadMode.MAIN 表示该方法在主线程中执行。当 Activity A 接收到事件后,该方法就会被调用,并将事件中传递的数据作为参数传递给该方法。这样,我们就实现了通过 EventBus 进行回调的功能。 需要注意的是,使用 EventBus 进行回调时,需要保证订阅者和布者在同一进程中,否则无法接收到事件。另外,在使用 EventBus 进行回调时,建议使用事件模型,将数据封装成事件对象,这样可以更好地进行解耦和管理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值