EventBus源码分析

EventBus-Publish-Subscribe.png

简述

EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。优点是开销小,代码更优雅,以及将发送者和接收者解耦。

基本使用

1.新建一个类,AnyEventType。可以是网络请求返回的字符串,也可以是某个开关状态,也可以是空。

public class AnyEventType {  
 public AnyEventType(){}  
}  

2.注册订阅者

EventBus.getDefault().register(this);

3.发送事件

EventBus.getDefault().post(new AnyEventType event);

4.编写响应事件订阅方法

@Subscribe(threadMode = ThreadMode.BACKGROUND, sticky = true, priority = 100)
public void hello(String str) {

}  

这里说明一下3.0之后可以通过@Subscribe注解,来确定运行的线程threadMode,是否接受粘性事件sticky以及事件优先级priority,而且方法名不在需要onEvent开头,所以又简洁灵活了不少.

5.解除注册

EventBus.getDefault().unregister(this);

源码解析

1.新建EventBus
  • 默认可通过静态函数 getDefault 获取单例

    public static EventBus getDefault() {
      if (defaultInstance == null) {
        synchronized (EventBus.class) {
      if (defaultInstance == null) {
        defaultInstance = new EventBus();
      }}}
    return defaultInstance;
    }
    
  • EventBusBuilder 新建一个 EventBus

    public static EventBusBuilder builder() {
    return new EventBusBuilder();
    }
    
  • 构造函数新建一个EventBus

    public EventBus() {
      this(DEFAULT_BUILDER);
    }
    
2.register

register 函数中会先根据订阅者类名去subscriberMethodFinder
中查找当前订阅者所有事件响应函数,然后循环每一个事件响应函数,依次执行subscribe 函数

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

源码太长就不全部贴出来了

1.首先通过subscriptionsByEventType得到该事件类型所有订阅者信息队列,根据优先级将当前订阅者信息插入到订阅者队列subscriptionsByEventType中;如果添加过就抛出异常。

CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
    subscriptions = new CopyOnWriteArrayList<>();
    subscriptionsByEventType.put(eventType, subscriptions);
  } else {
  if (subscriptions.contains(newSubscription)) {
      throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);
    }
  }

2.在typesBySubscriber中得到当前订阅者订阅的所有事件队列,将此事件保存到队列typesBySubscriber中,用于后续取消订阅;

List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); 
if (subscribedEvents == null) { 
    subscribedEvents = new ArrayList<>();         
    typesBySubscriber.put(subscriber, subscribedEvents); 
  }

3.检查这个事件是否是 Sticky 事件,如果是则立即分发sticky事件

if (subscriberMethod.sticky) { 
//eventInheritance 表示是否分发订阅了响应事件类父类事件的方法 
    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); 
      } 
}
4.post

首先得到当前线程的 post 信息PostingThreadState,其中包含事件队列,将当前事件添加到其事件队列中,然后循环调用postSingleEvent 函数发布队列中的每个事件。

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

postToSubscription 函数中会判断订阅者的 ThreadMode,从而决定在什么 Mode 下执行事件响应函数。ThreadMode 共有四类:

  • PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作
  • MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作
  • BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
  • Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问
post.png
5.unregister

通过typesBySubscriber来取出这个subscriber订阅者订阅的事件类型,从typesBySubscriber移除subscriber。

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

推荐阅读更多精彩内容

  • EventBus源码分析(一) EventBus官方介绍为一个为Android系统优化的事件订阅总线,它不仅可以很...
    蕉下孤客阅读 3,966评论 4 42
  • 面试有一种技巧据说叫做反客为主,当遇到Activity-Fragment通信,甚至模块化开发时的通信问题等等,可以...
    TruthKeeper阅读 530评论 0 6
  • EventBus源码分析(三) 在前面的两篇文章中分别对EventBus中的构建,分发事件以及注册方法的查询做了分...
    蕉下孤客阅读 1,207评论 0 9
  • title: EventBus 源码分析date: 2017-09-15 09:38:14tags: [Sourc...
    Passon_Fang阅读 212评论 0 0
  • 【如果一睁眼就收到妈妈成长的喜讯,你一定会绽放心情,我很感谢接受我帮助的姐妹,企鹅妈妈陪伴就是和你在一起感受生活】...
    企鹅妈妈阅读 497评论 0 0