EventBus源码分析

361 阅读8分钟

EventBus作为一个优秀的事件传递库,是一种用于Android的事件发布-订阅总线,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。里面的代码实现也指的我们深入研究:

双重校验锁

    static volatile EventBus instance;
     public static EventBus getDefault() {
        if (instance == null) {
            synchronized (EventBus.class) {
                if (instance == null) {
                    instance = new EventBus();
                }
            }
        }
        return instance;
    }

使用volatile关键字能防止指令重排序,如果没有该关键字可能造成在高并发情况下异常发生,问题出现在new操作中,我们以为的new操作应该是:

1.分配一块内存M

2.在内存M上初始化EventBus对象

3.然后在M的地址赋值给instance变量

但是实际上优化后的执行路径确是这样的:

1.分配一块内存M

2.将M的地址赋值给instance变量

3.最后在内存M上初始化EventBus对象

优化后可能导致在第一个if上直接返回没有初始化过的instance,那么这个时候我们访问instance的成员变量就可能触发空指针异常。

关键方法的分析

register(Object object): 根据object找到该类以及父类的所有有添加Subscribe注解的方法,然后添加到一个list当中,SubcribeMethod对象的参数为线程模式ThreadMode,回调方法Method,方法中参数类型Type(具体的Event)。最后再放到Map当中,key为object,value为list。一个List中存储着各种带注解的方法。EventBus2.+是反射调用,EventBus3.+是编译时注解器来处理。

post(Object event): 其实就是遍历所有Map中的key值,然后在根据具体的key值,拿到对应的list数组,如果数组中的SubscribeMethod中的type和event是同一类型或者父类,就进行反射调用(EventBus2.+和EventBus3.+都得反射调用)。线程切换用handler和线程池结合runnable进行切换。

postSticky(Object event): 最后还是调用的post()方法,只是多加了一个添加到粘性类表中,每个页面打开,会检查粘性列表的内容,然后进行分发。粘性事件接收后一定要记得移除。

优先级

@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
   //do something
}

取消事件传递

@Subscribe(priority = 1000,threadMode = ThreadMode.POSTING)
public void onEvent(MessageEvent event){
     textView.setText(event.message);//设置接收的数据
    //取消事件传递,则低级别的事件无法接收到信息,只有在threadMode = ThreadMode.POSTING情况下
    EventBus.getDefault().cancelEventDelivery(event) ;
}

ThreadMode(五种)

代码如下:

 /**
  * subscription:发布订阅的事件处理器
  * event:当前发布的事件
  * isMainThread:是否是在主线程中
  */
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }
  • MAIN:这个模式是无论发送消息是在主线程还是子线程,接收消息永远都在主线程当中。进行UI操作

  • POSTING:这个模式是EventBus默认的线程模式,跟发送消息是在同一个线程中,如果是在主线程发送的消息,接收也在主线程,如果是在子线程中发送的消息,那么接收也在同一个子线程中。

  • MAIN_ORDERED:订阅者将在Android的主线程中调用。该事件总是排队等待以后交付给订阅者,因此对post的调用将立即返回。这为事件处理提供了更严格且更一致的顺序(因此名称为MAIN_ORDERED)。

    例如,如果您在具有MAIN线程模式的事件处理程序中发布另一个事件,则第二个事件处理程序将在第一个事件处理程序之前完成(因为它是同步调用的 - 将其与方法调用进行比较)。

    使用MAIN_ORDERED,第一个事件处理程序将完成,然后第二个事件处理程序将在稍后的时间点调用(一旦主线程具有容量),使用此模式的事件处理程序必须快速返回以避免阻塞主线程。

  • BACGROUND:接收消息永远在子线程中,如果发送消息是在主线程中,则新开辟新的子线程来接收消息,如果发送消息是在子线程中,则接收消息是在同一子线程中。

  • ASYNC:接收消息永远在独立的子线程中,无论发送消息是在主线程还是子线程中都会新开辟新的子线程来接收消息

EventBus3中索引的使用和分析

首先来看看他们的性能区别,这来截取网络上的一张图

可以看到,EventBus3 在编译的时候为注册类构建了一个索引,效率得到了大幅提升。

如何使用,在app的build.gradle中加入如下配置:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                // 根据项目实际情况,指定辅助索引类的名称和包名
                arguments = [ eventBusIndex : 'org.greenrobot.eventbus.EventBusTestsIndex' ]
            }
        }
    }
}
dependencies {
    compile 'org.greenrobot:eventbus:3.1.1'
    // 引入注解处理器
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

如果使用kotlin,是用kapt

apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied

dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1'
    kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

kapt {
    arguments {
        arg('eventBusIndex', 'org.greenrobot.eventbus.EventBusTestsIndex')
    }
}

如果版本级别较低则使用(不推荐使用,应该升级gradle)

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    compile 'org.greenrobot:eventbus:3.1.1'
    apt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

apt {
    arguments {
        eventBusIndex "org.greenrobot.eventbus.EventBusTestsIndex" #这里要修改为你项目的包名
    }
}

配置完成后build项目,可以找到该文件

在Application中进行初始化

EventBus eventBus = EventBus.builder().addIndex(new EventBusTestsIndex()).build();

也可以拥有多个索引类

EventBus eventBus = EventBus.builder()
    .addIndex(new EventBusTestsIndex())
    .addIndex(new MyEventBusLibIndex()).build();

在第一次使用默认EventBus实例之前,这只能执行一次。对installDefaultEventBus()的后续调用 将引发异常。这可确保您的应用中的行为一致。所以应该在Application.class中使用配置和使用索引。

也要记得做好代码混淆

-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);
}

在3.0版本中,EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe注解,并解析和处理其中所包含的信息,然后生成java类来保存订阅者中的事件响应函数,这样就比在2.+版本运行时使用反射来获得订阅者中所有事件响应函数的速度要快。

框架中的一些细节点

  • getDeclaredMehods和getMethods

    作者两个方法都有采用,可以看看下面的说明和作者的写法,

    getDeclaredMethods:获取当前类的所有声明的方法,包括public、protected和private修饰的方法。需要注意的是,这些方法一定是在当前类中声明的,从父类中继承的不算,实现接口的方法由于有声明所以包括在内。

    getMethods:获取当前类和父类的所有public的方法。这里的父类,指的是继承层次中的所有父类。比如说,A继承B,B继承C,那么B和C都属于A的父类。

    private void findUsingReflectionInSingleClass(FindState findState) {
            Method[] methods;
            try {
                // This is faster than getMethods, especially when subscribers are fat classes like Activities
                methods = findState.clazz.getDeclaredMethods();
            } catch (Throwable th) {
                // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
                try {
                    methods = findState.clazz.getMethods();
                } catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad...
                    String msg = "Could not inspect methods of " + findState.clazz.getName();
                    if (ignoreGeneratedIndex) {
                        msg += ". Please consider using EventBus annotation processor to avoid reflection.";
                    } else {
                        msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
                    }
                    throw new EventBusException(msg, error);
                }
                findState.skipSuperClasses = true;
            }
      ...
      ...
    }  
    

    作者两个方法都有用,先用getDeclaredMethods,如果出现异常,就继续使用getMethods方法。可以看到为什么我们会使用该框架,因为成熟度都高很多,遇到的各种问题也五花八门。作者也在里面注释表明了issue号,其实两个方法都有使用,作者一开始使用的是getMethods,后面改成getDeclaredMehods导致了Fatal Exception: java.lang.NoClassDefFoundError android/telephony/CellInfoGsm异常,也有人在issue中解释了为什么报这个错误:

    SubscriberMethodFinder#findSubscriberMethods used to use Class#getMethods(), which doesn't check method signatures (for things such as argument types that don't exist). This was changed to use Class#getDeclaredMethods(), which does check, and throws an exception if something goes wrong.
    

    是因为getDeclaredMethods中有进行校验方法签名,所以可能抛出该异常,至于为什么会产生该异常可以看下面这个博客,写的很清楚 blog.csdn.net/jamesjxin/a… 因为getMethods会拿到当前类以及继承的父类所有的public方法,所以其实调用一次方法就可以了,然后再过滤一下就能拿到所有的带注解的方法,所以也有人还是建议还是使用getMethods方法,后面作者回答了自己经过验证getMethods会比getDeclaredMethods慢很多,下面我们来做一下验证:可以看到,当我们使用getMethods方法的时候,获取到的当前类以及父类中的public方法能有四百多个,这效率怎么能高呢!

而使用getDeclaredMethods只是调用了当前类的所声明的方法,继承的不算,只是调用了少数的几个方法,这里就不贴图了。

如果觉得不错,就点个赞吧~