2月5号,greenrobot发布了EventBus3.0,3.0中引入了注解,我们可以通过@Subscribe注解,来标识一个事件处理方法。事件处理方法的命名更加灵活,且通过注解我们可以设定方法的执行优先级,执行线程等。相比以前的版本方便很多。接下来我将对EventBus3.0的源码作简要分析,使读者可以了解其工作机制并能从中学习一些编程技巧。
tips:图看不清楚可右键新标签页打开或保存到本地再看。
由类图可以看出:
知道这些关键词和具体实例的对应关系对接下来的源码分析有帮助。
- 订阅者;
- 订阅方法;
- 订阅事件;
- 订阅。
//此处SubscriberActivity为<订阅者>,<订阅>维持着<订阅者>和<订阅方法>之间的关系
public class SubscriberActivity extends BaseActivity {
EventBus eventBus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
eventBus = EventBus.getDefault();
//注册订阅者
eventBus.register(this);
}
@Override
protected void onDestroy() {
//取消订阅者注册
eventBus.unregister(this);
super.onDestroy();
}
//订阅者中由@Subscribe注解,且只有一个参数的方法为<订阅方法>,event为<订阅事件>
//threadModel,设定<订阅方法>的执行线程,POSTING-post线程,MAIN-主线程,BACKGROUND-后台线程(相对于主线程的其他线程),ASYNC-异步执行,运行于不同于post线程的新线程
//priority,方法执行优先级,值越大,当TestEvent类型的事件被post出来后,该订阅方法的调用时机就越早。
@Subscribe(threadMode = ThreadMode.MAIN,priority = 1)
public void subscribeInMainThread(TestEvent event) {
System.out.println("main()--" + Thread.currentThread().getName() + event.postThreadInfo);
}
}
欲post的事件队列,节点类型为PendingPost,通过链表实现,每个方法(poll,pop等)都是同步方法,PendingPost保存着某次post的事件和订阅。
订阅。保存着订阅者和订阅方法的对应关系。
表示一个订阅方法的所有信息,包括method,threadModel,priority,eventType等。订阅方法最终通过method.invoke反射调用。
工具类,根据订阅者的类型,查找订阅者的所有订阅方法,并加入缓存。
Subscription
(表示订阅信息)的List,List的具体类型为CopyOnWriteArrayList
,它是一个线程安全的可以随机读写的基于数组实现的List。subscriptionsByEventType维持着事件类型到该事件类型所有订阅的映射关系。HandlerPoster
(图中2)继承于Handler,post事件到PendingPostQueue,使相应订阅方法执行于UI线程。Runnable
,post事件到PendingPostQueue,使相应订阅方法运行于后台线程(相对于UI线程)Runnable
,post事件到PendingPostQueue,使相应订阅方法运行于新的线程。返回EventBus的全局单例defaultInstance,getDeafult
的方法如下:
static volatile EventBus defaultInstance;
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
此方法采用了单例模式中的double-check机制,同时defaultInstance采用了volatile关键字修饰,以保证defaultInstance读写的原子性,以应对多线程,多处理器环境中的各种问题,我们的单例如果采用了延迟初始化且运行在多线程环境中,应该使用这种写法,可参考http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html,这篇文章讲的非常深入。
注册一个订阅者。
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
//通过SubscriberMethodFinder查询订阅者的所有订阅方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
//使用同步块,保证一个时候只有一个线程在进行订阅
synchronized (this) {
//遍历一个订阅者的所有订阅方法,进行订阅
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
寻找订阅方法:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//METHOD_CACHE缓存之前已经查找过的订阅方法,进来先从缓存查询
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("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
//把查找到的订阅方法加入缓存,下次订阅时直接从缓存拿订阅方法
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
订阅:
/** *@param subscriber 订阅者 *@param 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) {
//如果该事件类型还没有任何订阅,初始化一个该事件类型的订阅列表,并加入到subscriptionsByEventType中
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//如果订阅列表已经包含了该订阅,则抛出异常(重复注册),contains方法的返回者取决于Subscription的equals方法的实现
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++) {
//插入新订阅。顺序遍历订阅列表,将新订阅的订阅方法的优先级和订阅列表中订阅的订阅方法的优先级进行比较,优先级越高越靠前,这就是@Subscriber注解中设置priority的作用,priority的默认值为0。插入后,跳出循环
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的,找到最新的一个stickyEvent,自动post出去
if (subscriberMethod.sticky) {
//开启了事件继承,eventType的子类型也会post出去
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>).
//遍历stickyEvents,找到eventType类型及子类型的event,post出去
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
//如果candidateEvent可以向上转型为eventType,将candidateEvent也post出去
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
//stickyEvents保存着先前系统post过的stickyEvent
Object stickyEvent = stickyEvents.get(eventType);
//post stickyEvent
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
执行了register后,EventBus利用SubscriberMethodFinder检查了订阅者是否有订阅方法,完成了订阅者的所有订阅信息的收集(存储于subscriptionsByEventType,typesBySubscriber),待post事件时使用。subsrcibe时使用了同步块加锁,保证读写的同步。由代码可以看出订阅者不能被重复注册,否则会抛出异常。
假设一个订阅者对应n个订阅方法,通过类似矩阵数乘的方式会生成了n个订阅。这n个订阅会根据事件类型分类,并放入相应的Subscription list中
每次执行subscribe以后,subscriptionsByEventType中添加了一个事件的订阅(当订阅不存在的时候),typesBySubscriber中添加了一个事件,如果subscriberMethod为sticky的,且EventBus实例保存有eventType类型的stickyEvent,将会立即post出去。
一个订阅者可以订阅多种事件类型(取决于订阅方法的参数类型),一种事件类型可以对应于多个订阅,也可以对应多个订阅者,同时一个订阅者可以有多个订阅,订阅的个数取决于订阅方法数。订阅的事件类型数取决于所有订阅方法的参数类型数。
设某一订阅者的订阅方法数为m,订阅的事件类型数为n,则订阅数为m.且n<=m,n种事件类型中每种事件类型的和为m.
Note: register时,sticky事件的自动post过程不同于postSticky()/post()的过程,sticky事件的自动post过程中,如果设置了eventInheritance = true,stickyEvents中的所有相关事件均会post出去,也就是一个订阅方法可能收到多个事件。而postSticky()/post()的过程中一个订阅方法只能收到一个事件,如果设置了eventInheritance =true,那么将可能有多个订阅方法收到同一个事件。
Useful API:
public void post(Object event) {
//获取当前线程的PostingThreadState
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
//将新post的事件加入到队列中等待post
eventQueue.add(event);
//如果当前线程没有在post,执行post代码,否则返回,同一线程新post的事件将会被缓存在eventQueue中,等待执行。
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,取出事件,并post
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);//see 2.2
}
} finally {
//最后还原状态
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
post方法因使用了ThreadLocal保存事件队列等属性,故不需要采用同步操作。
Note: register时,sticky事件的自动post过程不同于postSticky()/post()的过程,sticky事件的自动post过程中,如果设置了eventInheritance = true,stickyEvents中的所有相关事件均会post出去,也就是一个订阅方法可能收到多个事件。而postSticky()/post()的过程中一个订阅方法只能收到一个事件,如果设置了eventInheritance = true,那么将可能有多个订阅方法收到同一个事件。
2.2.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++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//只寻找当前post事件类型的所有订阅,并执行相应订阅方法,不管该事件超类型的订阅
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);//see 2.3
}
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));
}
}
}
2.3.postSingleEventForEventType()
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
//公有资源使用访问保护,加锁
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
//遍历该事件类型的订阅,postToSubscription
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
//事件是可以取消的,但是得在订阅者的事件处理方法中进行,取消后事件将不再传递
boolean aborted = false;
try {
//将事件post到订阅者
postToSubscription(subscription, event, postingState.isMainThread);//see 2.4
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
2.4.postToSubscription()
根据threadMode,分发订阅,到不同线程中执行订阅方法
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
//threadMode为POSTING,在posting线程中直接执行
invokeSubscriber(subscription, event);//see 2.5
break;
case MAIN:
//threadMode为MAIN
if (isMainThread) {
//如果post线程为主线程,直接执行
invokeSubscriber(subscription, event);
} else {
//否则使用mainThreadPoster post到主线程中执行
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
//threadMode为BACKGROUND
if (isMainThread) {
//如果post线程为主线程,新开线程执行
backgroundPoster.enqueue(subscription, event);
} else {
//否则,直接在当前线程执行
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
//threadMode为ASYNC,总是在新线程中执行
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
2.5.invokeSubscriber()
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);
}
}
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
//获得订阅者的所有订阅事件类型
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
//遍历取消该事件订阅
unsubscribeByEventType(subscriber, eventType);//see 3.2
}
//删除订阅者订阅的所有事件
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
3.2.unsubscribeByEventType()
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
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);
//缩小索引和size
i--;
size--;
}
}
}
}
三种Poster:
HandlerPoster
:继承自Handler
,post事件到主线程,在handleMessage中调用订阅方法。
将新post的事件加入队列,通过发消息的方式通知handler处理此事件,方法的调用线程是非UI线程,参考2.4.postToSubscription()
,因此需要使用同步块去操作资源;
void enqueue(Subscription subscription, Object event) {
//创建一个PendingPost节点
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
//入队
queue.enqueue(pendingPost);
//如果handler处于活动状态,入队后,pendingPost等待执行,handleMessage会循环取出pendingPost执行
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
循环从PendingPostQueue中取出队头并调用Subascriber的方法,
该方法具有超时机制,当事件处理函数处理时间超过16ms时,重新发送消息(排队),给UI线程的其他操作腾出CPU,待下次重新post事件。
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
//double check 机制
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
//没有事件后,跳出循环,节省cpu资源
handlerActive = false;
return;
}
}
}
//调用订阅方法
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
//超时时,重发消息,重新排队,腾cpu给其他UI操作,避免ANR问题
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
BackgroundPoster
:继承于Runnbale,在run方法中调用订阅方法
操作基本同HandlerPoster,只是这里通过ExecutorService异步执行订阅方法
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
}
循环从队列中取出PendingPost,并调用订阅方法
public void run() {
try {
try {
while (true) {
PendingPost pendingPost = queue.poll(1000);
//double check 机制
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
//没有事件后,跳出循环,节省cpu资源
executorRunning = false;
return;
}
}
}
//调用订阅方法
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
AsyncPoster
:继承于Runnbale,在run方法中调用订阅方法,PendingPostQueue的每个方法都是同步方法
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);//同步的
queue.enqueue(pendingPost);//同步的
eventBus.getExecutorService().execute(this);
}
run方法的执行次数取决于queue的大小,run可能运行于多线程环境
public void run() {
PendingPost pendingPost = queue.poll();//同步的
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}
HandlerPost,BackgroundPoster非常类似,都使用了标志位,死循环,使用同步块和double check保证标志位操作的原子性,通过队列保证每一个任务都能按时间、入队顺序执行,不会造成新提交任务遗失(if active then enqueue and wait),且最大程度减少一些方法的调用。区别是前者的代码执行是在UI线程,后者则是非线程,前者由于工作在UI线程,故有超时机制—超过一定时间重新排队(sendMessage),腾CPU给其他UI线程操作,避免ANR
AsyncPoster能保证所有任务的执行,但不能保证执行顺序。
通过学习EventBus3.0源码,对多线程下的单例模式有了新的认识,double-check机制,可以保证对象的正确初始化;另外使用CopyOnWirteArrayList可以进行线程安全地读写,使用队列机制可以有序地执行多个任务且能不断动态地加入新任务;使用缓存机制可以提高查询性能。使用ThreadLocal可以保存线程局部变量(线程专有资源),在多线程环境下非常有用,可以免去同步操作;同时ThreadLocal对多个对象属性值的读写速度较快,如果保存的状态有多个属性值,可以采用ThreadLocal。EventBus是通过反射方法来执行订阅方法的,Java反射在移动端的性能并不好,所以不应该滥用EventBus,,另外在订阅者不需要处理订阅方法时,应取消订阅(unregister),否则可能导致内存泄露的问题。stickyEvent是通过postSticky()方法post的事件,stickyEvent会被保存在内存中,当遇到订阅者订阅时,可以自动post最新的stickyEvent到订阅者中,前提是订阅者要有相匹配的订阅方法。使用stickyEvent可以做数据缓存或者跨页面传参。