EventBus是一个应用于Android和Java的松耦合的“主题/订阅”模式的开源库。它能够依赖几行简单的代码解耦总线通信,删除依赖、提高开发效率。
EventBus有三个主要部分:
1. Event事件: 任意类型的类对象都可以;
2. Subscriber 事件订阅者: 事件订阅者具体处理它锁订阅的事件类型的事件的处理。在老版本的EventBus中事件订阅的处理方法必须是onEvent、onEventMainThread、onEventBackgroundThread、onEventAsync方法来代表不同的事件处理运行的线程模型。在新版本中事件处理方法名称可以是任何合法方法名,只需要在事件处理方法上添加注解@Subscriber,并且在注解中执行处理方法运行的线程模型。
3. Publisher 事件发布者:凡是通过EventBus对象的post(xxx)发送了事件的对象都是发布者。可以在任何线程中发布,发送的事件会自动分发给想用的事件类型的订阅者去处理。
EventBus–>ThreadMode介绍:
* POSTING:订阅者的事件处理函数的线程模型在POSTING模式下,事件是在那个线程下发送的就会在那个线程下执行;需要注意在Android的主线程发送事件时处理的函数的耗时操作的问题,避免出现ANR;
* MAIN:事件处理函数总是运行在主线程,注意ANR问题。
* MAIN_ORDERD:他和MAIN模式一样,处理函数都是运行在UI trhead中。不同之处在于这种模式下的事件总是排队交付,不会阻索post()的事件发送。
* BACKGROUND:如果事件发送实在主线程中进程的,处理函数会在新的工作线程中运行;如果发送事件实在工作进程中发出的,处理函数将在于发送事件相同的工作线程中运行。
* ASYNC:总是在新的工作线程中运行。
具体关于EventBus的介绍请去官网详细了解。
EventBus的使用方法较为简单在此不再做具体的介绍,直接进入源码解析。
EventBus类是EventBus框架的核心类中的核心,它集框架的配置信息、事件订阅、解除、和发送等功能于一体。在框架使用过程中我们经常使用EventBus.getDefault()的静态方法获取一个EventBus实例,也可以通过EventBus.builder()来构建一个EventBus对象。
/** Convenience singleton for apps using a process-wide EventBus instance. */
//双重检查DCL单例模式
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;
}
通过单例模式我们可以得到一个对象,这样不仅简化了使用方法同所有使用这个对象进行事件的订阅和发送都是使用这同一个事件总线模型。接下来我们看一下EventBus的构造函数的源码。
public EventBus() {
this(DEFAULT_BUILDER);
}
EventBus(EventBusBuilder builder) {
logger = builder.getLogger();
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
logSubscriberExceptions = builder.logSubscriberExceptions;
logNoSubscriberMessages = builder.logNoSubscriberMessages;
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}
我们从源码中可以看到EventBus的构造方法是通过一个EventBusBuiler的实例作为参数来构建对象的。这里使用了Builer构建者模式进行的复杂对象的灵活构建。其次我们还发现这里的构造函数是public的,和我们常用的单例模式把构造函数设置为private不同。主要做的好处时我们可以通过构造函数去创建不同的EventBus的对象,不同的EventBus对象代表着不同的事件总线。这说明我们可以在一个应用程序中使用不同的事件总线。
通过构造方法创建EventBus实例可以根据自己的使用场景配置不同的参数,例子:
eventBus = EventBus.builder().eventInheritance(params.isEventInheritance()).addIndex(new MyEventBusIndex())
.ignoreGeneratedIndex(params.isIgnoreGeneratedIndex()).build();
获取EventBus对象后,就是可以将需要处理事件的订阅者类注册到EventBus中,我们常用EventBus.getDefault().register(this)方法把当前类对象注册到EventBus中。下面看一下register():
/**
* 注册订阅者接收事件。一但订阅者不在对接收的事件感兴趣必须解除注册。
*/
public void register(Object subscriber) {
Class> subscriberClass = subscriber.getClass();
//查找指定订阅者类的事件处理方法 1
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//绑定事件处理方法 2
subscribe(subscriber, subscriberMethod);
}
}
}
在上边代码注释1处是查找指定订阅者说有的事件处理方法集合,然后将这些事件处理方法(在订阅者中所有对任何类型事件感兴趣的方法)通过subscriber(subscriber, subscriberMethed)进行注册。register()方法相对简单,第一找到所有事件处理的方法,然后通过subscribe()将这些方法绑定。
注释1这里有一个重要的类开始进入我们的视线,他就是subscriberMethodFinder变量的类。这个成员变量在EventBus对象的构造函数中进行了初始化,那我们看看它是如果通过findSubscriberMethods(SubscriberClass subscriberClass)查找对事件感兴趣的方法的:
List findSubscriberMethods(Class> subscriberClass) {
//从缓存中查找此类的事件方法集合 1
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
/**
* ignoreGeneratedIndex 表示是否忽略注解生成器生成的MyEventBusIndex, 默认值是false
*
* MyEventBusIndex的生成: http://greenbot.org/eventbus/documentation/subscriber-index/
*/
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 {
//将找到的方法添加到缓冲中(ConcurrentHashMap)
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
findSubscriberMethods方法首先从缓存中查找此订阅者的事件处理方法。如果缓存中有相关集合将直接返回,退出此方法。在第一次注册此订阅者时,缓存中无法找到,返回null。将根据ignoreGenerationIndex的值判读使用那种方式查找,ignoreGenerationIndex默认值为false,将调用findusingInfo()。如果订阅者中没有任何方法对EventBus的事件(Event)感兴趣将会有异常抛出。如果有事件处理方法,将将这些方法加入缓存后返回。
下面我们看看findUsingInfo()方法都干了哪些事情:
/**
* 最终找出subscriberClass中的对事件感兴趣的方法
* @param subscriberClass
* @return
*/
private List findUsingInfo(Class> subscriberClass) {
//准备查找状态
FindState findState = prepareFindState();
//指定订阅者类
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//默认实现中subscriberInfo是null 1
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
//通过反射的方式找到观察者的信息
findUsingReflectionInSingleClass(findState);
}
/**
* 移动到父类中查找事件处理方法 最终会跳出循环
*/
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
从findusingInfo()方法可以看出首先创建一个FindState类型对象用于记录查找事件处理方法的结果和状态。FindState对象的结构很重要我们一会看看FindState的结构,看看它是如何记录结果的。在EventBus中查找订阅者中的处理方法是支持查找订阅者的父类中的事件处理方法的,所以一个while循环不断循环查找。在注释1处getSubscriberInfo(FindState findState)的默认实现中是返回值是null,因此方法调用逻辑会转移到findUsingReflectionSingleClass()方法中,通过反射机制查找订阅者类中的事件处理方法。在当前订阅者类中查找完成后会通过findState.moveToSuperclass()方法将查找转移到父类中进行,直到查找结束。在方法查找结束后会释放findState对象的状态。
下面我们具体看一下EventBus是如何通过反射查找事件处理方法的。findUsingRefectionSingleClass()源码如下:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
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的方法
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
//获取方法的参数类型
Class>[] parameterTypes = method.getParameterTypes();
//订阅者事件处理方法只能有一个参数
if (parameterTypes.length == 1) {
//查看方法的注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class> eventType = parameterTypes[0];
//对方法进行检查
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
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");
}
}
}
查找过程就是通过反射机制查找方法结构,并结合@Subscriber的注解进行方法的查找和一定规则的检查方法是否符合要求。查找处理的方法信息被封装在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;
}
在方法查找结束后我们需要将视线重新回到EventBus的regsiter()方法中。在方法查找结束会通过加锁排他的遍历事件处理方法列表,并通过调用subscribe(subscriber, subscriberMethod);将方法进行绑定。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class> eventType = subscriberMethod.eventType;
//将订阅者类和类中的方法组装程一个Subscription类对象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//查找此事件处理方法所关注的事件类型,然后去查找EventBus中被事件类型的事件的所有订阅方法
CopyOnWriteArrayList 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> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//将新注册的事件类型(Event Type)添加进去
subscribedEvents.add(eventType);
/**
* 判断一下是不是粘性事件
*/
if (subscriberMethod.sticky) {
//粘性事件的处理暂时不做介绍
...........
}
}
在做事件绑定前现将订阅者和事件处理方法组装成一个Subscription对象,然后根据事件类型去获取已经订阅了此事件类型事件的所有处理方法。如果没有没有找到就创建一个方法集合放入到EventBus中,并将Subscription对象根据事件处理方法的优先级排序放入其中放入其中。且方法不能重复放入,否则会抛出异常。同时会记录此订阅者类都订阅了哪些事件类型。最后是粘性事件的处理,在此先不谈。在subscribe()方法中涉及到EventBus类的几个重要的结合,我们下面看一下这几个结合都是干什么的;
public class EventBus {
private static final Map, List>> eventTypesCache = new HashMap<>();
//event Type为key, 以和订阅了这个event的所有订阅者集合为value
private final Map, CopyOnWriteArrayList> subscriptionsByEventType;
//以Subscriber为key, 所订阅的事件集合为value
private final Map
注在整个订阅者注册的过程中主要涉及的主要类有EventBus、SubscriberMethodFinder、FindState等类。
在订阅者不再对订阅事件感兴趣时需要解除绑定,避免出现不必要的事件处理造成逻辑上的错误和内存泄漏。比如以Activity对象作为订阅者在activity退出时如果不取消订阅有可能造成内存泄漏。
常用的取消订阅的方式是EventBus.getDefault().unregister()来需要订阅者绑定。
/** Unregisters the given subscriber from all event classes.
* 取消事件订阅
* */
public synchronized void unregister(Object subscriber) {
//订阅的事件类型集合
List> 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());
}
}
方法逻辑较为简单,通过订阅者获取到此订阅者所感兴趣的事件类型集合。然后遍历集合中的事件类型并调用unsubscriberByEventType(subscriber, eventType)解除绑定。同时将订阅者自己从typesBySubscriber集合中删除。 现在看一下unsubscribeByEventType方法:
private void unsubscribeByEventType(Object subscriber, Class> eventType) {
//找到eventType的所有订阅者
List 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--;
}
}
}
}
通过事件类型查找处所有对此事件类型感兴趣的方法,然后在重其中找出属于subscriber对象的方法。并将这些方法从记录中删除。这样就完成了订阅者的解绑。
下面我们看一下发送事件的处理逻辑。
EventBus.geDefault().post(new XxxEvent());
具体看一下EventBus的post()方法;
public void post(Object event) {
//将事件添加到当前线程的事件队列中
PostingThreadState postingState = currentPostingThreadState.get();
List
事件发送的逻辑比较简单,首先从ThreadLocal类型对象中获取一个和post()所在线程绑定的PostingThreadState类型的对象。ThreadLocal是用户对象和线程绑定的,每个线程只能访问和自己绑定的对象。因此对绑定对象的访问是线程安全的。
/** For ThreadLocal, much faster to set (and get multiple values). */
final static class PostingThreadState {
final List
可以看出PostingThreadState中有事件队列、发送状态、线程标示、订阅者等信息。我们接着回到post()方法中,在获取了postingState对象后将事件添加到队列中。同时通过判断事件发送状态,如果未处在发送状态则启动一个while (!eventQueue.isEmpty())不断的从对了中获取Event事件调用postSingleEvent( Object )将事件发送出去。如果事件发送已经开启,将事件放入队列后就会自动发送,在事件发送完成后在finally块中恢复初始状态。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class> eventClass = event.getClass();
boolean subscriptionFound = false;
//eventInheritance 默认是true
if (eventInheritance) {
//查找该事件的所有父类
List> 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);
}
if (!subscriptionFound) {
//默认为ture
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
//发送没有订阅者的事件
post(new NoSubscriberEvent(this, event));
}
}
}
在EventBus的默认配置中eventInheritance 为true,所以首先调用lookupAllEventTypes()方法查找笨事件类型的父类和接口。然后遍历事件类型集合调用postSingleEventForEventType(event, postingState, eventClass)发送事件。
/**
* 根据事件类型发送事件
*/
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class> eventClass) {
CopyOnWriteArrayList 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;
try {
//TODO 发送到订阅者方法中
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;
}
本方法逻辑清晰,首先根据事件类型查找订阅这些事件的处理方法,然后调用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) {
//Runable的子类
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);
}
}
postToSubscription方法首先说去事件处理方法的线程模型,然后选择具体的方法处理所在线程的状态。在线程选择完成后都会调用invokeSubscriber(xxx,xxx)方法通过反射调用的方式调用事件处理方法。
//调用订阅者方法
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);
}
}
PPSTING 线程模式下:程序会调用到invokeSubscriber(subscription, event);此时事件处理方法和事件发送方法在同一个线程中执行。
MAIN线程模式下:如果发送事件不是在主进程中会调用mainThreadPoster.enqueue(subscription, event);执行事件。mainThreadPoster其实是一个Handler的对象。
上边已经看了各种XXXPoster的调用,下面看一看他们的具体实现。
mainThreadPoster的类型是HandlerPoster,继承自Handler:
public class HandlerPoster extends Handler implements Poster {
//消息队列
private final PendingPostQueue queue;
//可执行的最大时间段 方法执行超过这个事件,禁止继续发送事件
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
....................
}
PendingPostQueue是一个队列(链表),其中存储组装了Subscription和Event的PendingPost类型的对象。通过调用HandlerPsoter.enqueue()方法进行消息入队。
public void enqueue(Subscription subscription, Object event) {
//构建对象
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
//入队
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
//发送消息
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
在消息入队后会调用sendMessage(obtainMessage())发送消息。在消息发送后会自动调用handleMessage()方法。
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
//起始时间
long started = SystemClock.uptimeMillis();
while (true) {
//从队列获取PendingPost 消息
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
//调用事件处理方法
eventBus.invokeSubscriber(pendingPost);
//方法调用耗时
long timeInMethod = SystemClock.uptimeMillis() - started;
//超过最大执行事件
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
handleMessage()不断从消息队列中取消息进行方法调用。当方法执行时间超过最大值时,要求新入队事件不要发送消息过来,直到效力队列中的事件被执行完毕。
两个Poster差别不大,都是实现了Runnable接口。同时都是在同一个线程池中进行执行。内部实现和和HandlerPoster一样都有一个PendingPostQueue来存储事件。
final class BackgroundPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
}
具体的事件处理方法调用在run()方法的内部:
@Override
public void run() {
try {
try {
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}