首页 > 代码库 > 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提供了四种线程模式。

  1. ThreadMode.POSTING //等同于onEventPostThread();
  2. ThreadMode.MAIN //等同于onEventMainThread();
  3. ThreadMode.BACKGROUND //等同于onEventBackgroundThread()
  4. 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的使用方式和广播基本是如出一辙,都是经过注册,定义事件,发送消息这样的流程,就可以完成消息的发送,那两者有什么区别呢?

  • 具体流程

首先让我们看看两者的具体实现流程,以下为广播的流程:

  1. 广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;

  2. 广播发送者通过binder机制向AMS发送广播;

  3. AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到 BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;

  4. 消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。

以下为EventBus的具体实现流程:

  1. 初始化时注册EventBus.getDefault().register(this),遍历绑定对象中的所有方法;

  2. 用完之后注销EventBus.getDefault().unregister(this),移除HashMap中对应的绑定对象;

  3. 中间过程主要就是消息推送和接收,通过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);
}
<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    EventBus使用详情、源码分析和注意事项