EventBus相信大部分Androider都很熟悉,虽然现在谷歌官方出了JetPack来替代,但EventBus的一些设计思路还是值得借鉴的。下面就以阿里面试中我手写的一个EventBus案例为例;
其实EventBus原理并不难,就是维护了几个数组,然后根据对应的key找到对应的注册对象,通过放射的方式调用对应的方法。
EventBus3.0之前和之后有比较大的区别,最大的差别在于3.0之后通过apt再编译期间生成一个引用对象,这样做很大程度上提高了性能。
最简单的使用
//注册事件
EventBus.getDefault().register(this);
//注册方法
@Subscribe
public void event(BaseEventBusBeaan message) {
LogUtils.d("EventBusActivity event");
}
//发送事件
EventBus.getDefault().post(new BaseEventBusBeaan("123", new Bundle()));
//回收
EventBus.getDefault().unregister(this);
post流程
首先我们应该理清我们的需求,我们需要的是在 post
一个对象出去的时候,所有注册监听了这个对象的类都能接收到这个通知,于是这里应该需要一个数组来存储数据。
//post出去的对象为key,一个注册者Subscription的list作为value
private Map, CopyOnWriteArrayList> subscriptionsByEventType;
//这个Subscription包括下面参数
public class Subscription {
final Object subscriber; //activity或者fragment
final SubscriberMethod subscriberMethod;
public Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
this.subscriber = subscriber;
this.subscriberMethod = subscriberMethod;
}
}
public class SubscriberMethod {
private String methodName; // 订阅方法名
private Method method; // 订阅方法,用于最后的自动执行订阅方法
private ThreadMode threadMode; // 线程模式
private Class> eventType; // 事件对象Class,如:UserInfo.class
}
有了subscriptionsByEventType
之后,我们就可以根据post()
的发送的事件来查找所有注册者,再遍历list
,逐一反射。
public void post(Object event) {
postSingleEventForEventType(event, event.getClass());
}
//遍历所有的订阅者,发送对应的事件
private void postSingleEventForEventType(Object event, Class> eventClass) {
CopyOnWriteArrayList subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
invokeSubscriber(subscription, event);
}
}
}
//这里暂时不考虑线程的问题
private void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.getMethod().invoke(subscription.subscriber, event);
} catch (Exception e) {
e.printStackTrace();
}
}
上述就是一个简化版的post
过程.
Register流程
上述的post
还差一个很关键的地方,就是subscriptionsByEventType
数据的来源,我们很自然的就该想到是在register
的过程中。
再回来看看subscriptionsByEventType
的key和value,发现这些值大都能从下面这样的函数中取得。
@Subscribe
public void event(BaseEventBusBeaan message) {
LogUtils.d("EventBusActivity event");
}
所以我们需要遍历类中的所有方法,找到所有@Subscribe
注释过的函数,并保存下来。
这里采用的是apt方案,在编译过程中遍历所有类,寻找所有@Subscribe
注释过的函数,并将其按照一定格式保存下来,其结果会生成类似以下这样的类。
//具体的生成过程不再这里赘述,想要了解的可以自己看文末的代码
//编译过程中将所有 @Subscribe注释过的方法保存到SUBSCRIBER_INDEX数组中。
//key为函数所属的类,比如MainActivity,value则是一个对象,保存一个数据的集合。
public final class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap();
putIndex(new SimpleSubscriberInfo(EventBusActivity2.class,
new SubscriberMethod[] {
new SubscriberMethod(EventBusActivity2.class, "event", BaseEventBusBeaan.class, ThreadMode.POSTING, 0, false),
new SubscriberMethod(EventBusActivity2.class, "sticky", UserInfo.class, ThreadMode.POSTING, 2, true),
new SubscriberMethod(EventBusActivity2.class, "sticky2", UserInfo.class, ThreadMode.POSTING, 2, true)}
));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
@Override
public SubscriberInfo getSubscriberInfo(Class subscriberClass) {
return SUBSCRIBER_INDEX.get(subscriberClass);
}
}
有了MyEventBusIndex
之后,开始register
流程.
public void register(Object subscriber) {
Class> subscriberClass = subscriber.getClass();
List subscriberMethods = findSubscriberMethods(subscriberClass);
//这个循环是生成subscriptionsByEventType对象的关键,
for (SubscriberMethod method : subscriberMethods) {
subscribe(subscriber, method);
}
}
//1.根据subscriberClass先从methodBySubscriber缓存中找
private List findSubscriberMethods(Class> subscriberClass) {
List subscriberMethods = methodBySubscriber.get(subscriberClass);
if (subscriberMethods != null) return subscriberMethods;
subscriberMethods = findByAPT(subscriberClass);
if (subscriberMethods != null) {
methodBySubscriber.put(subscriberClass, subscriberMethods);
}
return subscriberMethods;
}
//2.接着从subscriberInfoIndex查找,subscriberInfoIndex这个对象就是上文中提到的MyEventBusIndex的对象
private List findByAPT(Class> subscriberClass) {
if (subscriberInfoIndex == null) {
throw new RuntimeException("未添加索引文件");
}
SubscriberInfo subscriberInfo = subscriberInfoIndex.getSubscriberInfo(subscriberClass);
if (subscriberInfo != null) return Arrays.asList(subscriberInfo.getSubscriberMethods());
return null;
}
接着开始遍历subscriberMethods
(因为每个订阅者不一定只有一个方法添加了@Subscribe
注解)
for (SubscriberMethod method : subscriberMethods) {
subscribe(subscriber, method);
}
//在这里就可以生成post过程中所需要的 subscriptionsByEventType 数据了。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class> eventType = subscriberMethod.getEventType();
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
}
Subscription subscription = new Subscription(subscriber, subscriberMethod);
subscriptions.add(i, subscription);
//订阅者类型集合,unregister的时候用到
List> subscribeEvents = typeBySubscriber.get(subscriber);
if (subscribeEvents == null) {
subscribeEvents = new ArrayList<>();
typeBySubscriber.put(subscriber, subscribeEvents);
}
subscribeEvents.add(eventType);
}
到了这里,其实一个简单的流程就已经通了。
总结一下大概的流程
- 通过apt在编译期将所有被
@Subscribe
注解的函数添加到MyEventBusIndex
对象中。 - 在
register
过程中生成subscriptionsByEventType
的数据。 - 在
post
过程中通过subscriptionsByEventType
数据查找对应的函数,然后再通过反射的方式调用。
优先级的问题
这个问题也十分简单,只需要在插入数据的时候,做下优先级判断即可。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class> eventType = subscriberMethod.getEventType();
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
}
Subscription subscription = new Subscription(subscriber, subscriberMethod);
//根据优先级插队
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.getPriority() > subscriptions.get(i).subscriberMethod.getPriority()) {
if (!subscriptions.contains(subscription)) subscriptions.add(i, subscription);
break;
}
}
//订阅者类型集合,unregister的时候用到
List> subscribeEvents = typeBySubscriber.get(subscriber);
if (subscribeEvents == null) {
subscribeEvents = new ArrayList<>();
typeBySubscriber.put(subscriber, subscribeEvents);
}
subscribeEvents.add(eventType);
}
粘性事件
普通事件是先注册,后发送。而粘性事件相反,是先发送,后注册。
我们只需要调换一下顺序即可。在发送的时候将事件存储下来,然后在register
的时候去检查有没有合适的事件
public void postSticky(Object event) {
stickyEvents.put(event.getClass(), event);
}
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
....
//检查是否有合适的事件可以触发
sticky(subscriberMethod, eventType, subscription);
}
private void sticky(SubscriberMethod subscriberMethod, Class> eventType, Subscription subscription) {
if (subscriberMethod.isSticky()) {
Object event = stickyEvents.get(eventType);
if (event != null) {
postToSubscription(subscription, event);
}
}
}
最后加上postToSubscription
的代码。
private void postToSubscription(final Subscription subscription, final Object event) {
switch (subscription.subscriberMethod.getThreadMode()) {
case POSTING: // 订阅、发布在同一线程
invokeSubscriber(subscription, event);
break;
case MAIN:
//事件发送方是主线程
if (Looper.myLooper() == Looper.getMainLooper()) {
invokeSubscriber(subscription, event);
} else {
//事件发送方是子线程
handler.post(new Runnable() {
@Override
public void run() {
invokeSubscriber(subscription, event);
}
});
}
break;
case ASYNC:
//发送方在主线程
if (Looper.myLooper() == Looper.getMainLooper()) {
executorService.execute(new Runnable() {
@Override
public void run() {
invokeSubscriber(subscription, event);
}
});
} else {
invokeSubscriber(subscription, event);
}
break;
}
}
private void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.getMethod().invoke(subscription.subscriber, event);
} catch (Exception e) {
e.printStackTrace();
}
}
关于我
本人是一个拥有6年开发经验的帅气Android攻城狮,记得看完点赞,养成习惯,微信搜一搜「 程序猿养成中心 」关注这个喜欢写干货的程序员。
另外耗时两年整理收集的Android一线大厂面试完整考点PDF出炉,资料【完整版】已更新在我的【Github】,有面试需要的朋友们可以去参考参考,如果对你有帮助,可以点个Star哦!
地址:【https://github.com/733gh/xiongfan】