• 【开源项目11】组件间通信利器EventBus


    概述及基本概念

    **EventBus**是一个Android端优化的publish/subscribe消息总线,简化了应用程序内各组件间、组件与后台线程间 的通信。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求 都可以通过**EventBus**实现。

    作为一个消息总线,有三个主要的元素:

    • Event:事件
    • Subscriber:事件订阅者,接收特定的事件
    • Publisher:事件发布者,用于通知Subscriber有事件发生

    Event

    **Event**可以是任意类型的对象。

    Subscriber

    在EventBus中,使用约定来指定事件订阅者以简化使用。即所有事件订阅都都是以onEvent开头的函数,具体来说,函数的名字是 onEvent,onEventMainThread,onEventBackgroundThread,onEventAsync这四个,这个和 ThreadMode有关,后面再说。

    Publisher

    可以在任意线程任意位置发送事件,直接调用EventBus的`post(Object)`方法,可以自己实例化EventBus对象,但一般使用 默认的单例就好了:`EventBus.getDefault()`,根据post函数参数的类型,会自动调用订阅相应类型事件的函数。

    ThreadMode

    前面说了,Subscriber函数的名字只能是那4个,因为每个事件订阅函数都是和一个`ThreadMode`相关联的,ThreadMode指定了会调用的函数。有以下四个ThreadMode:

    • PostThread:事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程,而这个线程可能是UI线程。对应的函数名是onEvent。
    • MainThread: 事件的处理会在UI线程中执行。事件处理时间不能太长,这个不用说的,长了会ANR的,对应的函数名是onEventMainThread。
    • BackgroundThread:事件的处理会在一个后台线程中执行,对应的函数名是onEventBackgroundThread,虽然名 字是BackgroundThread,事件处理是在后台线程,但事件处理时间还是不应该太长,因为如果发送事件的线程是后台线程,会直接执行事件,如果 当前线程是UI线程,事件会被加到一个队列中,由一个线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。
    • Async:事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作,每个事件会开启一个线程(有线程池),但最好限制线程的数目。

    根据事件订阅都函数名称的不同,会使用不同的ThreadMode,比如果在后台线程加载了数据想在UI线程显示,订阅者只需把函数命名为onEventMainThread。

    简单使用

    基本的使用步骤就是如下4步,点击此链接查看例子及介绍。

    1. 定义事件类型:
      `public class MyEvent {}`
    2. 定义事件处理方法:
      `public void onEventMainThread`
    3. 注册订阅者:
      `EventBus.getDefault().register(this)`
    4. 发送事件:
      `EventBus.getDefault().post(new MyEvent())`

    实现

    **EventBus**使用方法很简单,但用一个东西,如果不了解它的实现用起来心里总是没底,万一出问题咋办都不知道,所以还是研究一下它的实 现,肯定要Read the fucking Code。其实主要是`EventBus`这一个类,在看看Code时需要了解几个概念与成员,了解了这些后实现就很好理解了。

    • EventType:onEvent*函数中的参数,表示事件的类型
    • Subscriber:订阅源,即调用register注册的对象,这个对象内包含onEvent*函数
    • SubscribMethod:`Subscriber`内某一特定的onEvent*方法,内部成员包含一个`Method`类型的 method成员表示这个onEvent*方法,一个`ThreadMode`成员threadMode表示事件的处理线程,一个 `Class<?>`类型的eventType成员表示事件的类型`EventType`。
    • Subscription,表示一个订阅对象,包含订阅源`Subscriber`,订阅源中的某一特定方法`SubscribMethod`,这个订阅的优先级`priopity`


    了解了以上几个概念后就可以看`EventBus`中的几个重要成员了

    复制代码
    // EventType -> List<Subscription>,事件到订阅对象之间的映射
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    
    // Subscriber -> List<EventType>,订阅源到它订阅的的所有事件类型的映射
    private final Map<Object, List<Class<?>>> typesBySubscriber;
    
    // stickEvent事件,后面会看到
    private final Map<Class<?>, Object> stickyEvents;
    
    // EventType -> List<? extends EventType>,事件到它的父事件列表的映射。即缓存一个类的所有父类
    private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<Class<?>, List<Class<?>>>();
    复制代码

    注册事件:Register

    通过`EventBus.getDefault().register`方法可以向`EventBus`注册来订阅事件,`register`有很 多种重载形式,但大都被标记为`Deprecated`了,所以还是不用为好,前面说了事件处理方法都是以*onEvent*开头,其实是可以通过 register方法修改的,但相应的方法被废弃了,还是不要用了,就用默认的*onEvent*,除下废弃的register方法,还有以下4 个**public**的`register`方法

    复制代码
    public void register(Object subscriber) {
        register(subscriber, defaultMethodName, false, 0);
    }
    
    public void register(Object subscriber, int priority) {
        register(subscriber, defaultMethodName, false, priority);
    }
    
    public void registerSticky(Object subscriber) {
        register(subscriber, defaultMethodName, true, 0);
    }
    
    public void registerSticky(Object subscriber, int priority) {
        register(subscriber, defaultMethodName, true, priority);
    }
    复制代码

    可以看到,这4个方法都调用了同一个方法:

    复制代码
    private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
    methodName);
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod, sticky, priority);
        }
    }
    复制代码

    第一个参数就是订阅源,第二个参数就是用到指定方法名约定的,默认为*onEvent*开头,说默认是其实是可以通过参数修改的,但前面说了,方法已被废弃,最好不要用。第三个参数表示是否是*Sticky Event*,第4个参数是优先级,这两个后面再说。

    在上面这个方法中,使用了一个叫`SubscriberMethodFinder`的类,通过其`findSubscriberMethods`方 法找到了一个`SubscriberMethod`列表,前面知道了`SubscriberMethod`表示Subcriber内一个 onEvent*方法,可以看出来`SubscriberMethodFinder`类的作用是在Subscriber中找到所有以 methodName(即默认的onEvent)开头的方法,每个找到的方法被表示为一个`SubscriberMethod`对象。

    `SubscriberMethodFinder`就不再分析了,但有两点需要知道:

    1. 所有事件处理方法**必需是`public void`类型**的,并且只有一个参数表示*EventType*。
    2. `findSubscriberMethods`不只查找*Subscriber*内的事件处理方法,**同时还会查到它的继承体系中的所有基类中的事件处理方法**。

    找到*Subscriber*中的所有事件处理方法后,会对每个找到的方法(表示为`SubscriberMethod`对象)调用`subscribe`方法注册。`subscribe`方法干了三件事:

    1. 根据`SubscriberMethod`中的*EventType*类型将`Subscribtion`对象存放在`subscriptionsByEventType`中。建立*EventType*到*Subscription*的映射,每个事件可以有多个订阅者。
    2. 根据`Subscriber`将`EventType`存放在`typesBySubscriber`中,建立*Subscriber*到*EventType*的映射,每个Subscriber可以订阅多个事件。
    3. 如果是*Sticky*类型的订阅者,直接向它发送上个保存的事件(如果有的话)。

    通过*Subscriber*到*EventType*的映射,我们就可以很方便地使一个Subscriber取消接收事件,通过*EventType*到*Sucscribtion*的映射,可以方便地将相应的事件发送到它的每一个订阅者。

    Post事件

    直接调用`EventBus.getDefault().post(Event)就可以发送事件,根据Event的类型就可以发送到相应事件的订阅者。

    复制代码
    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);
        if (postingState.isPosting) {
            return;
        } else {
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            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;
                }
        }
    }
    复制代码

    可以看到post内使用了`PostingThreadState`的对象,并且是`ThreadLocal`,来看`PostingThreadState`的定义:

    复制代码
    final static class PostingThreadState {
        List<Object> eventQueue = new ArrayList<Object>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }
    复制代码

    主要是有个成员`eventQueue`,由于是ThreadLocal,所以 结果就是,每个线程有一个`PostingThreadState`对象,这个对象内部有一个事件的队列,并且有一个成员`isPosting`表示现在 是否正在派发事件,当发送事件开始时,会依次取出队列中的事件发送出去,如果正在派发事件,那么post直接把事件加入队列后返回,还有个成员 `isMainThread`,这个成员在实际派发事件时会用到,在`postSingleEvent`中会用到。

    复制代码
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<? extends Object> eventClass = event.getClass();
        List<Class<?>> eventTypes = findEventTypes(eventClass); // 1
        boolean subscriptionFound = false;
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) { // 2
            Class<?> clazz = eventTypes.get(h);
            CopyOnWriteArrayList<Subscription> subscriptions;
            synchronized (this) {
                subscriptions = subscriptionsByEventType.get(clazz);
            }
            if (subscriptions != null && !subscriptions.isEmpty()) { // 3
                for (Subscription subscription : subscriptions) {
                    postingState.event = event;
                    postingState.subscription = subscription;
                    boolean aborted = false;
                    try {
                        postToSubscription(subscription, event, postingState.isMainThread); // 4
                        aborted = postingState.canceled;
                    } finally {
                        postingState.event = null;
                        postingState.subscription = null;
                        postingState.canceled = false;
                    }
                    if (aborted) {
                        break;
                    }
                }
                subscriptionFound = true;
            }
        }
        if (!subscriptionFound) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
            if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }
    复制代码

    来看一下`postSingleEvent`这个函数,首先看第一点,调用了`findEventTypes`这个函数,代码不帖了,这个函数的应用就是,把这个类的类对象、实现的接口及父类的类对象存到一个List中返回.

    接下来进入第二步,遍历第一步中得到的List,对List中的每个类对象(即事件类型)执行第三步操作,即找到这个事件类型的所有订阅者向其发送 事件。可以看到,**当我们Post一个事件时,这个事件的父事件(事件类的父类的事件)也会被Post,所以如果有个事件订阅者接收Object类型的 事件,那么它就可以接收到所有的事件**。

    还可以看到,实际是通过第四步中的`postToSubscription`来发送事件的,在发送前把事件及订阅者存入了`postingState`中。再来看`postToSubscription`

    复制代码
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
        case PostThread:
            invokeSubscriber(subscription, event);
            break;
        case MainThread:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BackgroundThread:
            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);
        }
    }
    复制代码

    这里就用到`ThreadMode`了:

    • 如果是PostThread,直接执行
    • 如果是MainThread,判断当前线程,如果本来就是UI线程就直接执行,否则加入`mainThreadPoster`队列
    • 如果是后台线程,如果当前是UI线程,加入`backgroundPoster`队列,否则直接执行
    • 如果是Async,加入`asyncPoster`队列

    BackgroundPoster

    复制代码
    private final PendingPostQueue queue;
    
    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!executorRunning) {
                executorRunning = true;
                EventBus.executorService.execute(this);
            }
        }
    }
    复制代码

    代码比较简单,其实就是,待发送的事件被封装成了`PendingPost`对 象,`PendingPostQueue`是一个`PendingPost`对象的队列,当`enqueue`时就把这个事件放到队列 中,`BackgroundPoster`其实就是一个Runnable对象,当`enqueue`时,如果这个Runnable对象当前没被执行,就将 `BackgroundPoster`加入EventBus中的一个线程池中,当`BackgroundPoster`被执行时,会依次取出队列中的事件 进行派发。当长时间无事件时`BackgroundPoster`所属的线程被会销毁,下次再Post事件时再创建新的线程。

    HandlerPoster

    `mainThreadPoster`是一个`HandlerPoster`对象,`HandlerPoster`继承自`Handler`,构造 函数中接收一个`Looper`对象,当向`HandlerPoster` enqueue事件时,会像`BackgroundPoster`一样把这个事件加入队列中, 只是如果当前没在派发消息就向自身发送Message

    复制代码
    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");
                }
            }
        }
    }
    复制代码

    在`handleMessage`中会依次取出队列中的消息交由 `EventBus`直接调用事件处理函数,而`handleMessage`执行所在的线程就是构造函数中传进来的`Looper`所属的线程,在 `EventBus`中构造`mainThreadPoster`时传进来的是MainLooper,所以会在UI线程中执行。

    AsyncPoster

    `AsyncPoster`就简单了,把每个事件都加入线程池中处理

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        EventBus.executorService.execute(this);
    }

    Stick Event

    通过`registerSticky`可以注册Stick事件处理函数,前面我们知道了,无论是`register`还是`registerSticky`最后都会调用`Subscribe`函数,在`Subscribe`中有这么一段代码:

    也就是会根据事件类型从`stickyEvents`中查找是否有对应的事件,如果有,直接发送这个事件到这个订阅者。而这个事件是什么时候存起来 的呢,同`register`与`registerSticky`一样,和`post`一起的还有一个`postSticky`函数:

    复制代码
    if (sticky) {
        Object stickyEvent;
        synchronized (stickyEvents) {
            stickyEvent = stickyEvents.get(eventType);
        }
        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, Looper.getMainLooper() == Looper.myLooper());
        }
    }
    复制代码

    当通过`postSticky`发送一个事件时,这个类型的事件的最后一次事件会被缓存起来,当有订阅者通过`registerSticky`注册时,会把之前缓存起来的这个事件直接发送给它。

    事件优先级Priority

    `register`的函数重载中有一个可以指定订阅者的优先级,我们知道`EventBus`中有一个事件类型到 List<Subscription>的映射,在这个映射中,所有的Subscription是按priority排序的,这样当post事 件时,优先级高的会先得到机会处理事件。

    优先级的一个应用就事,高优先级的事件处理函数可以终于事件的传递,通过`cancelEventDelivery`方法,但有一点需要注意,`这个事件的ThreadMode必须是PostThread`,并且只能终于它在处理的事件。

    # 缺点
    无法进程间通信,如果一个应用内有多个进程的话就没办法了

    # 注意事项及要点

    • 同一个onEvent函数不能被注册两次,所以不能在一个类中注册同时还在父类中注册
    • 当Post一个事件时,这个事件类的父类的事件也会被Post。
    • Post的事件无Subscriber处理时会Post `NoSubscriberEvent`事件,当调用Subscriber失败时会Post `SubscriberExceptionEvent`事件。

    其他

    `EventBus`中还有个Util包,主要作用是可以通过`AsyncExecutor`执行一个Runnable,通过内部的 RunnableEx(可以搜索异常的Runnable)当Runnable抛出异常时通过`EventBus`发消息显示错误对话框。没太大兴趣,不作 分析

    项目主页:https://github.com/greenrobot/EventBus

     一个很简单的Demo,Activity中包含列表和详情两个Fragment,Activity启动时加载一个列表,点击列表后更新详情数据:EventBusDemo

  • 相关阅读:
    shell进行mysql统计
    java I/O总结
    Hbase源码分析:Hbase UI中Requests Per Second的具体含义
    ASP.NET Session State Overview
    What is an ISAPI Extension?
    innerxml and outerxml
    postman
    FileZilla文件下载的目录
    how to use webpart container in kentico
    Consider using EXISTS instead of IN
  • 原文地址:https://www.cnblogs.com/dongdong230/p/4193034.html
Copyright © 2020-2023  润新知