EventBus 3.0使用与源码分析
Posted 小菜鸟yjm
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EventBus 3.0使用与源码分析相关的知识,希望对你有一定的参考价值。
EventBus简介
EventBus is a publish/subscribe event bus optimized for android.
EventBus 是一个基于发布/订阅模式的事件总线。其模型图如下
从图可知,EventBus分为四个角色,消息发布者、事件总线、事件、消息订阅者。消息发布者把Event(消息)post(发送)到事件总线,事件总线维护一个事件队列,把Event发送到Subscriber(消息订阅者),调用其onEvent方法进行相应的处理。
1.特点
- 简化组件之间的通信
- 解耦事件发送者和事件订阅者
- 在Activities,Fragments和后台线程中运行良好
- 避免复杂的容易出错的关联和生命周期的问题
- 使编码更简单
- 性能好
- jar包小(50k,十余个类)
- 被100,000,000+个app广泛使用
- 有事件优先级(优先级高优先发送到订阅者),发送线程模式等高级特性
2.引用
Gradle:
compile 'org.greenrobot:eventbus:3.0.0'
Maven:
<dependency>
<groupId>org.greenrobot</groupId>
<artifactId>eventbus</artifactId>
<version>3.0.0</version>
</dependency>
3.三步即可使用
- 定义一个Event
public class MessageEvent /* Additional fields if needed */
- 准备消息订阅者
注册你的消息订阅者(在Activity的onCreate或者构造函数中)
eventBus.register(this);
声明你的订阅方法
@Subscribe
public void onEvent(AnyEventType event) /* Do something */;
- 发送消息
eventBus.post(event);
demo介绍
整个demo是从老罗的讲解广播机制的demo改过来的,基本思路就是onCreate中启动一个service,service中有一个异步任务做计数操作,然后在onProgressUpdate中post消息到MainActivity进行ui操作.
- 注册订阅事件
//把编译期间解析的订阅方法加载到EventBus的单例中
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
EventBus.getDefault().register(this);
- 订阅方法
//事件的处理会在UI线程中执行
@Subscribe(priority = 1)
public void onEventMainThread(CounterEvent counterEvent)
Log.d("priority", "priority 1");
Log.d("threadMode", "onEventMainThread");
counterText.setText(counterEvent.count + ",onEventMainThread");
@Subscribe(priority = 2)
public void onEventMainThread(CounterEvent2 counterEvent)
Log.d("priority", "priority 2");
counterText2.setText(counterEvent.count + ",onEventMainThread2");
//必须是同一个Event类型,priority才起作用
@Subscribe(threadMode = ThreadMode.POSTING, priority = 100)
public void yongjiaming(CounterEvent counterEvent)
Log.d("priority", "ThreadMode.POSTING");
counterText.setText(counterEvent.count + ",ThreadMode.POSTING");
//事件的处理在和事件的发送在相同的进程
@Subscribe
public void onEvent(CounterEvent counterEvent)
Log.d("threadMode", "onEvent: " + Thread.currentThread().toString() + ",id= " + Thread.currentThread().getId());
counterText.setText(counterEvent.count + ",onEvent2");
@Subscribe
public void onEventBackgroundThread(CounterEvent counterEvent)
Log.d("threadMode", "onEventBackgroundThread: " + counterEvent.count + ", " + Thread.currentThread().toString() + ", " + Thread.currentThread().getId());
@Subscribe
public void onEventBackgroundThread(CounterEvent2 counterEvent)
Log.d("threadMode", "onEventBackgroundThread2: " + counterEvent.count + ", " + Thread.currentThread().toString() + ", " + Thread.currentThread().getId());
@Subscribe(sticky = true)
public void sticky(String str)
Toast.makeText(this,"stickyEvent: " + str, Toast.LENGTH_LONG).show();
3.0版本register只有一个方法,priority,sticky,ThreadMode的定义放在了Subscribe中定义,同时订阅方法名不再强制以OnEvent开头,尽管demo中还是遵循这个规则.细心的同学注意到有个yongjiaming(CounterEvent2 counterEvent),这么写也没有问题,同样可以接收到CounterEvent2的事件.同一个事件有多个订阅事件,那么这几个订阅事件分别都会得到调用,如果有定义优先级,那么优先级高的优先被调用,其它情况按照在订阅者(demo中的mainActivity)的声明顺序.
同时注意到EventBus中有一个黏性事件(sticky event),注册是一样的,post的时候
EventBus.getDefault().postSticky("sticky");
post到eventBus之后会保存在一个以event.class为key的,value为Event的hashMap中,并且这个值会替换更新.当我们想要获取这个event的时候,这样就可以拿到,不用再一次post了
EventBus.getDefault().getStickyEvent(String.class)
具体的细节相信大家看下demo代码就一目了然,这里还要强调一点的是可以进一步提高eventbus的性能,在编译期间就对注解进行解释并保存结果.具体的步骤:
1.在app的build.gradle下添加
apply plugin: 'com.neenbedankt.android-apt'
apt
arguments
eventBusIndex "com.example.eventbus.MyEventBusIndex"
dependencies
compile 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
项目的build.gradle下添加
dependencies
classpath 'com.android.tools.build:gradle:2.1.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
编译后可以看到在build/generated/source/apt/debug/com/example/eventbus/下生成了MyEventBusIndex.java(名字在gradle中定义)
该类保存了订阅方法的信息,直接贴代码
package com.example.eventbus;
import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberMethodInfo;
import org.greenrobot.eventbus.meta.SubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;
import org.greenrobot.eventbus.ThreadMode;
import java.util.HashMap;
import java.util.Map;
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[]
new SubscriberMethodInfo("onEventMainThread", CounterEvent.class, ThreadMode.POSTING, 1, false),
new SubscriberMethodInfo("onEventMainThread", CounterEvent2.class, ThreadMode.POSTING, 2, false),
new SubscriberMethodInfo("yongjiaming", CounterEvent.class, ThreadMode.POSTING, 100, false),
new SubscriberMethodInfo("onEvent", CounterEvent.class),
new SubscriberMethodInfo("onEventBackgroundThread", CounterEvent.class),
new SubscriberMethodInfo("onEventBackgroundThread", CounterEvent2.class),
new SubscriberMethodInfo("sticky", String.class, ThreadMode.POSTING, 0, true),
));
private static void putIndex(SubscriberInfo info)
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass)
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null)
return info;
else
return null;
源码分析(3.0.0版本)
- register流程
/**
* Registers the given subscriber to receive events. Subscribers must call @link #unregister(Object) once they
* are no longer interested in receiving events.
* <p/>
* Subscribers have event handling methods that must be annotated by @link Subscribe.
* The @link Subscribe annotation also allows configuration like @link
* ThreadMode and priority.
*/
public void register(Object subscriber)
//获得订阅者的class对象,对应demo中的MainActivity
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this)
for (SubscriberMethod subscriberMethod : subscriberMethods)
subscribe(subscriber, subscriberMethod);
subscriberMethodFinder顾名思义,功能为查找订阅方法.其成员变量METHOD_CACHE缓存订阅者及其方法
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
下面接着看下findSubscriberMethods方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)
//第一步现在缓存的map中以class为key去查找
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
//如果有该类的订阅方法直接返回
if (subscriberMethods != null)
return subscriberMethods;
//ignoreGeneratedIndex为true时调用findUsingReflection()方法
if (ignoreGeneratedIndex)
subscriberMethods = findUsingReflection(subscriberClass);
else
subscriberMethods = findUsingInfo(subscriberClass);
if (subscriberMethods.isEmpty())
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
else
//把查找结果存储起来
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
接着来看下findUsingReflection和findUsingInfo分别做了哪些事情
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass)
//从一个FindState数组(size=4)中获取第一个不为null的实例,如果均为null直接返回new FindState();
FindState findState = prepareFindState();
//初始化参数,FindState包含订阅者的class,是否遍历父类的方法,
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null)
findUsingReflectionInSingleClass(findState);
//同时会去父类中查找
findState.moveToSuperclass();
return getMethodsAndRelease(findState);
反射获取订阅方法的核心部分
private void findUsingReflectionInSingleClass(FindState findState)
Method[] methods;
try
// This is faster than getMethods, especially when subscribers are fat classes like Activities
//优先调用getDeclaredMethods()获取订阅者的全部method,否则调用getMethods()
methods = findState.clazz.getDeclaredMethods();
catch (Throwable th)
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
for (Method method : methods)
int modifiers = method.getModifiers();
//遍历public并且非abstract和static修饰的方法
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0)
//获取方法的参数
Class<?>[] parameterTypes = method.getParameterTypes();
//只有一个参数
if (parameterTypes.length == 1)
//获取Subscribe注解(包含ThreadMode,prority,sticky)
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null)
//获取第一个参数,也只有一个即Event
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType))
ThreadMode threadMode = subscribeAnnotation.threadMode();
//把注解的信息保存到FindState的subscriberMethods成员变量
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class))
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class))
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
接着看checkAdd()做了哪些操作
boolean checkAdd(Method method, Class<?> eventType)
// 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
// Usually a subscriber doesn't have methods listening to the same event type.
//两重检测,如果在anyMethodByEventType中没有值则直接返回true,anyMethodByEventType以eventType(事件类型)为key,value订阅改event的method的hashmap
Object existing = anyMethodByEventType.put(eventType, method);
if (existing == null)
return true;
else
if (existing instanceof Method)
//
if (!checkAddWithMethodSignature((Method) existing, eventType))
// Paranoia check
throw new IllegalStateException();
// Put any non-Method object to "consume" the existing Method
anyMethodByEventType.put(eventType, this);
return checkAddWithMethodSignature(method, eventType);
好了,到这里我们终于拿到List subscriberMethods,接下来遍历subscriberMethods,
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod)
Class<?> eventType = subscriberMethod.eventType;
//Subscription包含订阅者和订阅方法
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//从subscriptionsByEventType(key:event的class;value:CopyOnWriteArrayList<Subscription>)中get对应的订阅list
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null)
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
else
//如果要订阅的方法已在subscriptions的list中则抛异常
if (subscriptions.contains(newSubscription))
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
int size = subscriptions.size();
//把当前要订阅的methond按照优先级从大到小放入subscriptions
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);
//如果是sticky类型
if (subscriberMethod.sticky)
if (eventInheritance)
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
//Map<Class<?>, Object> stickyEvents(key:event.class,value:最新的Event)
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);
这里特别要强调的是subscriptionsByEventType的成员变量,注册事件和对应的订阅方法都保存在这个hashmap当中,后面post消息的时候同样会从中拿到指定事件的订阅方法.
好了,到这里,订阅的流程就全部结束.
- post过程
发送事件到event bus,一句话搞定,
EventBus.getDefault().post(new Event());
跟进去看post的实现
/** Posts the given event to the event bus. */
public void post(Object event)
//currentPostingThreadState为ThreadLocal<PostingThreadState>类型
//PostingThreadState对象保存事件队列和订阅者的相关信息以及一些状态标志
PostingThreadState postingState = currentPostingThreadState.get();
//往事件队列中添加event
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
//循环从eventQueue中移除并发送event
while (!eventQueue.isEmpty())
postSingleEvent(eventQueue.remove(0), postingState);
finally
postingState.isPosting = false;
postingState.isMainThread = false;
来看看postSingleEvent是怎么发送一个个event的
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance)
//lookupAllEventtypes()遍历eventClass的父类和接口,eg,post(new Event3()), Event3 extends Event,
//那么即便没有订阅Event3类型的,订阅Event类型的方法同样可以接收到Event3这个事件
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
//这里即是遍历lookupAllEventtypes()得到的List<Class>
for (int h = 0; h < countTypes; h++)
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
else
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
post进入到postSingleEventForEventType,根据事件类型来发送
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this)
//这里的subscriptionsByEventType便是上文提到的保存事件类型和订阅方法的hashMap,现在根据eventClass获取
subscriptions = subscriptionsByEventType.get(eventClass);
if (subscriptions != null && !subscriptions.isEmpty())
//遍历所有订阅
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;
真正的调用订阅方法的地方
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将事件入列,mainThreadPoster extends handler,插入队列的同时sendMessage到hanlderMessage处调用invokeSubscriber
mainThreadPoster.enqueue(subscription, event);
break;
case BACKGROUND:
if (isMainThread)
//主线程则加入队列
//backgroundPoster extends Runnable,加入队列的同时eventBus.getExecutorService().execute(this);调用ExecutorService去执行一个backgroundPoster,
//run方法中自然除了出列操作还有invokeSubscriber的调用
backgroundPoster.enqueue(subscription, event);
else
//非主线程直接调用订阅方法
invokeSubscriber(subscription, event);
break;
case ASYNC:
//asyncPoster和backgroundPoster的实现基本一致
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
void invokeSubscriber(Subscription subscription, Object event)
try
//调用事件订阅者的订阅方法
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
catch (InvocationTargetException e)
handleSubscriberException(subscription, event, e.getCause());
catch (IllegalAccessException e)
throw new IllegalStateException("Unexpected exception", e);
这里有必要介绍下eventBus的四种threadMode,posting,main,background,async.
case posting 默认模式. 订阅者和发送事件在同一个线程.推荐这种模式处理简单的不耗时的任务,避免频繁的线程切换.这种模式下事件的处理也有可能在ui主线程.
case MainThread:
首先去判断当前如果是UI线程,则直接调用;否则: mainThreadPoster.enqueue(subscription, event);把当前的方法加入到队列,然后直接通过handler去发送一个消息,在handler的handleMessage中,去执行我们的方法。说白了就是通过Handler去发送消息,然后执行的。
case BackgroundThread:
如果当前非UI线程,则直接调用;如果是UI线程,则将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用
executorService = Executors.newCachedThreadPool();。
case Async:将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用;线程池与BackgroundThread用的是同一个。
BackgroundThread和Async的区别:
BackgroundThread中的任务,一个接着一个去调用
Async则会动态控制并发.
推荐两篇eventbus 3.0的文章,图文并茂,十分不错
老司机教你 “飙” EventBus 3
EventBus 3.0 源码分析
demo下载
以上是关于EventBus 3.0使用与源码分析的主要内容,如果未能解决你的问题,请参考以下文章