EventBus通过更少的代码和更高的质量为Android和Java提供的事件总线简化了活动,片段,线程,服务等之间的通信
正如EventBus website官网形容的那样:
(1)简化组件间的通信
(2)解耦事件发送器和接收器
(3)使用UI工件(例如活动、片段)和后台线程执行良好的操作
(4)避免复杂且容易出错的依赖关系和生命周期问题
(5)快;专门针对高性能进行优化
(6)很小(< 50 k jar)
(7)有100,000,000+安装的应用实践(证明有100,000,000+的应用替你踩过了坑)
(8)具有交付线程、用户优先级等高级功能
(9)我觉得通过注解使得方法名字更加自由
官网说:EventBus是Android和Java的开放源码库,使用发布者/订阅者模式(其实就是观察者模式)进行松散耦合。EventBus可以通过几行代码(简化代码、删除依赖项和加速应用程序开发)实现中央通信来解耦类
subscriber找到所有订阅方法和订阅方法参数
publisher组装消息对象,通过消息类和参数类对应,找到订阅方法,执行反射方法绑定消息
发布者/订阅者模式举个栗子: 微信公众号,你订阅了他,他发了推文,你就接收到消息
(1)线程安全的单例模式(教科书写法,同样还有静态代码实现和枚举实现)
(2)设计模式-- builder建造者模式
(3)注解使用
(4)反射使用
(5)AbstractProcessor注解处理器
(6)map put remove返回值(简化代码)
3.EventBus使用
官方Demo
(1) 基本操作:
Subscriber:
EventBus.getDefault().register(this);
@Subscribe
public void xxxx(TestEvent testEvent) {
}
Publisher:
EventBus.getDefault().post(new TestEvent());
(2)骚操作 : 通过注解处理器EventBusAnnotationProcessor,生成MyEventBusIndex.java
Subscriber:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).ignoreGeneratedIndex(false).build();
eventBus.register(this);
@Subscribe
public void xxxx(TestEvent testEvent) {
}
Publisher:
eventBus.post(new TestEvent());
我们的项目中采用这种方式除了把module拷过去还要修改如下几点:
(1) EventbusAnnotationProcessor build.gradle compile project(':eventbus')改为compile 'org.greenrobot:eventbus:3.1.1'
(2) app build.gradle
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [eventBusIndex: 'org.greenrobot.eventbusperf.MyEventBusIndex']
}
}
}
dependencies {
annotationProcessor project(':eventbus-annotation-processor')
}
(3)settings.gradle
include ':app'
include ':EventBusAnnotationProcessor'
project(":EventBusAnnotationProcessor").name = "eventbus-annotation-processor"
按照publisher/subscriber模式来分析代码
4.1 subscriber 订阅消息
( 1 ~ 3 )单例模式结合建造者模式创建EventBus对象,生成mainThreadPoster,backgroundPoster, asyncPoster
( 4 )得到主线线程Looper,创建主线程Handler HandlerPoster
( 5 )创建SubscriberMethodFinder对象
(6 ~ 15)递归订阅者及其父类们得到所有订阅消息方法,按照规则存入到集合subscriptionsByEventType, typesBySubscriber中
subscriber: 订阅者
method: 订阅者中@Subscribe注解的方法
threadMode: 订阅方法运行线程参数(POSTING,MAIN,MAIN_ORDERED,BACKGROUND,ASYNC)
evntType: 订阅方法的参数类名
priority: 方法优先级,subscriptions是按照priority进行插入,值越小就排越前,越早会被
sticky: 是否是粘性方法,配合粘性post方法,如果post在register之前,在register成功后也能保证收到订阅消息
这边有两个逻辑需要注意一下,(真的饶了我好久):
4.1.1递归订阅方法的方式
(1)!ignoreGeneratedIndex 配合List
上文介绍 greenrobot官方demo栗子中实例化自动化编译生成MyEventBusIndex类,通过建造者builder方法addIndex最后赋值给了SubscriberMethodFinder对象的subscriberInfoIndexes集合.集合里面保存了订阅类,订阅方法,参数类,回调线程。
(2)ignoreGeneratedIndex 这个是我们利用默认构造器生成EventBus单例时会走这个方法
其实目的都是要找订阅类及其父类所有订阅方法,方法1是实例化EventBus对象时通过new MyEventBusIndex()获得相关字段,调用getSubscriberMethods反射获取订阅方法.方法2是直接利用反射解析当前订阅类及其父类获取订阅方法.方法1比方法2少了递归查找父类的逻辑,提高了效率,但是一般我们的项目类的层级不高,所以基本忽略不计.
List findSubscriberMethods(Class> subscriberClass) {
List 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;
}
}
4.1.2是否可以添加方法集合逻辑
判断条件:这边的逻辑分为两步骤:
第一步: 校验首次存入集合键订阅方法参数,值方法,的正确性.(greenrobot也说了自己是偏执狂,非要写这个恶心的逻辑)
第二步: 后来进来这个为标准,除非这个方法所在类是标准值方法所在类的子类或子接口才能被添加进去.我一开始觉得这个是没道理的,因为是从子类开始往父类找订阅方法,怎么会找到子类的子类呢?其实这里是逻辑需要,因为subscriberClassByMethodKey里面已经存入标准键值,没法通过存不存在来判断,只能通过isAssignableFrom将它排除.
效果:子类和父类都有相同参数的相同订阅方法,只有子类的接收到订阅消息
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.
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);
}
}
private boolean checkAddWithMethodSignature(Method method, Class> eventType) {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
Class> methodClass = method.getDeclaringClass();
Class> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
// Only add if not already found in a sub class
return true;
} else {
// Revert the put, old class is further down the class hierarchy
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
4.2 publisher 发送消息(以MainThread为例)
(1 ~ 2)创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
(3 ~ 6)创建主线程Poster
(7 ~ 8)把消息对象存入线程间局部变量currentPostingThreadState中
( 9 ~ 18)得到订阅方法对象,发送方法在对应的线程中执行反射
关键方法理解
4.2.1策略模式实现线程模式
这边就是EventBus核心就是能让发送的消息运行在任意线程中
MAIN,MAIN_ORDERED 利用主线程Looper创建HandlerPoster对象,故能运行在主线程
BACKGROUND 利用线程池创建一个线程对象运行BackgroundPoster方法
ASYNC 利用线程池创建一个线程对象运行AsyncPoster方法
BACKGROUND和ASYNC两个异步线程的区别:
BACKGROUND线程run起来是一个死循环,只要一直在有消息发出,所有的消息都是在这一个线程消息中,如果没有消息发出了,循环会被打断,线程会休眠.有人和线程池申请开辟线程时,这个线程会被回收掉.而ASYNC线程代码很简单,只要有消息我就让线程池开辟一个线程给我,让我去执行反射方法绑定消息,执行完立马线程休眠,再来消息重复操作,所以这种方式发送的消息都不会是同一个线程,这也是为什么greenrobot要采用线程池,这边不停的在创建,回收线程,采用线程池能减少开销
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);
}
}
}
@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;
}
}
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);
}
4.2.2 方法10
你发送的消息对象可能subscriptionsByEventType key值(订阅方法参数类) 的子类或者子接口,如果没有这个逻辑是过滤不出来的,这边就是递归将发送消息的类,还有他的实现接口,还有他超类,超接口存在集合中,循环到subscriptionsByEventType中匹配,所以只要是订阅方法参数是发送消息的超类和超接口都会收到通知
private static List> lookupAllEventTypes(Class> eventClass) {
synchronized (eventTypesCache) {
List> eventTypes = eventTypesCache.get(eventClass);
if (eventTypes == null) {
eventTypes = new ArrayList<>();
Class> clazz = eventClass;
while (clazz != null) {
eventTypes.add(clazz);
addInterfaces(eventTypes, clazz.getInterfaces());
clazz = clazz.getSuperclass();
}
eventTypesCache.put(eventClass, eventTypes);
}
return eventTypes;
}
}
5.1粘性消息
publisher 调用postSticky方法 Subscriber 订阅方法必须要将sticky设置true
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().postSticky(new Event(1));
EventBus.getDefault().register(this);
}
@Subscribe(sticky = true)
public void handleEvent(Event event) {
Log.e("wjq","MainActivity = " + event.id + "");
}
}
有时候发送消息在订阅之前,按照现在的逻辑,先把所有订阅方法检索出来,当有消息需要发出时,匹配方法反射的思路是不行.greenrobot的思路是你发送粘性消息我把你保存在内存中,当有人订阅消息时先检查他是不是粘性订阅方法,再按照原有逻辑执行
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class> eventType = subscriberMethod.eventType;
....
if (subscriberMethod.sticky) {
if (eventInheritance) {
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, 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);
}
}
}