转载请注明出处:https://blog.csdn.net/m0_37840695/article/details/119647057
EventBus的Github地址
1.EventBus的使用
(摘自EventBus官方)
2.EventBus要满足需求
核心需求:进程内部发送事件
补充需求:
不能引起界面卡顿
不能引起内存泄露
不能引起内存抖动
2.1.需求拆分
2.1.1.发送事件不受线程限制(可以跨线程)
2.1.2.订阅者可以指定在什么线程接收到事件(比如主线程)
2.1.3.不能引起主线程阻塞(或者主线程执行权被争抢)
2.1.4.必须能及时去除对实例的引用(在实例如activity销毁时)
2.1.5.发送事件可以是密集型(高频)操作
2.1.6.高频操作不会引起内存抖动
3.EventBus原理图
(摘自EventBus官方)
4.源码高效解析
本文中讨论的源码版本是V3.2.0
先从简单开始
我们先看这部分的实现:event投送给订阅者,回调onEvent方法
后台投递手-图
异步投递手-图
handler投递手-图
eventBus就是通过这三个投递手,将事件投递给指定线程的订阅者的,满足了2.1.1和2.1.2需求;投递手类被如下调用:
再看post事件部分:
4.2分析剩下的细分需求
4.2.1. 细分需求-“2.1.3.不能引起主线程阻塞(或者主线程执行权被争抢)”
可能在主线程执行的方法都不耗时,唯一可能耗时的是onEvent方法(那不是你写的吗?你得保证它不耗时,否则可能导致界面卡顿,mainLooper处理完一个任务,16ms后不能处理第二个任务,就可能让用户感受到卡顿,所以你得onEvent方法最多不能耗时超过16ms);
由于EventBus的线程池默认是Executors.newCachedThreadPool,其创建线程的优先级是
Thread.NORM_PRIORITY,所以主线程的执行权得到保障。
4.2.2. 细分需求-2.1.4.必须能及时去除对实例的引用(在实例如activity销毁时)
我们看完HandlerPoster类后,发现所有定义在主线程回调的onEvent方法,都会在post(event)后立即执行,开发者必须保证所有在主线程执行的onEvent都是不耗时的,否则可能造成界面卡顿。
4.2.2. 细分需求-2.1.5.发送事件可以是密集型(高频)操作
由于EventBus的线程池默认是Executors.newCachedThreadPool,当池中没有可用线程时,会立即创建新线程来执行任务,此线程池的最大线程数是Integer.MAX_VALUE,所以如果onEvent耗时并且event高频post,会导致创建线程太多,导致资源耗尽。可以从业务角度考虑,如果是这种情况,建议给EventBus设置自定义的线程池。Executors.newCachedThreadPool被阿里巴巴的《java开发手册》明确禁止使用。
4.2.2. 细分需求-2.1.6.高频不会引起内存抖动
从EventBus.post->postSingleEvent->postSingleEventForEventType->投递手.enqueue->投递手.run,临时变量只有PendingPost和FindState,而该2个类的实例是池化了的:
5 Subscriber Index
本文遗漏了一个关键点,EventBus如何找到使用了@Subscribe注解了的onEvent方法?
在EventBus3.0之前,是通过在运行时反射查找Subscribe注解找到的
EventBus3.0之后, 官方推荐subscriber index
如此,eventbus的注解处理器会在编译期找到所有被Subscribe注解了的方法(订阅点),并会生产一个MyEventBusIndex.java文件,所有找到的订阅点会保存该文件中,官方demo生产的文件:
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusMain.class,
true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEventMainThread", TestEvent.class, ThreadMode.MAIN),
}));
putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusMainOrdered.class,
true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEvent", TestEvent.class, ThreadMode.MAIN_ORDERED),
}));
putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
}));
putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusBackground.class,
true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEventBackgroundThread", TestEvent.class, ThreadMode.BACKGROUND),
}));
putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.SubscribeClassEventBusDefault.class,
true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEvent", TestEvent.class),
}));
putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
}));
}
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;
}
}
}
将index文件传入event:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
之后还是通过EventBus.getDefault()获取单例使用。
如此,订阅点查找器就很好找订阅点了:
6 此时该有的收获
收获1
收获2
如果你得组员问你:我不用单例模式(不通过getDefault方法获取eventBus实例)而是new一个新实例使用,除了浪费内存,会有别的问题吗?
答案:你用eventbus实例post的事件,只有你用同一个eventbus实例注册了的对象(如activity实例)才能收到;换句话说,register某activity的eventbus实例,和发送事件的eventbus实例不同,该activity实例是无法收到事件的。
收获3
还记得这张图吗?
当你看到图中最下方那个注释时 ,应该发现了,当你的activity交给EventBus注册(register)那一刻,activity中Subscribe注解的粘性事件方法将被立即调用,所以你想象下,如果你在setContentVidew方法前EventBus.register(activity)会怎样?你的粘性事件回调会在视图未加载的情况下被调用,大部分业务逻辑应该都希望粘性事件回调发生在视图加载好后,这种情况的建议是视图渲染完再注册:view.post(run{EventBus.register(activity)})。
7.注解处理器
看到这儿,你会发现EventBus整个框架都比较简单,其实这个注解处理器也很简单^_^
将注解处理器声明在META-INF/services/javax.annotation.processing.Processor 文件中。
在编译器查找注解点并保存到文件中的方式,被很多框架所使用,包括另外一个你很熟悉butterknife。
类似推荐: