EventBus3源码解析(高工视角)

转载请注明出处: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。

类似推荐:

OkHttp3源码解析

Retrofit源码解析

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3 中的事件总线 (EventBus) 并不像 Vue 2 中那样直接可用。但是,你仍然可以创建一个类似的全局事件总线,并在应用程序的不同组件之间进行通信。 一种常见的方法是通过创建一个单独的 Vue 实例来实现事件总线。你可以在应用程序的入口文件中创建该实例,并将其导出以供其他组件使用。 下面是一个示例代码,展示了如何在 Vue 3 中实现一个简单的事件总线: ```javascript // event-bus.js import { createApp } from 'vue'; const app = createApp({}); const eventBus = app.config.globalProperties.$eventBus = app.mount('#app'); export default eventBus; ``` 在上面的代码中,我们先创建了一个空的 Vue 应用程序实例 `app`,然后将其作为事件总线 `eventBus` 导出。我们通过 `app.config.globalProperties` 将 `eventBus` 注册为全局属性,这样我们就可以在任何组件中使用它。 然后,在你想要使用事件总线的组件中,你可以通过 `this.$eventBus` 访问该实例,并使用它来发送和接收事件: ```javascript // ComponentA.vue export default { mounted() { this.$eventBus.$emit('eventName', data); } } ``` ```javascript // ComponentB.vue export default { created() { this.$eventBus.$on('eventName', this.handleEvent); }, methods: { handleEvent(data) { // 处理事件数据 } } } ``` 通过上述代码,`ComponentA` 可以通过 `$emit` 方法触发名为 `'eventName'` 的事件,并传递数据。而 `ComponentB` 则通过 `$on` 方法监听该事件,并在事件发生时执行相应的处理函数 `handleEvent`。 这样,你就可以在 Vue 3 中实现一个类似于 Vue 2 中的事件总线。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值