Skip to content

Commit 378c086

Browse files
committed
added EventBusBuilder
1 parent a3b7d5d commit 378c086

File tree

9 files changed

+181
-81
lines changed

9 files changed

+181
-81
lines changed

EventBus/src/de/greenrobot/event/AsyncPoster.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
class AsyncPoster implements Runnable {
2525

2626
private final PendingPostQueue queue;
27-
2827
private final EventBus eventBus;
2928

3029
AsyncPoster(EventBus eventBus) {
@@ -35,7 +34,7 @@ class AsyncPoster implements Runnable {
3534
public void enqueue(Subscription subscription, Object event) {
3635
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
3736
queue.enqueue(pendingPost);
38-
EventBus.executorService.execute(this);
37+
eventBus.getExecutorService().execute(this);
3938
}
4039

4140
@Override

EventBus/src/de/greenrobot/event/BackgroundPoster.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
final class BackgroundPoster implements Runnable {
2626

2727
private final PendingPostQueue queue;
28-
private volatile boolean executorRunning;
29-
3028
private final EventBus eventBus;
3129

30+
private volatile boolean executorRunning;
31+
3232
BackgroundPoster(EventBus eventBus) {
3333
this.eventBus = eventBus;
3434
queue = new PendingPostQueue();
@@ -40,7 +40,7 @@ public void enqueue(Subscription subscription, Object event) {
4040
queue.enqueue(pendingPost);
4141
if (!executorRunning) {
4242
executorRunning = true;
43-
EventBus.executorService.execute(this);
43+
eventBus.getExecutorService().execute(this);
4444
}
4545
}
4646
}

EventBus/src/de/greenrobot/event/EventBus.java

Lines changed: 45 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package de.greenrobot.event;
1717

18+
import android.os.Looper;
19+
import android.util.Log;
20+
1821
import java.lang.reflect.InvocationTargetException;
1922
import java.util.ArrayList;
2023
import java.util.HashMap;
@@ -23,28 +26,24 @@
2326
import java.util.concurrent.ConcurrentHashMap;
2427
import java.util.concurrent.CopyOnWriteArrayList;
2528
import java.util.concurrent.ExecutorService;
26-
import java.util.concurrent.Executors;
27-
28-
import android.os.Looper;
29-
import android.util.Log;
3029

3130
/**
32-
* EventBus is a central publish/subscribe event system for Android. Events are posted ({@link #post(Object)} to the
33-
* bus, which delivers it to subscribers that have matching handler methods for the event type. To receive events,
34-
* subscribers must register themselves to the bus using the {@link #register(Object)} method. Once registered,
35-
* subscribers receive events until the call of {@link #unregister(Object)}. By convention, event handling methods must
31+
* EventBus is a central publish/subscribe event system for Android. Events are posted ({@link #post(Object)}) to the
32+
* bus, which delivers it to subscribers that have a matching handler method for the event type. To receive events,
33+
* subscribers must register themselves to the bus using {@link #register(Object)}. Once registered,
34+
* subscribers receive events until {@link #unregister(Object)} is called. By convention, event handling methods must
3635
* be named "onEvent", be public, return nothing (void), and have exactly one parameter (the event).
3736
*
3837
* @author Markus Junginger, greenrobot
3938
*/
4039
public class EventBus {
41-
static ExecutorService executorService = Executors.newCachedThreadPool();
4240

4341
/** Log tag, apps may override it. */
4442
public static String TAG = "Event";
4543

46-
private static volatile EventBus defaultInstance;
44+
static volatile EventBus defaultInstance;
4745

46+
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
4847
private static final String DEFAULT_METHOD_NAME = "onEvent";
4948
private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<Class<?>, List<Class<?>>>();
5049

@@ -64,10 +63,13 @@ protected PostingThreadState initialValue() {
6463
private final BackgroundPoster backgroundPoster;
6564
private final AsyncPoster asyncPoster;
6665
private final SubscriberMethodFinder subscriberMethodFinder;
66+
private final ExecutorService executorService;
6767

68-
private boolean subscribed;
69-
private boolean logSubscriberExceptions;
70-
private boolean logNoSubscriberMessages;
68+
private final boolean failFast;
69+
private final boolean logSubscriberExceptions;
70+
private final boolean logNoSubscriberMessages;
71+
private final boolean sendSubscriberExceptionEvent;
72+
private final boolean sendNoSubscriberEvent;
7173

7274
/** Convenience singleton for apps using a process-wide EventBus instance. */
7375
public static EventBus getDefault() {
@@ -81,65 +83,40 @@ public static EventBus getDefault() {
8183
return defaultInstance;
8284
}
8385

86+
public static EventBusBuilder builder() {
87+
return new EventBusBuilder();
88+
}
89+
8490
/** For unit test primarily. */
8591
public static void clearCaches() {
8692
SubscriberMethodFinder.clearCaches();
8793
eventTypesCache.clear();
8894
}
8995

90-
/**
91-
* Method name verification is done for methods starting with onEvent to avoid typos; using this method you can
92-
* exclude subscriber classes from this check. Also disables checks for method modifiers (public, not static nor
93-
* abstract).
94-
*/
95-
public static void skipMethodVerificationFor(Class<?> clazz) {
96-
SubscriberMethodFinder.skipMethodVerificationFor(clazz);
97-
}
98-
99-
/** For unit test primarily. */
100-
public static void clearSkipMethodNameVerifications() {
101-
SubscriberMethodFinder.clearSkipMethodVerifications();
102-
}
103-
10496
/**
10597
* Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
10698
* central bus, consider {@link #getDefault()}.
10799
*/
108100
public EventBus() {
101+
this(DEFAULT_BUILDER);
102+
}
103+
104+
EventBus(EventBusBuilder builder) {
109105
subscriptionsByEventType = new HashMap<Class<?>, CopyOnWriteArrayList<Subscription>>();
110106
typesBySubscriber = new HashMap<Object, List<Class<?>>>();
111107
stickyEvents = new ConcurrentHashMap<Class<?>, Object>();
112108
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
113109
backgroundPoster = new BackgroundPoster(this);
114110
asyncPoster = new AsyncPoster(this);
115-
subscriberMethodFinder = new SubscriberMethodFinder();
116-
logSubscriberExceptions = true;
117-
logNoSubscriberMessages = true;
111+
subscriberMethodFinder = new SubscriberMethodFinder(builder.skipMethodVerificationForClasses);
112+
logSubscriberExceptions = builder.logSubscriberExceptions;
113+
logNoSubscriberMessages = builder.logNoSubscriberMessages;
114+
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
115+
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
116+
failFast = builder.failFast;
117+
executorService = builder.executorService;
118118
}
119119

120-
/**
121-
* Before registering any subscribers, use this method to configure if EventBus should log exceptions thrown by
122-
* subscribers (default: true).
123-
*/
124-
public void configureLogSubscriberExceptions(boolean logSubscriberExceptions) {
125-
checkConfigurationAllowed();
126-
this.logSubscriberExceptions = logSubscriberExceptions;
127-
}
128-
129-
/**
130-
* Configure if EventBus should log "No subscribers registered for event" messages (default: true).
131-
*/
132-
public void configureLogNoSubscriberMessages(boolean logNoSubscriberMessages) {
133-
checkConfigurationAllowed();
134-
this.logNoSubscriberMessages = logNoSubscriberMessages;
135-
}
136-
137-
// TODO maybe we should switch to a builder pattern to avoid this
138-
private void checkConfigurationAllowed() throws EventBusException {
139-
if (subscribed) {
140-
throw new EventBusException("This method must be called before any registration");
141-
}
142-
}
143120

144121
/**
145122
* Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
@@ -258,7 +235,6 @@ private synchronized void register(Object subscriber, String methodName, boolean
258235

259236
// Must be called in synchronized block
260237
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
261-
subscribed = true;
262238
Class<?> eventType = subscriberMethod.eventType;
263239
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
264240
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
@@ -502,7 +478,8 @@ private void postSingleEvent(Object event, PostingThreadState postingState) thro
502478
if (logNoSubscriberMessages) {
503479
Log.d(TAG, "No subscribers registered for event " + eventClass);
504480
}
505-
if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {
481+
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
482+
eventClass != SubscriberExceptionEvent.class) {
506483
post(new NoSubscriberEvent(this, event));
507484
}
508485
}
@@ -578,7 +555,7 @@ void invokeSubscriber(PendingPost pendingPost) {
578555
}
579556
}
580557

581-
void invokeSubscriber(Subscription subscription, Object event) throws Error {
558+
void invokeSubscriber(Subscription subscription, Object event) {
582559
try {
583560
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
584561
} catch (InvocationTargetException e) {
@@ -591,13 +568,18 @@ void invokeSubscriber(Subscription subscription, Object event) throws Error {
591568
Log.e(TAG, "Initial event " + exEvent.causingEvent + " caused exception in "
592569
+ exEvent.causingSubscriber, exEvent.throwable);
593570
} else {
571+
if (failFast) {
572+
throw new EventBusException("Invoking subscriber failed", cause);
573+
}
594574
if (logSubscriberExceptions) {
595575
Log.e(TAG, "Could not dispatch event: " + event.getClass() + " to subscribing class "
596576
+ subscription.subscriber.getClass(), cause);
597577
}
598-
SubscriberExceptionEvent exEvent = new SubscriberExceptionEvent(this, cause, event,
599-
subscription.subscriber);
600-
post(exEvent);
578+
if(sendSubscriberExceptionEvent) {
579+
SubscriberExceptionEvent exEvent = new SubscriberExceptionEvent(this, cause, event,
580+
subscription.subscriber);
581+
post(exEvent);
582+
}
601583
}
602584
} catch (IllegalAccessException e) {
603585
throw new IllegalStateException("Unexpected exception", e);
@@ -614,6 +596,10 @@ final static class PostingThreadState {
614596
boolean canceled;
615597
}
616598

599+
ExecutorService getExecutorService() {
600+
return executorService;
601+
}
602+
617603
// Just an idea: we could provide a callback to post() to be notified, an alternative would be events, of course...
618604
/* public */interface PostCallback {
619605
void onPostCompleted(List<SubscriberExceptionEvent> exceptionEvents);
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright (C) 2014 Markus Junginger, greenrobot (http://greenrobot.de)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.greenrobot.event;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.concurrent.ExecutorService;
21+
import java.util.concurrent.Executors;
22+
23+
/**
24+
* Creates EventBus instances with custom parameters and also allows to install a custom default EventBus instance.
25+
* Create a new builder using {@link EventBus#builder()}.
26+
*/
27+
public class EventBusBuilder {
28+
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
29+
30+
boolean logSubscriberExceptions = true;
31+
boolean logNoSubscriberMessages = true;
32+
boolean sendSubscriberExceptionEvent = true;
33+
boolean sendNoSubscriberEvent = true;
34+
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
35+
List<Class<?>> skipMethodVerificationForClasses;
36+
boolean failFast;
37+
38+
EventBusBuilder() {
39+
}
40+
41+
/** Default: true */
42+
public EventBusBuilder logSubscriberExceptions(boolean logSubscriberExceptions) {
43+
this.logSubscriberExceptions = logSubscriberExceptions;
44+
return this;
45+
}
46+
47+
/** Default: true */
48+
public EventBusBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages) {
49+
this.logNoSubscriberMessages = logNoSubscriberMessages;
50+
return this;
51+
}
52+
53+
/** Default: true */
54+
public EventBusBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent) {
55+
this.sendSubscriberExceptionEvent = sendSubscriberExceptionEvent;
56+
return this;
57+
}
58+
59+
/** Default: true */
60+
public EventBusBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent) {
61+
this.sendNoSubscriberEvent = sendNoSubscriberEvent;
62+
return this;
63+
}
64+
65+
/** Default: false */
66+
public EventBusBuilder failFast(boolean failFast) {
67+
this.failFast = failFast;
68+
return this;
69+
}
70+
71+
/**
72+
* Provide a custom thread pool to EventBus used for async and background event delivery. This is an advanced
73+
* setting to that can break things: ensure the given ExecutorService won't get stuck to avoid undefined behavior.
74+
*/
75+
public EventBusBuilder executorService(ExecutorService executorService) {
76+
this.executorService = executorService;
77+
return this;
78+
}
79+
80+
/**
81+
* Method name verification is done for methods starting with onEvent to avoid typos; using this method you can
82+
* exclude subscriber classes from this check. Also disables checks for method modifiers (public, not static nor
83+
* abstract).
84+
*/
85+
public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) {
86+
if (skipMethodVerificationForClasses == null) {
87+
skipMethodVerificationForClasses = new ArrayList<Class<?>>();
88+
}
89+
skipMethodVerificationForClasses.add(clazz);
90+
return this;
91+
}
92+
93+
/**
94+
* Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders' values. Must be
95+
* done only once before the first usage of the default EventBus.
96+
*
97+
* @throws EventBusException if there's already a default EventBus instance in place
98+
*/
99+
public EventBus installDefaultEventBus() {
100+
synchronized (EventBus.class) {
101+
if (EventBus.defaultInstance != null) {
102+
throw new EventBusException("Default instance already exists." +
103+
" It may be only set once before it's used the first time to ensure consistent behavior.");
104+
}
105+
EventBus.defaultInstance = build();
106+
return EventBus.defaultInstance;
107+
}
108+
}
109+
110+
public EventBus build() {
111+
return new EventBus(this);
112+
}
113+
114+
}

EventBus/src/de/greenrobot/event/SubscriberMethodFinder.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,17 @@ class SubscriberMethodFinder {
3737

3838
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
3939
private static final Map<String, List<SubscriberMethod>> methodCache = new HashMap<String, List<SubscriberMethod>>();
40-
private static final Map<Class<?>, Class<?>> skipMethodVerificationForClasses = new ConcurrentHashMap<Class<?>, Class<?>>();
40+
41+
private final Map<Class<?>, Class<?>> skipMethodVerificationForClasses;
42+
43+
SubscriberMethodFinder(List<Class<?>> skipMethodVerificationForClassesList) {
44+
skipMethodVerificationForClasses = new ConcurrentHashMap<Class<?>, Class<?>>();
45+
if (skipMethodVerificationForClassesList != null) {
46+
for (Class<?> clazz : skipMethodVerificationForClassesList) {
47+
skipMethodVerificationForClasses.put(clazz, clazz);
48+
}
49+
}
50+
}
4151

4252
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {
4353
String key = subscriberClass.getName() + '.' + eventMethodName;
@@ -120,14 +130,4 @@ static void clearCaches() {
120130
}
121131
}
122132

123-
static void skipMethodVerificationFor(Class<?> clazz) {
124-
if (!methodCache.isEmpty()) {
125-
throw new IllegalStateException("This method must be called before registering anything");
126-
}
127-
skipMethodVerificationForClasses.put(clazz, clazz);
128-
}
129-
130-
public static void clearSkipMethodVerifications() {
131-
skipMethodVerificationForClasses.clear();
132-
}
133133
}

EventBusTest/src/de/greenrobot/event/test/AbstractEventBusTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ public AbstractEventBusTest(boolean collectEventsReceived) {
6060
protected void setUp() throws Exception {
6161
super.setUp();
6262
EventBus.clearCaches();
63-
EventBus.clearSkipMethodNameVerifications();
6463
eventBus = new EventBus();
6564
mainPoster = new EventPostHandler(Looper.getMainLooper());
6665
assertFalse(Looper.getMainLooper().getThread().equals(Thread.currentThread()));

0 commit comments

Comments
 (0)