Android eventbus笔记

检查更新2020年3月5日 14:17:30

Event bus for Android and Java that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality

简单理解一下就是 Event bus 给 Android 及 Java (当然主要是 Android)的 Activity,Fragment,Threads,Services 之间提供一个简单的通信方式,从而能让我们用质量高且少的代码来完成我们的功能。
传统的事件传递方式包括:Handler、BroadCastReceiver、Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。

一、使用

1.1gradle中引入

    api 'org.greenrobot:eventbus:3.0.0'

1.2 定义消息事件

public class MessageEvent {

   public final String message;
   public MessageEvent(String message) {
        this.message = message;
    }
 }

1.3 准备订阅者

订阅者实现事件处理方法(也称为“订阅者方法”),这些方法将在发布事件时调用。并使用 @Subscribe 注释定义。

// 此方法将在 UI 线程执行
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

// 此方法将在发送事件的线程执行
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
    doSomethingWith(event);
}

订阅者需要在 EventBus 中注册和注销。只有当订阅着注册后,才会接受事件。在 Android 中,activities 和 fragments 通常在他们生命周期执行。例如 onStart/onStop:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

1.4发送事件

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

粘性事件
粘性事件用于有一些场景,比如在已经发送事件后,才会注册订阅者。这时候,就不需要用户自己做消息缓存,只需要使用粘性事件,在注册订阅者时,会自动检查发送事件。

// 发送粘性事件
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));

// 移除粘性事件
MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
if(stickyEvent != null) {
    EventBus.getDefault().removeStickyEvent(stickyEvent);
    // Now do something with it
}

普通事件和粘性事件区别:
如果发布的是普通事件,当前如果没有Subscriber,则后续注册的Subscriber也不会收到该事件。
如果发布的是粘性事件,当前如果没有Subscriber,内部会暂存该事件,当注册Subscriber时,该Subscriber会立刻收到该事件。

二、EventBus 设计思路、源码分析

注册订阅者时,通过反射,把注解了 Subscribe 的方法保存到自己相对的 Map 数据项中;
发送消息时,从 Map 数据项中获取对应类型的订阅者,通过反射方法执行;

注销时,就会把订阅者中缓存到 Map 数据项的数据删除。

图片来源于网络
2.1 构造EventBus
  public static EventBus getDefault() {
   if (defaultInstance == null) {
    synchronized (EventBus.class) {
          if (defaultInstance == null) {
              defaultInstance = new EventBus();
            }
        }
    }
     return defaultInstance;
}

采用双重校验并加锁的单例模式生成EventBus实例。这个单例模式实现并不会有多线程的安全问题。因为对于 defaultInstance 的定义是 volatile 的。

 static volatile EventBus defaultInstance;

EventBus主要成员变量:

 private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
 private final Map<Object, List<Class<?>>> typesBySubscriber;
 private final Map<Class<?>, Object> stickyEvents;

subscriptionsByEventType:以event(即事件类)为key,以订阅列表(Subscription)为value,事件发送之后,在这里寻找订阅者,而Subscription又是一个CopyOnWriteArrayList,这是一个线程安全的容器。
typesBySubscriber:以订阅者类为key,以event事件类为value,在进行register或unregister操作的时候,会操作这个map。
stickyEvents:保存的是粘性事件
Subscription是一个封装类,封装了订阅者、订阅方法这两个类。定义了两个成员变量,

  final Object subscriber;  // 订阅一个事件的对象
  final SubscriberMethod subscriberMethod; // 订阅的具体信息(方法名/ThreadMode/isStrick/priority)

具体代码:

  final class Subscription {
      final Object subscriber;    // 订阅者类
      final SubscriberMethod subscriberMethod;    // 订阅者类对于的方法信息
  }

  public class SubscriberMethod {
      final Method method;    // 订阅者方法
      final ThreadMode threadMode;    // 线程模式
      final Class<?> eventType;   // 订阅的事件类型
      final int priority;
      final boolean sticky;
      /** Used for efficient comparison */
      String methodString;
  }
2.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);
        }
    }
}

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
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);
    }
}

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

这段代码主要执行

  1. findSubscriberMethods 查找该类中所有注解了 Subscribe 的方法,并保存到 List中;
    2.在subscribe 方法中,针对每个订阅方法处理;
    3.把 SubscriberMethod 封装为 Subscription,并添加到 subscriptionsByEventType;
    4.向 typesBySubscriber 添加订阅者方法信息;
    5.检查是否该订阅者方法在粘性事件中有相同的事件类型,如果有则会触发方法通知。
    SubscriberMethodFinder中的findSubscriberMethods方法:

         List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
       // 这里首先从缓存当中尝试去取该订阅者的订阅方法
       List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
       if (subscriberMethods != null) {
           return subscriberMethods;
       }
    
       // 当缓存中没有找到该观察者的订阅方法的时候使用下面的两种方法获取方法信息
       if (ignoreGeneratedIndex) {
           subscriberMethods = findUsingReflection(subscriberClass);
       } else {
           subscriberMethods = findUsingInfo(subscriberClass);
       }
       if (subscriberMethods.isEmpty()) {
           throw new EventBusException(...);
       } else {
           // 将获取到的订阅方法放置到缓存当中
           METHOD_CACHE.put(subscriberClass, subscriberMethods);
           return subscriberMethods;
       }
       }
    

findSubscriberMethods 方法通过反射,获取注解了 Subscribe 的方法,并保存到 SubscriberMethod 里。

静态常量 METHOD_CACHE,中缓存了 findSubscriberMethods 中注册过的类,有效的避免了重复注册过的类,再次通过反射去查找,提高性能

2.2 unregister

代码

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 {
        ...
    }
}

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
        if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

从这个代码可以看出来,unregister就是把订阅者信息从两个map变量中删除。

subscriptionsByEventType key 为 Class 类型,保存有事件类型;value 为 ArrayList 类型(Subscription 是一个保存有订阅者信息的类,我们之后分析)一个事件可以被多个类订阅(但同一个类不能有多个方法同时订阅同一个事件类型)。
typesBySubscriber ,key 为 Class 类型,保存订阅者类;value 为 List 类型,保存每个订阅者订阅的事件类型。

2.3 post

事件发布,

  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;

       ...
    while (!eventQueue.isEmpty()) {
        postSingleEvent(eventQueue.remove(0), postingState);
    }
    ..
   }
  }
  private void postSingleEvent(Object event, PostingThreadState postingState) {
   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);
   }
         ...
 }
  private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
   synchronized (this) {
       subscriptions = subscriptionsByEventType.get(eventClass);
   }
   if (subscriptions != null && !subscriptions.isEmpty()) {
       for (Subscription subscription : subscriptions) {
        postingState.event = event;
        postingState.subscription = subscription;
        boolean aborted = false;

        postToSubscription(subscription, event, postingState.isMainThread);
        ...
    }
    return true;
   }
   return false;
  }
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
 //...
 }    
  void invokeSubscriber(Subscription subscription, Object event) {
}

放到事件队列里,并运行 postSingleEvent 方法执行事件;
postSingleEventForEventType 方法中通过 eventClass 获取所有订阅的方法;
postToSubscription 方法中包含一些线程的切换,代码见下,如果是主线程或者其他线程,会放到线程对应的队列中;
invokeSubscriber 方法中通过反射执行方法。

2.4 指定线程
 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);
    }
}

说明:

  1. POSTING(默认):事件在哪个线程发布,就在哪个线程消费,因此要特别注意不要在UI线程进行耗时的操作,否则会ANR。
  2. MAIN:事件的消费会在UI线程。因此,不宜进行耗时操作,以免引起ANR。
  3. BACKGROUND:如果事件在UI线程产生,那么事件的消费会在单独的子线程中进行。否则,在同一个线程中消费。
  4. ASYNC:不管是否在UI线程产生事件,都会在单独的子线程中消费事件。
    根据当前的线程状态和订阅方法指定的threadMode信息来决定合适触发方法。这里的invokeSubscriber会在当前线程中立即调用反射来触发指定的观察者的订阅方法。否则会根据具体的情况将事件加入到不同的队列中进行处理。这里的mainThreadPoster最终继承自Handler,当调用它的enqueue方法的时候,它会发送一个事件并在它自身的handleMessage方法中从队列中取值并进行处理,从而达到在主线程中分发事件的目的。这里的backgroundPoster实现了Runnable接口,它会在调用enqueue方法的时候,拿到EventBus的ExecutorService实例,并使用它来执行自己。在它的run方法中会从队列中不断取值来进行执行。
2.5 粘性事件

postSticky 函数做了两个事情:

把事件缓存到 stickyEvents 中,再有新的订阅者注册的时候可以检查通知事件;
像 post 普通的事件一样,执行 post 方法

三、问答部分

  1. 在EventBus中,使用@Subscribe注解的时候指定的ThreadMode是如何实现在不同线程间传递数据的?

要求主线程中的事件通过Handler来实现在主线程中执行,非主线程的方法会使用EventBus内部的ExecutorService来执行。实际在触发方法的时候会根据当前线程的状态和订阅方法的ThreadMode指定的线程状态来决定何时触发方法。非主线程的逻辑会在post的时候加入到一个队列中被随后执行。

  1. 使用注解和反射的时候的效率问题,是否会像Guava的EventBus一样有缓存优化?

内部使用了缓存,确切来说就是维护了一些映射的关系。但是它的缓存没有像Guava一样使用软引用之类方式进行优化,即一直是强引用类型的。

  1. 黏性事件是否是通过内部维护了之前发布的数据来实现的,是否使用了缓存?

黏性事件会通过EventBus内部维护的一个事件类型-黏性事件的哈希表存储,当注册一个观察者的时候,如果发现了它内部有黏性事件监听,会执行post类似的逻辑将事件立即发送给该观察者。

参考:1.https://www.cnblogs.com/all88/archive/2016/03/30/5338412.html

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

推荐阅读更多精彩内容