EventBus源码分析

好久没写博文了,这几个月复习了很多东西,新年在老家闲的慌啊,我这么爱学习的人只能学习了哈啊哈哈哈哈哈哈(滑稽脸)。顺便总结总结一些容易忘的东西。

EventBus的使用相信大家都使的贼溜了。实在不行看文档嘛。本文只要是分析下EventBus的源码执行过程,分析分析设计的思路,这样对使用和深入学习甚至改进轮子都有好处。


这张是EventBus官网档下来的哈哈哈哈哈!

很明显啦,EventBus用的观察者模式,Publisher作为事件发布者(被观察者)把Event发布到中转服务站EventBus,EventBus把Event传给Subscriber事件订阅者(观察者)并且唤醒了它。


image

一. EventBus结构

根据EventBus源码,其架构可以分为5个部分

  1. 构造方法
  2. 订阅者注册
  3. 发送事件
  4. 根据不同线程参数等处理事件(用反射运行目标方法)
  5. 订阅者取消注册

1. 构造方法

EventBus注册的入口是EventBus.getDefault().register(this)

getDefault()方法如下


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

这里用了双重检查模式的单例模式。并且这里很高明的使用volatile 避免的指令重排序引起 未被完全初始化的问题。
什么?不懂重排序?
没事,重排序其实通俗的讲就是虚拟机为了优化指令,提高程序运行效率搞的东西。或者这么说,本来1,2,3三条指令按顺序运行,重排序之后那可能会变成1,3,2。这样会影响某些对象的初始化问题。

虽然效率双重检查模式会有一定影响运行效率,但是对同步代码天大的好处。

回头来,EventBus()的构造方法


private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
public EventBus() {
        this(DEFAULT_BUILDER);
    }

这货,搞了个独生子EventBusBuilder(),让它全权代理各参数以及配置的存储(其实也就是设计模式中的Builder模式)

EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        ...省略一大堆妃子.....
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }

2. 订阅者注册

接着到了register()方法

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

这里有俩个地方很关键,一个是1. findSubscriberMethods()方法还有一个是2. subscribe()方法,他们一个是为了找到订阅方法,一个是用来注册订阅方法。

1方法是为了找到订阅者传进来的所有订阅方法,当然里面还挺复杂的,还写了判断有没有缓存来提高效率。

findSubscriberMethods()中用了findUsingInfo()方法来获取订阅者的各种信息,并且进而调用了findUsingReflectionInSingleClass()来反射获取订阅者中的方法还有将他们保存到findState里面。

总结起来1方法也就是找到所有订阅方法,保存订阅方法信息到findState

2方法是为了注册所有的订阅方法,当然还有粘性事件的处理,解析可以看下注释

 private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        //根据订阅者和订阅对象创建订阅对象
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //根据事件类型获取订阅对象集合
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(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;
            }
        }

       .....
        
        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();
                         //从上面stickyEvents集合中获取该类型的事件发送给当前订阅者。下面else里头也一样
                         checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

这里有点特别的地方就是,粘性事件的处理,如果是粘性事件,那么从粘性事件的集合中取出该事件发送到当前订阅者。


3. 发送事件

这里从currentPostingThreadState对象中取出事件队列并且将当前需要的事件保存到队列中,然后处理队列中的所有事件,哦,同时移除操作的事件

 public void post(Object event) {
        //事件队列和线程状态信息的Model类
        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()处理事件,并且移除该事件
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

又回到postSingleEvent()方法里,从这里开始向下,又是一系列的对方法的查找的算法(向上查找事件的父类),结果是查找到所有的父类事件后存在List中,然后通过postSingleEventForEventType(event, postingState, eventClass)方法对各个事件分别处理,这里的函数名可以看出。。。。。
这里处理的关键在于postToSubscription(subscription, event, postingState.isMainThread);

另外,假如是粘性事件,那么,
从入口函数开始

EventBus.getDefault().postSticky(object);

它会把粘性事件的object放到下面这个Map里面

private final Map<Class<?>, Object> stickyEvents;

便于第2步的subscribe()方法处理粘性事件

4. 根据不同线程参数等处理事件(用反射运行目标方法)

从第3步的最后那里,可以知道事件给了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);
        }
    }

还记得使用EventBus定义处理事件的注解吗,也就是EventBus的4种线程模式,再看看上面的switch语句的处理,是不是遇到了父老乡亲。

这里根据不同的线程模式,来处理各个方法(反射执行方法),比如ThreadMode是MAIN,那么这里假如不是主线程的方法,则用Handler切换到主线程再执行。


5. 订阅者取消注册

取消注册那就是跟注册是相反的,大体原理一样,不过是变成了遍历取消注册


public synchronized void unregister(Object subscriber) {
        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());
        }
    }

这里最关键的地方在遍历函数里头的

unsubscribeByEventType(subscriber, eventType);

它是为了从订阅对象集合里面逐个逐个移除subscriber(订阅者)


总结

我画了张思维导图,可能要放大才清楚咯

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

推荐阅读更多精彩内容

  • EventBus源码分析(一) EventBus官方介绍为一个为Android系统优化的事件订阅总线,它不仅可以很...
    蕉下孤客阅读 3,972评论 4 42
  • 面试有一种技巧据说叫做反客为主,当遇到Activity-Fragment通信,甚至模块化开发时的通信问题等等,可以...
    TruthKeeper阅读 534评论 0 6
  • EventBus源码分析(二) 在之前的一篇文章EventBus源码分析(一)分析了EventBus关于注册注销以...
    蕉下孤客阅读 1,648评论 0 10
  • 停停走走又几天,断断续续这一年。时间还是如此之快,转眼间又到了17年的末尾了。趁着这些天还能静下心来看看代码,赶紧...
    nick_young阅读 1,267评论 0 3
  • 断食第三天感觉整个身体轻飘飘的,额头上起了三个大包,刚她们起了个外号,叫谷大包哈哈哈! 资源要到的太少了吧!就两个...
    是军儿呀阅读 249评论 0 0