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);
}
时间: 2024-10-10 05:15:07

EventBus使用详情、源码分析和注意事项的相关文章

EventBus 2.4 源码分析

EventBus简介 本篇基于EventBus 2.4撰写. Android optimized event bus that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality. 上面是从官方repo拉来的代码,大致是说简化的组件之间的交流通信,减少代码,提高质量. 其实和EventBus最早是在qzone的代码里认识的,空间内部有一个

EventBus for Android 源码分析

上文实例讲解EventBus for Android介绍了EventBus的基本用法,本文将介绍EventBus的实现原理.EventBus的实现主要围绕两个函数register和post,下面分别介绍之. 1 register(Object subscriber) 功能 注册subscriber中以onEvent开头的方法 代码: private synchronized void register(Object subscriber, boolean sticky, int priority

EventBus 3.0 源码简要分析

EvenBus 可以在不同模块间传递信息,减少接口的使用. 一.使用例子 <span style="font-size:18px;">public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.la

EventBus 3.0使用与源码分析

EventBus简介 EventBus is a publish/subscribe event bus optimized for Android. EventBus 是一个基于发布/订阅模式的事件总线.其模型图如下 从图可知,EventBus分为四个角色,消息发布者.事件总线.事件.消息订阅者.消息发布者把Event(消息)post(发送)到事件总线,事件总线维护一个事件队列,把Event发送到Subscriber(消息订阅者),调用其onEvent方法进行相应的处理. 1.特点 - 简化组

EventBus框架源码分析

开源项目 上周又手动撸了一遍EventBus实现,同时上传EventBus的中文注释源码到Github上,欢迎大家fork&star. EventBusAnalysis EventBus 基础概念 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化Android事件传递.事件传递既可以用于Android四大组件间的通讯,也可以用于用户异步线程和主线程间通讯等. 传统的事件传递方法包括:Handler,BroadCastReceiver,interface回调,相比

EventBus源码分析

  一.         EventBus简介 1.1.EventBus EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化Android 事件传递,这里的事件可以理解为消息,本文中统一称为事件.事件传递既可用于 Android 四大组件间通讯,也可以用户异步线程和主线程间通讯等等. 传统的事件传递方式包括:Intent.Handler.BroadCastReceiver.Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将

8.源码分析---从设计模式中看SOFARPC中的EventBus?

我们在前面分析客户端引用的时候会看到如下这段代码: // 产生开始调用事件 if (EventBus.isEnable(ClientStartInvokeEvent.class)) { EventBus.post(new ClientStartInvokeEvent(request)); } 这里用EventBus调用了一下post方法之后就什么也没做了,就方法名来看是发送了一个post请求,也不知道发给谁,到底有什么用. 所以这一节我们来分析一下EventBus这个类的作用. 首先我们来看一下

Android:Otto源码分析

Otto源码分析 Otto是一个轻量级的EventBus,它的使用非常简单,我们使用一个Bus的单例,所有需要产生事件(@Produce bus.post(new YourEvent(-)))或者处理事件(@Subscribe)的对象,在create时register,销毁destroy时unregister即可. 使用 @Subscribe 订阅事件,也就是事件的处理者,它有且仅有一个参数YourEvent,每一个Subscribe对应处理一个YourEvent.Event用于连接(匹配)po

redis 源码分析(一) 内存管理

一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是非常重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中统一使用zmalloc,zfree一系列函数,其对应的源码在src/zmalloc.h和src/zmalloc.c两个文件中,源码点这里. 二,redis内存管理源码分析 redis封装是为了屏蔽底层平台的差异,同时方便自己实现相关的函数,我们可以通过src/zmalloc.h 文件中的相关宏定义