学习EventBus之发布事件

上篇文章中,我们从源码里了解了EventBus的注册机制。这篇文章我们接着上篇,把另一半——发布来了解一下。

我们一般可以通过eventBus.getDefault().post(event)的方式来发布一个事件,然后订阅了这个类型事件的类就会接收到这个事件并进行相应的处理。那么,我们就从post(event)下手,看看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 = Looper.getMainLooper() == Looper.myLooper();
        //更改发布状态
        postingState.isPosting = true;
        if (postingState.canceled) {
        //这个状态会在发布成功后重置为false
            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,这个对象里记录了当前线程的事件队列,是否主线程以及事件的订阅者等信息。

final static class PostingThreadState {
    //事件队列
    final List<Object> eventQueue = new ArrayList<Object>();
    boolean isPosting;
    boolean isMainThread;
    //订阅者
    Subscription subscription;
    //推送的事件
    Object event;
    boolean canceled;
}

新发布的事件会添加到事件队列中,然后从队列头部拿出一个事件调用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++) {//这里进行便利推送事件,调用post一次其实推送了好多个事件
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
    //如果不发布有继承关系的事件,则只发布一个该类型的事件
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    if (!subscriptionFound) {
    //没有注册的接收该类事件的对象,跑出异常
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

private List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
    synchronized (eventTypesCache) {
        List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
        if (eventTypes == null) {
            eventTypes = new ArrayList<Class<?>>();
            Class<?> clazz = eventClass;
            while (clazz != null) {
                eventTypes.add(clazz);//事件类型
                addInterfaces(eventTypes, clazz.getInterfaces());//事件类实现的接口类型
                clazz = clazz.getSuperclass();//事件类的父类
            }
            eventTypesCache.put(eventClass, eventTypes);
        }
        return eventTypes;
    }
}

这里有一个eventTypesCache的集合,该集合内以事件类型为key,以该事件类型的父类、接口以及自己为value存储了该类事件的所有父类和接口的Class类型。当post一个新事件的时候,首先根据该事件的类型查找之前有没有发送过同样的事件,如果没有则存起来,方便下次使用。最后调用postSingleEventForEventType()方法把该类型的事件发布出去。

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);//根据事件的类型从订阅列表中查找该类事件的订阅者
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {//查找到了该类事件的订阅者 最终会返回True 如果没查到则返回false
        for (Subscription subscription : subscriptions) {//遍历查找到的订阅者
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            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;
}

这里我们看到了上篇文章中出现的subscriptionsByEventType,从上篇文章我们知道这个集合里根据事件类型存储了订阅某类事件的所有类。所以这里首先根据事件类型拿到所有的该类型事件的订阅者,然后遍历调用postToSubscription()方法,该方法通过反射的方式调用订阅者内的对应onEvent**方法。

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {//根据订阅者中订阅方法的线程模式进行不同的调用
        case PostThread://如果订阅者的订阅方法线程模式是PostThread,则直接在推送线程中调用订阅方法
            invokeSubscriber(subscription, event);
            break;
        case MainThread:
            if (isMainThread) {//如果订阅者的订阅方法线程模式是MainThread,且事件推送线程也是MainThread,则直接调用订阅方法
                invokeSubscriber(subscription, event);
            } else {//否则发送到主线程的队列中进行调用
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BackgroundThread:
            if (isMainThread) {//如果订阅者的订阅方法线程模式是BackgroundThread,且事件推送线程也是MainThread,则发送到BackgroundThread的队列中调用
                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);
    }
}

可以看到,这里根据订阅者的方法名判断的方法应该在哪个线程模式下执行,分了4个分支。注释中对这几种模式的订阅者方法回调方式已经做了说明。

不管是那种线程模式,最终都会调用invokeSubscriber(Subscription subscription, Object event)方法。

void invokeSubscriber(Subscription subscription, Object event) {

subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

}

显然,这里使用了反射的方法来回调订阅者中的事件处理方法。至此,一个订阅/发布以及处理的流程就完成了。

当然,我们这里没有分析EventBus的高级使用方法,包括事件的阻断、粘滞事件等等,有兴趣的朋友可以自己研究下。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • 先吐槽一下博客园的MarkDown编辑器,推出的时候还很高兴博客园支持MarkDown了,试用了下发现支持不完善就...
    Ten_Minutes阅读 549评论 0 2
  • EventBus源码分析(一) EventBus官方介绍为一个为Android系统优化的事件订阅总线,它不仅可以很...
    蕉下孤客阅读 3,945评论 4 42
  • 《鹧鸪天·黄菊枝头生晓寒》 【宋】黄庭坚 黄菊枝头生晓寒。人生莫放酒杯干。风前横笛斜吹雨,醉里簪花倒著冠。 身健在...
    树梢的雪阅读 610评论 15 11
  • 当秋日夕阳的那抹嫣红笼罩四野,当薄凉的秋风轻抚脸庞,我的灵魂独卧在红尘蒲扇上,那厚重的喘息也慢慢的舒缓了下来。陀螺...
    恰如初阅读 241评论 0 0