内容简介:本文概要:Android中消息传递有多种方式.当我们需要在多个地方接收事件通知,此时接口回调过于繁琐;广播又显得资源浪费.这时就需要用到EventBus了. EventBus是一种基于观察者模式,降低组件之间耦合,简化通信的方式.
本文概要:
- EventBus的介绍
- 简单使用
- EventBus源码分析
引言
Android中消息传递有多种方式.
- Handler : 线程间的通信.
- BroadcastReceiver : 进程间的通信.接收系统广播.
- 接口回调 : 事件触发通知.
当我们需要在多个地方接收事件通知,此时接口回调过于繁琐;广播又显得资源浪费.这时就需要用到EventBus了. EventBus是一种基于观察者模式,降低组件之间耦合,简化通信的方式.
官方介绍地址: github.com/greenrobot/…
一. EventBus的介绍
EventBus的三剑客 :
- Publisher : 事件发布者
- Event : 事件
- Subscriber : 事件订阅者
事件由发布者通过EvenentBus传递给订阅者.
EventBus的四模型 :
- ThreadMode.POSTING : 在哪个线程发布,就在哪个线程处理
- ThreadMode.MAIN : 在主线程处理.
- ThreadMode.BACKGROUND : 子~子 和 主~线程池
- ThreadMode.ASYNC : 线程池处理
在3.0之前只能固定的方法来指定线程模型:
- OnEvent
- OnEventMainThread
- OnEventBackgroundThread
- OnEventAsync
二. 简单使用
- 定义一个事件类
public class DataEvent{ String data; public DataEvent(String data){ this.data = data; } } 复制代码
- 注册订阅
EventBus.getDefault().register(this); 复制代码
- 发布事件
//发布普通事件 EventBus.getDefault().post(new DataEvent()); //发布黏性事件 EventBus.getDefault().postSticky(new DataEvent()); 复制代码
- 定义订阅方法
//指定线程模型和优先级 @Subscriber (threadMode = ThreadMode.MAIN,priority = 0) public void onDataEvent(DataEvent dataEvent){ } 复制代码
- 反注册订阅
EventBus.getDefault().unregister(this); 复制代码
上述示例中,使用了黏性事件和优先级. 接下来看两者概念.
- 黏性事件 : 事件发送之后再进行注册依然能收到该事件.
- 优先级 : 默认为0,数值越大优先级越高.
三. 基本原理
EventBus的原理主要理解注册-发布事件-反注册流程三个方面.
3.1 定义注解
同样EventBus也定义了我们需要用到的注解.
@Documented @Retention(RetentionPolicy.RUNTIME)//运行时注解 @Target({ElementType.METHOD})//修饰方法 public @interface Subscribe { //定义了三个参数 ThreadMode threadMode() default ThreadMode.POSTING; boolean sticky() default false; int priority() default 0; } 复制代码
3.2 EvenBus的注册
先初始化:使用单例模式双重检查DCL方式创建EventBus对象.
public static EventBus getDefault() { EventBus instance = defaultInstance; if (instance == null) { synchronized (EventBus.class) { instance = EventBus.defaultInstance; if (instance == null) { instance = EventBus.defaultInstance = new EventBus(); } } } return instance; } 复制代码
初始化完成后,就需要调用register方法进行注册.
public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); //获取当前订阅者所有订阅方法(findSubscriberMethods) List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { //遍历所有方法,将他们保存起来(通过subscribe保存). subscribe(subscriber, subscriberMethod); } } } 复制代码
接着看如何查到所有订阅方法的
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); //如果有缓存,则直接返回 if (subscriberMethods != null) { return subscriberMethods; } //无缓存,则通过反射活编译时生成的代码找到订阅方法集合(具体实现稍后在分析,先了解到这就行) if (ignoreGeneratedIndex) { //反射获取 subscriberMethods = findUsingReflection(subscriberClass); } else { //3.0开启索引加速 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; } } 复制代码
当找到所有订阅方法后,会保存订阅方法.我们看保存的具体过程
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<?> eventType = subscriberMethod.eventType; Subscription newSubscription = new Subscription(subscriber, subscriberMethod); CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); //核心map,事件类型对应的Subscription订阅信息. //保存在subscriptionsByEventType(Map,key:eventType(事件类型的class对象); // value:CopyOnWriteArrayList<Subscription> (Subscription集合)) 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) { //添加单个subscription subscriptions.add(i, newSubscription); break; } } List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); //次核心map,订阅者对应的所有事件类型 // 保存在typesBySubscriber(Map, key:subscriber(订阅者); value:List<Class<?>>(事件类型的class对象集合)) typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); if (subscriberMethod.sticky) { if (eventInheritance) {//处理继承 // Existing sticky events of all subclasses of eventType have to be considered. // Note: Iterating over all events may be inefficient with lots of sticky events, // thus data structure should be changed to allow a more efficient lookup // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>). Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<?>, 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); } } } 复制代码
3.3 EventBus的反注册
注册的时候核心是对核心Map和次核心Map,进行添加操作.那么反注册则是删除操作.
public synchronized void unregister(Object subscriber) { //从次核心Map取出所有事件类型 List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { //从核心Map,移除订阅信息 unsubscribeByEventType(subscriber, eventType); } //从次核心Map,移除订阅者 typesBySubscriber.remove(subscriber); } else { logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } } //从核心Map,移除订阅信息的具体过程 private void unsubscribeByEventType(Object subscriber, Class<?> eventType) { List<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions != null) { int size = subscriptions.size(); for (int i = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); if (subscription.subscriber == subscriber) { subscription.active = false; subscriptions.remove(i); i--; size--; } } } } 复制代码
3.4 EventBus发布事件
发布事件,还是跟核心Map有很大关联,接着看发布事件流程.
由于Post方法内部调用了postSingleEvent来,其内部最终调用了postSingleEventForEventType发送事件,这里直接看该方法.
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { //从核心Map,根据事件类型找到所有订阅信息 subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { //遍历调用订阅信息里面的订阅方法 postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; } //postToSubscription的具体实现 private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: //当前线程调用 invokeSubscriber(subscription, event); break; case MAIN: //主线程调用 if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case MAIN_ORDERED: if (mainThreadPoster != null) { mainThreadPoster.enqueue(subscription, event); } else { // temporary: technically not correct as poster not decoupled from subscriber invokeSubscriber(subscription, event); } break; case BACKGROUND: if (isMainThread) { //线程池中调用 backgroundPoster.enqueue(subscription, event); } else { //当前子线程调用 invokeSubscriber(subscription, event); } break; case ASYNC: //线程池中调用 asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } } 复制代码
最后我们对整个EventBus流程做一个总结.
准备工作:
先初始化,使用单例模式双重检查DCL方式创建EventBus对象.
-
注册:
-
获取当前订阅者所有订阅方法(findSubscriberMethods)
- 如果 有缓存 (METHOD_CACHE),则直接返回; 无缓存 ,则通过++反射或者编译时生成的代码++来找到订阅方法集合,然后缓存起来返回.
-
遍历所有方法,将他们保存起来(通过subscribe保存).
- 组合订阅信息,Subscription(订阅信息) = Subscriber(订阅者) + SubscriberMethods(订阅方法)
- 保存在事件类型对应的订阅信息集合的Map中 .(核心Map)
- ++保存在订阅者对应的所有事件类型(次核心Map)++
-
-
反注册:
- 根据当前订阅者,从次核心Map找到所有订阅事件类型.
- 遍历根据订阅事件,从核心Map找到对应所有订阅信息.
- 最后一次移除订阅信息,订阅者.
-
发布事件
- 根据当前事件类型,从核心Map找到所有订阅信息,遍历调用.
3.4 EventBus的3.0索引加速
之前在讲解EventBus的注册过程时,只是简单得讲到获取所有的订阅方法,如果没有缓存时,则从反射或者编译时注解生成的代码中获取.接下来继续看反射相关代码
a. 通过反射获取所有订阅方法
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities //获取所有方法 methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 methods = findState.clazz.getMethods(); findState.skipSuperClasses = true; } //遍历 for (Method method : methods) { int modifiers = method.getModifiers(); //检查是否满足订阅方法的条件. if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<?>[] parameterTypes = method.getParameterTypes(); //获取参数长度 if (parameterTypes.length == 1) { //获取方法注解 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)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length); } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); } } } 复制代码
b. 通过编译时生成的代码获取所有订阅方法
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { //获取订阅信息(getSubscriberInfo方法在编译时索引类里) findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo != null) { //获取所有订阅方法 SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { //继续回去调用反射 findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); } 复制代码
到此为止,我们知道3.0之前通过反射获取订阅的方法,对于性能上是有所欠缺.所以在3.0后提供了(Subscribe Index)索引加速,其实本质就是注解处理器的应用,这样就不用通过反射了,性能上得到了很大的提高.
接下来看如何开启索引. greenrobot.org/eventbus/do…
1. 添加注解处理器:
android { defaultConfig { javaCompileOptions { annotationProcessorOptions { //指定生成的索引文件名以及日志打印 arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex', verbose : 'true' ] } } } } dependencies { implementation 'org.greenrobot:eventbus:3.1.1' annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1' } 复制代码
最后我们在MainActivity准备一些订阅方法方法
@Subscribe(threadMode = ThreadMode.MAIN, priority = 0) public void onDataEventOne(Integer dataEvent) { } @Subscribe(threadMode = ThreadMode.MAIN, priority = 1) public void onDataEventTwo(String dataEvent) { } 复制代码
接着编译下项目,此时在build-generated-source-apt的包下,通过注解处理器生成了我们自己定义的索引文件MyEventBusIndex.java
public class MyEventBusIndex implements SubscriberInfoIndex { private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX; static { //索引Map,key为订阅者,value为订阅者信息 SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>(); //添加订阅者信息到Map中 putIndex( // new SimpleSubscriberInfo(org.jasonhww.eventbusdemo.MainActivity.class, true, new SubscriberMethodInfo[] { //创建订阅方法对象 new SubscriberMethodInfo("onDataEventOne", Integer.class, ThreadMode.MAIN), new SubscriberMethodInfo("onDataEventTwo", String.class, ThreadMode.MAIN, 1, false), })); } //将订阅信息添加到索引Map中 private static void putIndex(SubscriberInfo info) { SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info); } //获取订阅者对应的订阅信息 //此方法就在注册查找所有订阅方法的具体实现. @Override public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) { SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); if (info != null) { return info; } else { return null; } } } 复制代码
接着在初始化EventBus时添加我们配置好的索引.
//方法一,使用自定义的EventBus EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build(); //方法二,使用默认的EventBus,大多数情况下采取这种就行. EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus(); EventBus eventBus = EventBus.getDefault(); 复制代码
2. 注解处理器-创建索引类
在之前分析编译时注解和ButterKnife,都有讲到注解处理器的运用.同样我们依葫芦画瓢来看EventBus是如何利用注解处理器去生成我们的索引类的.
相比之前分析的,这里注解处理器类上多了一个@SupportedOptions(value = {"eventBusIndex", "verbose"})配置,这里对应了最开始gradle的配置.
@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe") @SupportedOptions(value = {"eventBusIndex", "verbose"}) 复制代码
这里直接看注解处理器process方法里,最重要的两步.
- 收集信息
待生成的 java 文件信息存储在methodsByClass中.
private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) { for (TypeElement annotation : annotations) {//遍历所有注解类对应的TypeElement //获取被注解的元素(如MainActivity的onDataEventOne方法对应的元素对象) Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation); for (Element element : elements) { if (element instanceof ExecutableElement) { ExecutableElement method = (ExecutableElement) element; if (checkHasNoErrors(method, messager)) { //获取被注解所在的类 TypeElement classElement = (TypeElement) method.getEnclosingElement(); //存入Map中,生成文件时调用. methodsByClass.putElement(classElement, method); } } else { messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element); } } } } 复制代码
-
生成java文件
与ButterKnife不同,这里使用了JDK的JavaFileObject创建索引类.可以对照之前生成的索引类.
private void createInfoIndexFile(String index) { BufferedWriter writer = null; try { JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index); int period = index.lastIndexOf('.'); String myPackage = period > 0 ? index.substring(0, period) : null; String clazz = index.substring(period + 1); writer = new BufferedWriter(sourceFile.openWriter()); if (myPackage != null) { writer.write("package " + myPackage + ";\n\n"); } writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n"); writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n"); writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n"); writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n"); writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n"); writer.write("import java.util.HashMap;\n"); writer.write("import java.util.Map;\n\n"); writer.write("/** This class is generated by EventBus, do not edit. */\n"); writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n"); writer.write(" private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n"); writer.write(" static {\n"); writer.write(" SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n"); //此里面调用收集信息时存入的Map(methodsByClass) writeIndexLines(writer, myPackage); writer.write(" }\n\n"); writer.write(" private static void putIndex(SubscriberInfo info) {\n"); writer.write(" SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n"); writer.write(" }\n\n"); writer.write(" @Override\n"); writer.write(" public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n"); writer.write(" SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n"); writer.write(" if (info != null) {\n"); writer.write(" return info;\n"); writer.write(" } else {\n"); writer.write(" return null;\n"); writer.write(" }\n"); writer.write(" }\n"); writer.write("}\n"); } catch (IOException e) { throw new RuntimeException("Could not write source for " + index, e); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { //Silent } } } } 复制代码
这样就成功的将订阅者的所有订阅方法都保存在了索引文件了,这样最终注册的时候就不需要再利用反射区查找订阅方法了.
最后还是老规矩做一个小的总结.
所谓索引加速,就是利用注解处理器通过解析注解信息,生成java文件的索引类,将所有订阅的方法保存在索引类而已.
由于本人技术有限,如有错误的地方,麻烦大家给我提出来,本人不胜感激,大家一起学习进步.
参考链接:
以上所述就是小编给大家介绍的《常用轮子之EventBus基本使用及原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法心得:高效算法的奥秘(原书第2版)
(美)Henry S. Warren, Jr. / 爱飞翔 / 机械工业出版社 / 2014-3 / 89.00
【编辑推荐】 由在IBM工作50余年的资深计算机专家撰写,Amazon全五星评价,算法领域最有影响力的著作之一 Google公司首席架构师、Jolt大奖得主Hoshua Bloch和Emacs合作创始人、C语言畅销书作者Guy Steele倾情推荐 算法的艺术和数学的智慧在本书中得到了完美体现,书中总结了大量高效、优雅和奇妙的算法,并从数学角度剖析了其背后的原理 【读者评价......一起来看看 《算法心得:高效算法的奥秘(原书第2版)》 这本书的介绍吧!