首页 > 代码库 > EventBus使用详情、源码分析和注意事项
EventBus使用详情、源码分析和注意事项
基本介绍
EventBus主要用于事件的订阅和发布,主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。官方文档介绍了EventBus的很多优点,归纳一下就是三个优点:小,快和方便。以下会围绕初步使用、使用进阶、源码分析和注意事项来讲解,如果需要快速上手,只要看完初步使用和注意事项就可以了。
初步使用
EventBus的使用非常简单,主要使用到以下三个方法:
//注册EventBus
EventBus.getDefault().register(Object subscriber);
//准备事件处理方法
@Subscribe
public void onEventXXX(Object event){
//处理逻辑
...
}
//发送消息
EventBus.getDefault().post(Object event);
//注销EventBus
EventBus.getDefault().unregister(Object subscriber);
EventBus.getDefault()是获得EventBus的单例
register和unregister中传入的参数是需要绑定的组件(Activity,Fragment,Service都可以)
onEventXXX(Object msg)中event和post()中的event一致,常用的有四种方法:
//将任务执行在UI线程中,例如更新控件 onEventMainThread(); //将任务执行在发布事件的线程 onEventPostThread(); //如果在非UI线程中发布,直接执行在该线程中;如果在UI线程中发布,则会加入后台任务队列,使用线程池一个个调用 onEventBackgroundThread(); //直接加入后台任务,使用线程池调用 onEventAsync()
post(Object event)中的event是我们所需要传递的参数,这里我们可以通过一个实例去理解
利用EventBus传递一个String值,并弹吐司框显示
1. 定义消息类
public class MessageEvent {
public String message;
public MessageEvent(String message) {
this.message = message;
}
public getMsg(){
return message;
}
}
2.准备工作
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
EventBus.getDefault().unregister(this);
super.onDestroy();
}
@Subscribe
public void onEventMainThread(MessageEvent event){
String msg = event.getMsg();
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
}
3.发送消息
...
EventBus.getDefault().post(new MessageEvent("hello wordl!"));
...
使用进阶
讲到这里相信大家都能理解EventBus非常容易使用了吧,其中的event我们可以是获取的Bean类,也可以是关键的变量;但是要注意,不能传递8种基本数据类型,需要使用其封装类来传递(例如int要用Integer),并且onEvent方法在接受的时候,就是以入参event作为标志,如果写了多个onEventXXX(Object event)入参均为同一个event,则在发送消息后,这几个方法都会受到消息。
流程机制
然而EventBus是怎么做到消息的订阅和发布的呢,其中主要使用了反射机制,通过遍历EventBus.getDefault().register(subscriber)中绑定的类,寻找带有@Subscribe并且是public void onEvent开头的方法,把找到的方法在一个HashMap中根据优先度,以subscriber为键,方法为值存储起来,并在post()时遍历map找到符合入参event类型的方法,再利用clazz.invoke()来唤醒(详细代码会在源码解析中叙述)
ThreadMode
如果直接使用onEvent()也可以完成事件的执行,此时需要修改@Subscribe(threadMode = ThreadMode.XXX)中控制onEvent运行的线程,效果等同于重写onEventXXX()方法,EventBus提供了四种线程模式。
- ThreadMode.POSTING //等同于onEventPostThread();
- ThreadMode.MAIN //等同于onEventMainThread();
- ThreadMode.BACKGROUND //等同于onEventBackgroundThread()
- ThreadMode.ASYNC //等同于onEventAsync();
Sticky Events
Sticky Events可以用于需要延迟接收事件的情况。
普通的事件发送出去以后,EventBus便不再持有了,因此后续注册的订阅者无法接收到它。但是sticky事件不同,EventBus会在内存中为每一种事件类型保存一个最新发送的事件,直到后续发送新的sticky事件,把它覆盖掉。因此后续注册的订阅者,依然可以从内存中得到这个已发送的sticky事件,以下为实例。
...
//一个消息在之前被粘性发送了
EventBus.getDefault().postSticky(new MessageEvent("Hello world!"));
...
//现在EventBus注册了另一个组件,例如Activity
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
/**此处将sticky设置为true,一旦Activity被创建后便会直接接受到之前粘性发送的Hello World!*/
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent event) {
tvShow.setText(event.message);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
...
//也可以在任意代码处直接获取消息
MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
...
//也可以手动清除储存下来的stickyEvent
if(stickyEvent != null) {
EventBus.getDefault().removeStickyEvent(stickyEvent);
}
...
优先级
EventBus为同一个线程下发送的消息提供了优先级的设定,如果需要优先处理或者显示,可以再@Subscribe(priority = ?)中设置,priority的默认值为0。注意:不同线程下的优先级是没有影响的。
取消消息的发送
EventBus为取消消息也提供了API,调用EventBus.getDefault().cancelEventDelivery(Object event)就可以完成,通常使用在优先级高的消息,来取消优先级低的消息。
源码解析
EventBus主要的API都已经列举在初步使用中了,让我们一个一个来看。
getDefault(): 标准的单例模式,使用双重判断来防止单例模式在异步获取下的失效的情况
public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }
register(Object subscriber):注册subscriber,主要利用反射来找到带有@Subscribe的onEvent方法
public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
其中最重要的两个方法是findSubscriberMethods和subscribe,逐层往下找,可以找到findSubscriberMethods的核心代码如下:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
//将当前类下所有方法都保存到methods数组中
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
//如果在当前类中没有找到方法,则在其父类中的所有方法储存到methods数组中
methods = findState.clazz.getMethods();
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的方法,如果不是则返回null
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");
}
}
}
以下是subscribe的核心代码:
...
//创建包含传入新方法字段的实体类
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//根据传入的Subscriber的eventType从subscriptionsByEventType中获取封装好的实体类的数组
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//如果有这个数组,则说明注册过了,没有就创建一个数组放进去,这里是关键,其中subscriptionsByEventType是一个存储所有方法的集合,以绑定类的键,方法数组为值
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
...//如果获取的方法数组中有新方法,则说明注册过了,就抛异常
}
//将新传入的实体类按照优先值插入到数组中
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;
}
}
...
//判断是否是粘性方法,如果是就立即执行,checkPostStickyEventToSubscription是执行方法
if (subscriberMethod.sticky) {
if (eventInheritance) {
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);
}
}
与BroadcastReceiver的区别和对比
看到这里大家能明显的看出来,EvnetBus的使用方式和广播基本是如出一辙,都是经过注册,定义事件,发送消息这样的流程,就可以完成消息的发送,那两者有什么区别呢?
- 具体流程
首先让我们看看两者的具体实现流程,以下为广播的流程:
广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;
广播发送者通过binder机制向AMS发送广播;
AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到 BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;
消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。
以下为EventBus的具体实现流程:
初始化时注册EventBus.getDefault().register(this),遍历绑定对象中的所有方法;
用完之后注销EventBus.getDefault().unregister(this),移除HashMap中对应的绑定对象;
中间过程主要就是消息推送和接收,通过EventBus.getDefault().post(param)推送,通过onEventMainThread(param),onEventPostThread(param),onEventBackgroundThread(param),onEventAsync(param)接收并处理。
由于广播会在AMS中进行注册,并在AMS中查找符合条件的BroadcastReceiver,当项目的组件越来越多时,发送一个广播到接受所需要的时间也会变得越来越长,而EventBus只会针对绑定的类进行查找,所以效率明显比Broadcast快很多。
但是当出现跨进程的情况,由于EventBus在跨进程方面会出现各种不同的问题,所以在跨进程的情况下还是使用Broadcast更为稳定。
- 效率对比
那EventBus到底比BroadcastReceiver快多少呢,我们来写一个Demo测试一下:
Demo中有两个Activity,首先从MainActivity跳转到SecondActivity,之后SecondActivity通过EventBus或Broadcast向MainActivity传输发送的时间,并在MainActivity中记录下接收到的时间,并展示在TextView中两者的时间差,代码如下。
public class SecondActivity extends Activity implements View.OnClickListener{
...
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_sendMessage_eventbus:
//记录时间
long time = System.nanoTime();
//EventBus发送消息
EventBus.getDefault().post(new MsgEvent(time));
break;
case R.id.btn_sendMessage_broadcast:
//记录时间
long time2 = System.nanoTime();
Intent intent = new Intent();
intent.setAction("Time");
intent.putExtra("Time",time2);
//Broadcast发送消息
sendBroadcast(intent);
break;
}
...
}
public class MainActivity extends Activity implements View.OnClickListener{
...
@Subscribe
public void onEventMainThread(MsgEvent event){
long l = event.getMsg();
long l1 = System.nanoTime();//记录接受到的时间
long time = l1 - l ;//计算时间差
tvEventBus.setText("eventBus使用的时间为" + time);//展示数据
Toast.makeText(MainActivity.this, "eventBus使用的时间为" + time , Toast.LENGTH_SHORT).show();
}
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
long l = intent.getLongExtra("Time" ,0);
long l1 = System.nanoTime();//记录接受到的时间
long time = l1 - l ;//计算时间差
tvBroadcast.setText("broadcast使用的时间为" + time);//展示数据
Toast.makeText(MainActivity.this, "broadcast使用的时间为" + time , Toast.LENGTH_SHORT).show();
}
};
...
}
基本逻辑如上,结果请看截图:
根据上图可知EventBus大概比broadcastReceiver快30倍到50倍左右,当项目结构更加复杂的时候优势更明显
注意事项
- BUG
1.当post后,onEvent方法处理了多次
(1)检查你绑定类的父类是否也绑定了EventBus
(2)检查你是否忘记注销了
(3)绑定的Fragment被多次添加
如果肉眼没有找到的话,就在注册后打印Log,在注销后也打印Log,看日志中两者的数量,如果注册的Log多于注销的Log,则问题就在于没有注销的绑定类
2.java.lang.NoClassDefFoundError
首先我们要理解为什么会抛这个异常,是因为Android的老版本在调用反射的getDeclaredMethods或getMethods方法时,无法检测到新版本被检测方法的入参,例如 onCreate (Bundle savedInstanceState, PersistableBundle persistentState)中PersistableBundle这个入参,是在API21被引入的,如果在API低于21的机子上调用会抛出以上异常,以下是解决方案。
(1)如果这个方法是生命周期中的onCreate (Bundle savedInstanceState, PersistableBundle persistentState),尽量避免使用,改用 onCreate (Bundle savedInstanceState)。
(2)这个方法是public的,把它修改成非public(如果可以的话)
(3)要么在绑定类中避免定义会产生异常的方法,要么绑定需要绑定的子类
- 混淆
由于混淆引用了反射并需要在绑定的方法中寻找以onEvent开头的方法,所以该方法不能被混淆,只需要在混淆规则中加入以下代码即可。
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
EventBus使用详情、源码分析和注意事项