首页 > 编程语言 >EventBus源码赏析一 —— 基本使用

EventBus源码赏析一 —— 基本使用

时间:2023-06-22 11:04:37浏览次数:32  
标签:事件处理 发送 源码 线程 事件 EventBusTest EventBus event 赏析


EventBus简介

EventBus是一种用于Android的发布/订阅事件总线。我们经常用来在不同界面,不同线程传递数据,它解耦了事件发送方和事件处理方。 虽然Android本身提供了LocalBroadcastReceiver类可以实现类似的功能,但是LocalBroadcastReceiver使用起来稍微繁琐,而且传递数据大小也受intent限制。

EventBus角色

EventBus主要有一下三个角色:

  1. Event 事件,可以是任意引用类型,不能是基础数据类型如int,char。他是EventBus传递,处理的对象。
  2. Subscriber 事件订阅者,订阅者必须有@Subscribe注解的订阅方法,用来处理事件。
  3. Publisher 事件发布者,可以在任意线程发布事件。事件订阅者收到后会进行处理。

基本使用

添加依赖

implementation 'org.greenrobot:eventbus:3.3.1'

定义事件

public class MessageEvent {}

这一步不是必须的,因为事件可以是任意引用类型,所以可以使用一些已经存在的类作为事件类,比如String

注册/反注册

// 注册
protected void onCreate(@Nullable  Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    EventBus.getDefault().register(this);
}
// 反注册
protected void onDestroy() {
    super.onDestroy();
    EventBus.getDefault().unregister(this);
}

注册完了之后必须反注册,否则造成内存泄漏,在没有反注册之前,不能重复注册,不然会抛出异常。

事件处理

@Subscribe(threadMode = ThreadMode.MAIN,priority=1,sticky=true)
public void handleMessage(MessageEvent event) {
    Log.i("EventBusTest", event.toString());
}

在上一步注册的类里面添加一个方法,用于事件处理,方法名任意,但是只能有一个参数,而且修饰符必须是public的,不能使用static,abstract修饰,然后使用@Subscribe注解。@Subscribe提供了一些可选参数让我们在不同场景选用。

事件发送

EventBus.getDefault().post(new MessageEvent());

在需要发送事件的地方调用Eventbuspost()方法发送事件。

@Subscribe参数详解

@Subscribe有3个参数,threadMode用于指定线程模型,sticky指定是否处理粘性事件,priority指定优先级。

threadMode

EventBus允许事件发送的线程和事件处理的线程不一样,我们可以通过threadMode指定,ThreadMode是一个枚举类型,有以下五种取值:

  • POSTING 订阅者的订阅方法将在发布事件的同一线程中被调用,这种模式下不会涉及线程切换,所以开销比较小,但是可能发布事件的线程是主线程,所以需要避免在订阅方法中处理耗时操作
  • MAIN 订阅方法将在UI线程被调用,如果发布事件的线程也是UI线程,则可以直接执行调用订阅方法,否则会使用Handler切换到UI线程
  • MAIN_ORDEREDMAIN类似,只不过不管发布线程在不在UI线程,都会由Handler处理,这确保了post()的调用是非阻塞的
  • BACKGROUND 订阅方法将在非UI线程被调用,如果发布事件的线程在UI线程,则会创建一个线程来执行订阅方法,否则直接执行,为避免后续事件的发送或调用,需要避免在订阅方法中处理耗时操作
  • ASYNC 订阅方法的调用线程将独立于发送事件的线程,所以这种情况下可以做耗时操作,但是需要避免在同一时间进行大量的异步订阅,控制并发线程的数量。

ThreadMode.POSTING

事件发送

new Thread(() -> EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName()))).start();

在发布线程处理事件

@Subscribe(threadMode = ThreadMode.POSTING)
public void postingButton(MessageEvent event) {
    Log.i("EventBusTest", "事件发送线程:" + event.name + ",事件处理线程:" + Thread.currentThread().getName());
}

结果

EventBusTest: 事件发送线程:Thread-7,事件处理线程:Thread-7

可以看出订阅者的订阅方法将在发布事件的同一线程中被调用。

ThreadMode.MAIN

事件发送

long start = System.currentTimeMillis();
EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName()));
long end = System.currentTimeMillis();
Log.i("EventBusTest", "间隔 " + (end - start) + "ms 才后发送下一个事件");
new Thread(() -> EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName()))).start();

处理事件

@Subscribe(threadMode = ThreadMode.MAIN)
public void mainButton(MessageEvent event) {
    Log.i("EventBusTest", "事件发送线程: " + event.name + ",事件处理线程: " + Thread.currentThread().getName());
    SystemClock.sleep(1000);
}

结果输出

EventBusTest: 事件发送线程: main,事件处理线程: main
EventBusTest: 间隔 1001ms 才后发送下一个事件
EventBusTest: 事件发送线程: Thread-7,事件处理线程: main

可以看出,即使发送线程不在主线程,最后处理的时候也会切换到主线程,而且因为处理事件的方法做了耗时操作,所以隔了1s多才发送第二个事件。

ThreadMode.MAIN_ORDERED

事件发送与上面一样,处理事件的方法调整下,将 threadMode 设置为 ThreadMode.MAIN_ORDERED

@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void mainOrderButton(MessageEvent event) {
    Log.i("EventBusTest", "事件发送线程: " + event.name + ",事件处理线程: " + Thread.currentThread().getName());
    SystemClock.sleep(1000);
}

结果输出

EventBusTest: 间隔 0ms 才后发送下一个事件
EventBusTest: 事件发送线程: main,事件处理线程: main
EventBusTest: 事件发送线程: Thread-8,事件处理线程: main

可以看出MAIN_ORDEREDMAIN相似的是都是切换到MAIN线程处理事件,不一样的是ThreadMode.MAIN这种模型下,前一个事件处理可能会阻塞下一个事件的发送,而ThreadMode.MAIN_ORDERED不会

这里说的是“可能阻塞”,至于会不会阻塞取决于事件的发送线程,如果一个事件在非主线程发送,结果其实与MAIN_ORDERED一样,不会阻塞,如果是在主线程发送,线程模型又是指定的ThreadMode.MAIN,则会先立即执行处理此事件的方法,等到方法执行完毕,才会发送下一个事件。

ThreadMode.BACKGROUND

事件发送

new Thread(() -> {
    long start = System.currentTimeMillis();
    EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName(), start));
    long end = System.currentTimeMillis();
    Log.i("EventBusTest", "子线程发送耗时 " + (end - start) + "ms");
}).start();
//保证上下两种情况互不干扰
SystemClock.sleep(2000);
long start = System.currentTimeMillis();
EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName(), System.currentTimeMillis()));
long end = System.currentTimeMillis();
Log.i("EventBusTest", "主线程发送耗时 " + (end - start) + "ms");

这里在非MAIN线程和MAIN线程都分别发送了事件,主要是为了体现发布线程对结果的影响。

事件处理

@Subscribe(threadMode = ThreadMode.BACKGROUND,priority = 1)
public void bgButton(MessageEvent event) {
    long time = System.currentTimeMillis() - event.sendTime;
    Log.i("EventBusTest", "优先级1:事件发送线程: " + event.name + ",事件处理线程: " + Thread.currentThread().getName() + ",发送" + time + " m后才开始处理");
    SystemClock.sleep(500);
}
@Subscribe(threadMode = ThreadMode.BACKGROUND,priority = 2)
public void bgButton1(MessageEvent event) {
    long time = System.currentTimeMillis() - event.sendTime;
    Log.i("EventBusTest", "优先级2:事件发送线程: " + event.name + ",事件处理线程: " + Thread.currentThread().getName() + ",发送" + time + " m后才开始处理");
    SystemClock.sleep(500);
}

结果输出

EventBusTest: 优先级2:事件发送线程: Thread-7,事件处理线程: Thread-7 发送1 m后才开始处理
EventBusTest: 优先级1:事件发送线程: Thread-7,事件处理线程: Thread-7 发送501 m后才开始处理
EventBusTest: 子线程发送耗时 1002ms
EventBusTest: 主线程发送耗时 4ms
EventBusTest: 优先级2:事件发送线程: main,事件处理线程: pool-3-thread-1 发送4 m后才开始处理
EventBusTest: 优先级1:事件发送线程: main,事件处理线程: pool-3-thread-1 发送506 m后才开始处理

从日志可以看出

  • ThreadMode.BACKGROUND线程模型下如果发布线程不是MAIN线程,则事件处理是在当前线程进行,而且同一个线程内是串行执行的,上一个执行完了才会执行下一个。
  • 如果发布线程是MAIN线程,则事件处理线程会切换到非MAIN线程,而且事件处理不会阻塞MAIN线程,同一个线程内也是串行执行。

ThreadMode.ASYNC

事件发送与上面一致,事件处理方法体也一样,只是修改下threadMode

@Subscribe(threadMode = ThreadMode.ASYNC)

结果输出

EventBusTest: 子线程发送耗时 1ms
2EventBusTest: 事件发送线程: Thread-7,事件处理线程: pool-3-thread-1 发送0 m后才开始处理
EventBusTest: 事件发送线程: Thread-7,事件处理线程: pool-3-thread-2 发送0 m后才开始处理
EventBusTest: 主线程发送耗时 2ms
EventBusTest: 事件发送线程: main,事件处理线程: pool-3-thread-1 发送1 m后才开始处理
EventBusTest: 事件发送线程: main,事件处理线程: pool-3-thread-2 发送0 m后才开始处理

ThreadMode.BACKGROUND相似的是,事件处理线程都不会是MAIN线程,不一样的是即使ThreadMode.ASYNC是在非MAIN线程发送的事件,处理事件的线程也不会是原线程,而且事件处理是异步的,不会阻塞原线程,多个事件处理方法也是并行执行。

sticky

粘性事件是指发送了事件之后在注册订阅者,订阅者依然能收到事件。

需要注意的是粘性事件是以参数类型来缓存的,同一类型的事件只会保留最近的一个。

priority

EventBus可以指定事件处理方法的优先级。默认情况下为0。数值越大优先级越高,优先级只有在相同的线程模式下才有意义。

首先我们发送一个事件

MessageEvent event = new MessageEvent(Thread.currentThread().getName());
event.info = "项目100天完成";
EventBus.getDefault().post(event);

然后定义三个不同优先级的事件处理方法

@Subscribe(priority = 0,threadMode = ThreadMode.POSTING)
public void priorityButton0(MessageEvent event) {
    Log.i("EventBusTest","码农收到消息: "+event.info);
}
@Subscribe(priority = 1,threadMode = ThreadMode.POSTING)
public void priorityButton1(MessageEvent event) {
    Log.i("EventBusTest","经理收到消息: "+event.info);
    EventBus.getDefault().cancelEventDelivery(event);
}
@Subscribe(priority = 2,threadMode = ThreadMode.POSTING)
public void priorityButton2(MessageEvent event) {
    Log.i("EventBusTest","主管收到消息: "+event.info);
    event.info = "项目10天完成";
}

既然事件是按照优先级处理的,而且处理的时候能拿到事件本身,那么高优先级的就可以对事件进行修改和取消,所以结果输出如下:

EventBusTest: 主管收到消息: 项目100天完成
EventBusTest: 经理收到消息: 项目10天完成

优先级为2的先收到原始事件,他处理完成后对事件进行了修改,优先级为1的收到的就是他修改之后的事件,在优先级为1的方法处理完成之后将事件取消,导致优先级为0的就收不到事件了。

需要注意的是事件的取消是有限制的

  • 它的线程模型必须是ThreadMode.POSTING
  • 取消的事件不能为null并且必须和发送中的事件一样。
  • 只有在事件发送中才能取消,否则事件都发送完了,取消了个寂寞。

订阅者索引

经过上面的使用我们基本掌握了EventBus常见用法,但是这并没有享受到EventBus3.x给我们带来的性能提升。

我们知道EventBus3.0之后就引入了编译时注解,避免了反射查找订阅方法带来的性能损耗。虽然上面我们引入的是3.3.1的包,但是查找订阅方法使用的依然是反射。虽然默认情况下EventBus3.x会优先使用编译时注解,但是在上面的使用过程中我们并没有提供订阅者索引,导致最后降级为反射查找。

要想享受到性能提升,首先需要配置索引类的信息

android {
    defaultConfig {
            kapt  {
                arguments  {
                    arg("eventBusIndex","com.example.test.MyEventBusIndex")
                }
        }
    }
}
dependencies {
	kapt 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}

build之后,EventBus会生成MyEventBusIndex文件。里面包含了我们的订阅方法的信息。

然后通过EventBusBuilder配置索引类

EventBus eventBus= EventBus.builder().addIndex(new MyEventBusIndex()).build()

虽然通过EventBusBuilder#buildr()可以获取EventBus实例,但是我们在平时使用更多的是EventBus.getDefault()获取,为了让getDefault()方法获取的实例设置了索引类,我们需要使用installDefaultEventBus()方法初始化默认实例defaultInstance

installDefaultEventBus()只能在defaultInstancenull的时候调用,所以一般在Application#onCreate()中调用。

事件继承

默认情况下,发送一个事件,该事件类父类的订阅方法也能得到调用,如果想避免这种情况,只需要调用EventBusBuilder#eventInheritance()方法将eventInheritance属性设置为false就可以了。


标签:事件处理,发送,源码,线程,事件,EventBusTest,EventBus,event,赏析
From: https://blog.51cto.com/u_16163452/6534420

相关文章

  • Android binder 机制驱动核心源码详解
    前言应用程序中执行getService()需与ServiceManager通过binder跨进程通信,此过程中会贯穿Framework、Natve层以及Linux内核驱动。binder驱动的整体分层如上图,下面先来宏观的了解下getService()在整个Android系统中的调用栈,ServiceManager本身的获取:与ServiceManage......
  • Android - Jetpack ViewModel源码探秘
    ViewModel使用场景当横竖屏切换时,希望数据不丢失,可以用ViewModel当成存储媒介;可作为Activity&Fragment通讯的媒介;ViewModel的创建//Activity中构建MyViewModelViewModelProvider(this).get(MyViewModel::class.java)//ViewModelProviders类中publicViewModelProvider(@NonNu......
  • 牛掰,阿里P7程序员花了半个月,编成这份1880页的《Android百大框架源码解析》,快来收藏
    为什么要深入了解源码?只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是浮于表象,这对我们的知识体系的建立和完备以及实战技术的提升都是不利的。真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读Android系统源码,还包括各种优秀的开源库。一方面,这些作品都......
  • Dubbo 源码安装与编译
    我这里通过github的客户端工具下载到了下面目录。/Users/ghj1976/project/github/alibaba/dubbo在dubbo的根目录下,执行mvninstall注意,这里执行的是,跳过测试。mvninstall-Dmaven.test.skip=truemvninstall在本地Repository中安装jar参考:http://www.oracle.com/technetw......
  • 医院信息化手麻系统源码
    手麻系统作为医院信息化系统的一环,由监护设备数据采集系统和麻醉信息管理系统两个子部分组成。手麻信息系统覆盖了患者术前、术中、术后的手术过程,可以实现麻醉信息的电子化和手术麻醉全过程动态跟踪。以服务围术期临床业务工作的开展为核心,通过与床边监护设备以及医院HIS、LIS、PA......
  • 手撕ArrayList底层源码
    publicabstractclassAbstractList<E>extendsAbstractCollection<E>implementsList<E>{//外部操作数protectedtransientintmodCount=0;//2}publicclassArrayList<E>extendsAbstractList<E>implementsList<E>{......
  • LinkedList底层源码
    publicabstractclassAbstractList<E>extendsAbstractCollection<E>implementsList<E>{//外部操作数protectedtransientintmodCount=0;//0}publicabstractclassAbstractSequentialList<E>extendsAbstractList<E>{......
  • 手撕Vector底层源码
    publicabstractclassAbstractList<E>extendsAbstractCollection<E>implementsList<E>{//外部操作数protectedtransientintmodCount=0;}publicclassVector<E>extendsAbstractList<E>implementsList<E>{//元素......
  • CentOS7 源码编译安装 Python 3.8.10,开启 SSL 功能
    背景CentOS7自带的Python3,或者通过yum安装的Python3,可能会有无法使用ssl的问题:$python3Python3.8.10(default,Jun132023,14:51:15)[GCC11.2.120220127(RedHat11.2.1-9)]onlinuxType"help","copyright","credits"or"license&qu......
  • 直播平台搭建源码,uni中使用轮播图
    直播平台搭建源码,uni中使用轮播图 <swiperclass="swiper"style="height:90rpx;"circularvertical="true"   :autoplay="true":interval="3000":duration="1000"><swiper-itemv-for="(item,index)in......