EventBus 深入学习四之实例&类说明
本篇开始,则转向
greenrobot/EventBus
, 之前基本上将Guava中设计的思路捋了一遍,逻辑比较简单和清晰,接下来则看下广泛运用于android的这个框架又有什么不一样的地方,有什么独特的精妙所在
一些废话
开始之前,当然是要先把代码donw下来,然后本机能跑起来才行; so,基本的环境要搞起, Android Studio
将作为主要的ide
在导入工程之后,发现一直报一个 jdk版本过低的异常,解决方法是设置ide的jdk环境,如下,指定jdk为8就可以了
使用方法一览
在开始之前,先看下这个框架怎么用,会用了之后才能更好的考虑怎么去分析拆解
用法相比较Guava EventBus
差别不大, 除了支持注解方式之外,还支持非注解形式,如
public class EventBusIndexTest {
private String value;
@Test
/** Ensures the index is actually used and no reflection fall-back kicks in. */
public void testManualIndexWithoutAnnotation() {
SubscriberInfoIndex index = new SubscriberInfoIndex() {
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
Assert.assertEquals(EventBusIndexTest.class, subscriberClass);
SubscriberMethodInfo[] methodInfos = {
new SubscriberMethodInfo("someMethodWithoutAnnotation", String.class)
};
return new SimpleSubscriberInfo(EventBusIndexTest.class, false, methodInfos);
}
};
EventBus eventBus = EventBus.builder().addIndex(index).build();
eventBus.register(this);
eventBus.post("Yepp");
eventBus.unregister(this);
Assert.assertEquals("Yepp", value);
}
public void someMethodWithoutAnnotation(String value) {
this.value = value;
}
}
上面与我们之前的使用,区别主要在于 EventBus
对象的获取,down下来的工程中,有一个基础的测试类,给我们演示了不少的使用方式
@RunWith(AndroidJUnit4.class)
public class EventBusBasicTest {
public static class WithIndex extends EventBusBasicTest {
@Test
public void dummy() {}
}
@Rule
public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
protected EventBus eventBus;
private String lastStringEvent;
private int countStringEvent;
private int countIntEvent;
private int lastIntEvent;
private int countMyEventExtended;
private int countMyEvent;
private int countMyEvent2;
@Before
public void setUp() throws Exception {
eventBus = new EventBus();
}
@Test
@UiThreadTest
public void testRegisterAndPost() {
// Use an activity to test real life performance
TestActivity testActivity = new TestActivity();
String event = "Hello";
long start = System.currentTimeMillis();
eventBus.register(testActivity);
long time = System.currentTimeMillis() - start;
Log.d(EventBus.TAG, "Registered in " + time + "ms");
eventBus.post(event);
assertEquals(event, testActivity.lastStringEvent);
}
// 无订阅者
@Test
public void testPostWithoutSubscriber() {
eventBus.post("Hello");
}
@Test
public void testUnregisterWithoutRegister() {
// Results in a warning without throwing
eventBus.unregister(this);
}
// This will throw "out of memory" if subscribers are leaked
@Test
public void testUnregisterNotLeaking() {
int heapMBytes = (int) (Runtime.getRuntime().maxMemory() / (1024L * 1024L));
for (int i = 0; i < heapMBytes * 2; i++) {
EventBusBasicTest subscriber = new EventBusBasicTest() {
byte[] expensiveObject = new byte[1024 * 1024];
};
eventBus.register(subscriber);
eventBus.unregister(subscriber);
Log.d("Test", "Iteration " + i + " / max heap: " + heapMBytes);
}
}
@Test
public void testRegisterTwice() {
eventBus.register(this);
try {
eventBus.register(this);
fail("Did not throw");
} catch (RuntimeException expected) {
// OK
}
}
@Test
public void testIsRegistered() {
assertFalse(eventBus.isRegistered(this));
eventBus.register(this);
assertTrue(eventBus.isRegistered(this));
eventBus.unregister(this);
assertFalse(eventBus.isRegistered(this));
}
@Test
public void testPostWithTwoSubscriber() {
EventBusBasicTest test2 = new EventBusBasicTest();
eventBus.register(this);
eventBus.register(test2);
String event = "Hello";
eventBus.post(event);
assertEquals(event, lastStringEvent);
assertEquals(event, test2.lastStringEvent);
}
@Test
public void testPostMultipleTimes() {
eventBus.register(this);
MyEvent event = new MyEvent();
int count = 1000;
long start = System.currentTimeMillis();
// Debug.startMethodTracing("testPostMultipleTimes" + count);
for (int i = 0; i < count; i++) {
eventBus.post(event);
}
// Debug.stopMethodTracing();
long time = System.currentTimeMillis() - start;
Log.d(EventBus.TAG, "Posted " + count + " events in " + time + "ms");
assertEquals(count, countMyEvent);
}
@Test
public void testMultipleSubscribeMethodsForEvent() {
eventBus.register(this);
MyEvent event = new MyEvent();
eventBus.post(event);
assertEquals(1, countMyEvent);
assertEquals(1, countMyEvent2);
}
@Test
public void testPostAfterUnregister() {
eventBus.register(this);
eventBus.unregister(this);
eventBus.post("Hello");
assertNull(lastStringEvent);
}
@Test
public void testRegisterAndPostTwoTypes() {
eventBus.register(this);
eventBus.post(42);
eventBus.post("Hello");
assertEquals(1, countIntEvent);
assertEquals(1, countStringEvent);
assertEquals(42, lastIntEvent);
assertEquals("Hello", lastStringEvent);
}
@Test
public void testRegisterUnregisterAndPostTwoTypes() {
eventBus.register(this);
eventBus.unregister(this);
eventBus.post(42);
eventBus.post("Hello");
assertEquals(0, countIntEvent);
assertEquals(0, lastIntEvent);
assertEquals(0, countStringEvent);
}
@Test
public void testPostOnDifferentEventBus() {
eventBus.register(this);
new EventBus().post("Hello");
assertEquals(0, countStringEvent);
}
@Test
public void testPostInEventHandler() {
RepostInteger reposter = new RepostInteger();
eventBus.register(reposter);
eventBus.register(this);
eventBus.post(1);
assertEquals(10, countIntEvent);
assertEquals(10, lastIntEvent);
assertEquals(10, reposter.countEvent);
assertEquals(10, reposter.lastEvent);
}
@Test
public void testHasSubscriberForEvent() {
assertFalse(eventBus.hasSubscriberForEvent(String.class));
eventBus.register(this);
assertTrue(eventBus.hasSubscriberForEvent(String.class));
eventBus.unregister(this);
assertFalse(eventBus.hasSubscriberForEvent(String.class));
}
@Test
public void testHasSubscriberForEventSuperclass() {
assertFalse(eventBus.hasSubscriberForEvent(String.class));
Object subscriber = new ObjectSubscriber();
eventBus.register(subscriber);
assertTrue(eventBus.hasSubscriberForEvent(String.class));
eventBus.unregister(subscriber);
assertFalse(eventBus.hasSubscriberForEvent(String.class));
}
@Test
public void testHasSubscriberForEventImplementedInterface() {
assertFalse(eventBus.hasSubscriberForEvent(String.class));
Object subscriber = new CharSequenceSubscriber();
eventBus.register(subscriber);
assertTrue(eventBus.hasSubscriberForEvent(CharSequence.class));
assertTrue(eventBus.hasSubscriberForEvent(String.class));
eventBus.unregister(subscriber);
assertFalse(eventBus.hasSubscriberForEvent(CharSequence.class));
assertFalse(eventBus.hasSubscriberForEvent(String.class));
}
@Subscribe
public void onEvent(String event) {
lastStringEvent = event;
countStringEvent++;
}
@Subscribe
public void onEvent(Integer event) {
lastIntEvent = event;
countIntEvent++;
}
@Subscribe
public void onEvent(MyEvent event) {
countMyEvent++;
}
@Subscribe
public void onEvent2(MyEvent event) {
countMyEvent2++;
}
@Subscribe
public void onEvent(MyEventExtended event) {
countMyEventExtended++;
}
public static class TestActivity extends Activity {
public String lastStringEvent;
@Subscribe
public void onEvent(String event) {
lastStringEvent = event;
}
}
public static class CharSequenceSubscriber {
@Subscribe
public void onEvent(CharSequence event) {
}
}
public static class ObjectSubscriber {
@Subscribe
public void onEvent(Object event) {
}
}
public class MyEvent {
}
public class MyEventExtended extends MyEvent {
}
public class RepostInteger {
public int lastEvent;
public int countEvent;
@Subscribe
public void onEvent(Integer event) {
lastEvent = event;
countEvent++;
assertEquals(countEvent, event.intValue());
if (event < 10) {
int countIntEventBefore = countEvent;
eventBus.post(event + 1);
// All our post calls will just enqueue the event, so check count is unchanged
assertEquals(countIntEventBefore, countIntEventBefore);
}
}
}
}
EventBus实例创建
提供了三中创建方式,一个是直接使用默认实例; 一个最简单普通的构造;再者使用 EventBusBuilder
来构建, 下面会分别对上面的几种情况进行分析说明
1. EventBus.getDefault()
默认实例
这里使用了最常见的延迟加载的单例模式,来获取实例,注意下 snchronized
的使用位置,并没有放在方法签名上( 注意这个类不是严格意义上的单例,因为构造函数是public)
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;
}
此外另一种常见的单例模式下面也顺手贴出,主要利用静态内部类
private static class InnerInstance {
static volatile EventBus defaultInstance = new EventBus();
}
public static EventBus getInstance() {
return InnerInstance.defaultInstance;
}
2. new EventBus()
构造器
这个比较简单了,基本上获取实例的方法都这么玩,对于EventBus而言,把这个放开的一个关键点是再你选的系统中,不会限制你的EventBus
实例个数,也就是说,你的系统可以有多个并行的消息-事务总线
3. Builder模式
这个模式也是比较常用的,对于构建复杂对象时,选择的Builder模式,对这个的分析之前,先看下 EventBus
的属性
static volatile EventBus defaultInstance;
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
private final HandlerPoster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
private final SubscriberMethodFinder subscriberMethodFinder;
private final ExecutorService executorService;
private final boolean throwSubscriberException;
private final boolean logSubscriberExceptions;
private final boolean logNoSubscriberMessages;
private final boolean sendSubscriberExceptionEvent;
private final boolean sendNoSubscriberEvent;
private final boolean eventInheritance;
常用类分析
1. SubscriberMethod
订阅者回调方法封装类
这个类主要保存的就是订阅者的回调方法相关信息
final Method method;
final ThreadMode threadMode;
// 监听的事件类型, 也就是注册方法的唯一参数类型
final Class<?> eventType;
// 定义监听消息的优先级
final int priority;
//在Android开 发中,Sticky事件只指事件消费者在事件发布之后才注册的也能接收到该事件的特殊类型
final boolean sticky;
/** Used for efficient comparison */
String methodString;
ThreadMode, 区分了以下几种类型,且各自的意思如下
/**
* 和发布消息的公用一个线程
* Subscriber will be called in the same thread, which is posting the event. This is the default. Event delivery
* implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
* simple tasks that are known to complete is a very short time without requiring the main thread. Event handlers
* using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
*/
POSTING,
/**
* 再android的主线程下
* Subscriber will be called in Android's main thread (sometimes referred to as UI thread). If the posting thread is
* the main thread, event handler methods will be called directly. Event handlers using this mode must return
* quickly to avoid blocking the main thread.
*/
MAIN,
/**
* Subscriber will be called in a background thread. If posting thread is not the main thread, event handler methods
* will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
* background thread, that will deliver all its events sequentially. Event handlers using this mode should try to
* return quickly to avoid blocking the background thread.
*/
BACKGROUND,
/**
* Event handler methods are called in a separate thread. This is always independent from the posting thread and the
* main thread. Posting events never wait for event handler methods using this mode. Event handler methods should
* use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
* of long running asynchronous handler methods at the same time to limit the number of concurrent threads. EventBus
* uses a thread pool to efficiently reuse threads from completed asynchronous event handler notifications.
*/
ASYNC
2. Subscription
注册回调信息封装类
EventBus中维护的订阅关系, 在对象注册时注入到
EventBus
, 推送消息时,则会从EventBus
中获取
EventBus
中维护的订阅者关系数据结构就是 Map<Class<?>, CopyOnWriteArrayList<Subscription>>
, 其中key为事件类型, value就是订阅者信息集合
final Object subscriber;
final SubscriberMethod subscriberMethod;
3. Subscribe
注解
注解标注在类的唯一参数的公共方法上, 表示这个方法就是我们注册的订阅者回调方法
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
/**
* If true, delivers the most recent sticky event (posted with
* {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
*/
boolean sticky() default false;
/** Subscriber priority to influence the order of event delivery.
* Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
* others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
* delivery among subscribers with different {@link ThreadMode}s! */
int priority() default 0;
}
4. SubscriberMethodFinder
辅助工具类,获取订阅者中的回调方法
顾名思义,这个就是用来获取订阅者类中的所有注册方法的,支持两种方式,一个是上面的注解方式;还有一个则是利用
SubscriberInfo
核心方法:List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)
5. SubscriberInfo
定义获取订阅者注册方法的接口
通常这个会和SubscriberInfoIndex
配合使用,后面这个接口专注返回 SubscriberInfo
对象,其默认的实现也比较简单,基本上就是指定完整的注册方法信息(SubscriberMethodInfo
)即可
public class SubscriberMethodInfo {
final String methodName;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
}
public class SimpleSubscriberInfo implements SubscriberInfo {
public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass,SubscriberMethodInfo[] methodInfos) {
this.subscriberClass = subscriberClass;
this.superSubscriberInfoClass = null;
this.shouldCheckSuperclass = shouldCheckSuperclass;
this.methodInfos = methodInfos;
}
}
6. xxxPost 发送事件的辅助类
拿一个异步的例子看一下, 里面包含两个对象, 一个 queue
消息推送的队列,一个 eventBus
实例,消息推送,则是调用 enqueue()
方法, 先将监听者 + 消息塞入队列, 然后调用 eventBus的线程池实进行实现异步的消息推送
private final PendingPostQueue queue;
private final EventBus eventBus;
AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}