EventBus之高效使用
Posted xyTianZhao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EventBus之高效使用相关的知识,希望对你有一定的参考价值。
EventBus之高效使用
说起 EventBus,作为一名 android 开发者,应该不会太陌生,但是我们大部分都会根据官方文档直接进行使用,其实还有一种比较高效的使用方式。就是不使用注解的方式,在编译时期,对相关注册方法进行注册。
这其实就相当于用空间换时间的一种常规操作了。这里附上 官方源码 和 官方文档 的地址。
先来贴一张官方文档中的图解,让大家对 EventBus 的工作机制现有一个宏观上的回忆。
常规使用
先来看看常规的使用方式。
1、接入EventBus
implementation 'org.greenrobot:eventbus:3.2.0'
2、使用
//1、创建订阅事件
class DataModel(val msg: String)
class MainActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//2、注册订阅
EventBus.getDefault().register(this)
//3、创建接收发布的事件的方法
@Subscribe(threadMode = org.greenrobot.eventbus.ThreadMode.MAIN, sticky = true, priority = 2)
fun dataReceive(dataModel: DataModel)
Toast.makeText(this, dataModel.msg, Toast.LENGTH_LONG).show()
fun post(view: View)
//4、发布事件
EventBus.getDefault().post(DataModel("send success"))
override fun onDestroy()
//5、取消订阅
EventBus.getDefault().unregister(this)
super.onDestroy()
使用方式很简单,基本上和官方介绍的一样,为了方便就在这里聚合了一下。
下面来分析这种方式,是怎么实现订阅者与发布者之间进行信息交互的。
常规使用之源码分析
注册订阅者
在分析具体源码之前,先来看看几个相关的辅助类
1、Subscribe 作用于订阅事件方法的注解类
//运行时
@Retention(RetentionPolicy.RUNTIME)
//目标作用于方法
@Target(ElementType.METHOD)
public @interface Subscribe
//线程模式,有当线程、主线程、子线程、异步线程等。
ThreadMode threadMode() default ThreadMode.POSTING;
//是否为粘性事件,就是可以先进行发布,后进行注册,注册时候会立刻收到发布的事件
boolean sticky() default false;
//接受的优先级,数值越大优先级越高
int priority() default 0;
2、Subscription、SubscriberMethod 订阅方法的封装类
final class Subscription
//订阅的对象,例如上面的 MainActivity 对象(这里需要注意,不是类,而是具体的对象)
final Object subscriber;
//注册类中的订阅方法(与对象无关,解析相关类中的订阅方法)
final SubscriberMethod subscriberMethod;
public class SubscriberMethod
//订阅的方法名
final Method method;
//执行的线程环境
final ThreadMode threadMode;
//订阅事件类型(订阅方法中的参数类型)
final Class<?> eventType;
//优先级
final int priority;
//是否为粘性事件
final boolean sticky;
//用于比较两个方法是否相同,具体在继承中会用到
String methodString;
基于上面的使用方法,我们可以直接从第二步的注册订阅事件的 register()
方法来进行源码的跟踪。
先来看看注册事件
public class EventBus
//订阅方法的集合:<订阅类型,该类型的多有方法集合>,方便用于事件分发
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//订阅方法的集合:<订阅者对象,该类中的所有注册方法>,方便用于去掉订阅
private final Map<Object, List<Class<?>>> typesBySubscriber;
//所有的粘性事件集合:<订阅事件类型,事件对象>,用于后续新事件注册后的粘性事件分发
private final Map<Class<?>, Object> stickyEvents;
public void register(Object subscriber)
Class<?> subscriberClass = subscriber.getClass();
//从注册类中找出所有订阅事件的相关方法
//下文在分析 SubscriberMethodFinder 这个辅助类,这里先跳过,看注册的主流程
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this)
for (SubscriberMethod subscriberMethod : subscriberMethods)
subscribe(subscriber, subscriberMethod);
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod)
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//拿到当前订阅事件type的订阅集合
CopyOnWriteArrayList<Subscription> 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<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null)
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
//将订阅事件添加到订阅对象的集合中
subscribedEvents.add(eventType);
if (subscriberMethod.sticky)
......
//如果是粘性事件,则判断是否需要进行派发
//这也就是为什么可以先发布事件,后面进行注册也可以收到发布事件的原因了
//每次新注册方法时都会判断是否需要进行粘性事件的分发
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
......
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent)
if (stickyEvent != null)
postToSubscription(newSubscription, stickyEvent, isMainThread());
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread)
......
invokeSubscriber(subscription, event)
......
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);
下面来看下上文中略过的 SubscriberMethodFinder
辅助类。
这个类的作用就是从传入的注册类中找出订阅方法的集合并返回。
class SubscriberMethodFinder
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
//从缓存中查找是否曾经注册过,如果有则直接返回
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
//这里就是上文中注册方法调用的方法,根据订阅的 class 寻找该类中的所有订阅方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null)
return subscriberMethods;
if (ignoreGeneratedIndex)
//使用反射寻找,由于没有进行任何相关配置,所以默认会使用反射进行查找,所以效率相对来说比较底下
subscriberMethods = findUsingReflection(subscriberClass);
else
//使用添加的信息寻找
//这里后文分析高效使用时,在进行分析
subscriberMethods = findUsingInfo(subscriberClass);
if (subscriberMethods.isEmpty())
//如果注册的类中没有订阅方法,则直接抛出异常。这就有点坑,容易出bug
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;
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass)
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
//循环遍历当前类和父类,通过反射查找相应的订阅方法
while (findState.clazz != null)
//查找当前类的订阅方法
findUsingReflectionInSingleClass(findState);
//查找完毕后,将当前 class 定位到父类
findState.moveToSuperclass();
return getMethodsAndRelease(findState);
private void findUsingReflectionInSingleClass(FindState findState)
Method[] methods;
try
// 拿到该类中的所有方法,不包括父类的方法
methods = findState.clazz.getDeclaredMethods();
catch (Throwable th)
try
// 拿到该类中的所有方法,包括父类的方法
methods = findState.clazz.getMethods();
catch (LinkageError error)
......
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 注解属性,如果没有则不是订阅方法
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))
......
else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class))
......
到这里,注册的流程基本上就完了。上面的注释写的基本上很详细了,也是从上到下的一个流程。
发布订阅事件
注册完成后,就是接受订阅事件了,以发布事件为入口,看看事件发布后,订阅的方法是如何接受到发布的订阅事件。
public class EventBus
public void post(Object event)
//用于判断在当前线程的分发状态
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// 将发布事件加入队列中
eventQueue.add(event);
if (!postingState.isPosting)
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled)
throw new EventBusException("Internal error. Abort state was not reset");
try
while (!eventQueue.isEmpty())
// 循环从队列中取出后进行分发
postSingleEvent(eventQueue.remove(0), postingState);
finally
postingState.isPosting = false;
postingState.isMainThread = false;
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error
......
postSingleEventForEventType(event, postingState, eventClass);
......
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this)
// 根据事件的类型,拿到所有订阅该事件的方法
subscriptions = subscriptionsByEventType.get(eventClass);
if (subscriptions != null && !subscriptions.isEmpty())
for (Subscription subscription : subscriptions)
......
// 遍历所有订阅该事件的方法,并进行分发
postToSubscription(subscription, event, postingState.isMainThread);
......
return true;
return false;
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread)
......
// 这里会进行相应的线程切换,就不做过多解析
invokeSubscriber(subscription, event)
......
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);
发布事件到这就结束了,就是从订阅的集合中拿到订阅的相关方法并进行调用,就完事了。
解注册订阅者
最后就是解注册了,相比分发就更为简单了,没什么说的,就是从集合中找出,删除完事。
public class EventBus
public synchronized void unregister(Object subscriber)
// 拿到解注册类中订阅的所有方法
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null)
for (Class<?> eventType : subscribedTypes)
// 遍历所有方法,根据事件类型,从 subscriptionsByEventType 集合中移除相应的订阅事件
unsubscribeByEventType(subscriber, eventType);
Android之EventBus使用