文章基于EventBus 3.0讲解。
首先对于EventBus的使用上,大多数人还是比较熟悉的。如果你还每次烦于使用接口回调,广播去更新数据,那么EventBus可以帮助你解决这个问题。
第一篇主要将一些用法和注解
EventBus源码解析系列
EventBus源码解析(一)关于用法和注解
EventBus源码解析(二)register与unregister
EventBus源码解析(三)Post方法和注解处理器
一. 使用
先从平常我们的使用方法看,Service处理数据,处理完发给Activity做显示
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv);
EventBus.getDefault().register(this);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
Intent intent = new Intent(MainActivity.this , TestService.class);
startService(intent);
}
} , 3000);
Log.e(TAG, "onCreate: " );
}
@Subscribe
public void onEventMainThread(String s){
Log.e(TAG, "onEventMainThread: " + s );
tv.setText(s);
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
在onCreate调用了EventBus.getDefault().register(this);
,在onDestory解除了注册。注册之后就可以在@Subcribe
注解下面的方法接收到。而发送事件则是在Service
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
EventBus.getDefault().post("Service Test");
return super.onStartCommand(intent, flags, startId);
}
Service和Activity之间的通讯通过EventBus就可以很简单的实现出了,如果换成以前的话,则是使用
- 接口回调
- IBinder
- 广播
来进行联系,但是如果这样的话一些代码上就会显得有些冗余,而使用EventBus则可以很简便。
此外,这EventBus的版本迭代上,在3.0则是采用了注解来进行监听事件。
上面那个监听方法
@Subscribe
public void onEventMainThread(String s){
Log.e(TAG, "onEventMainThread: " + s );
tv.setText(s);
}
只要通过@Subcribe注解的方法,且类型是public的,方法名可以自由取
@Subscribe
public void test(String s){
Log.e(TAG, "onEventMainThread: " + s );
tv.setText(s);
}
而在3.0之前则默认了onEvent+类型 的方法名,然后通过反射来获取对应的方法。
//3.0前的版本
public void onEventMainThread(String s){
Log.e(TAG, "onEventMainThread: " + s );
tv.setText(s);
}
二.注解
这里主要说说注解的使用与实例。没知道注解的可以了解的。
前面讲到了EventBus的一个注解@Subscribe
,点进去看下类型
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
...
boolean sticky() default false;
...
int priority() default 0;
}
没了解过注解的话可能会有点懵。
注解的语法其实比较简单,除了@符号的使用外,它基本与Java固有的语法一样。Java SE5则内置了三种标准的注解
- @Override:表示当前的方法将覆盖超类中的方法
- @Deprecated:使用了注解为它的元素编译器将发出警告,因为@Deprecated注解是被弃用的代码,不被赞成
- @SuppressWarnings:关闭编译器警告信息
对于上述的三个注解大家在日常开发中算是经常见到的,点进去看他们的结构也和@Subscribe差不多.
Java提供了4种注解
-
@Target :表示该注解可以用于什么地方,可能的
ElementType
参数有:- CONSTRUCTOR:构造器的声明
- FIELD:域声明(包括enum实例)
- LOCAL_VARIABLE:局部变量声明
- METHOD:方法声明
- PACKAGE:包声明
- PARAMETER:参数声明
-
@Retention:表明需要在什么级别保存该注解信息。可选的
RetentionPolicy
参数包括: @Document:将注解包含在Javadoc中
@Inherited:允许子类继承父类中的注解
定义一个注解的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
除了@符号,注解很像是一个接口。定义注解的时候需要用到元注解,上面用到了@Target和@RetentionPolicy,它们的含义在上面已给出。
在注解中一般会有一些元素来表示某些值,注解里面的元素看起来很像接口的方法,唯一区别在于注解可以为其制定默认值,没有元素的注解称为标记注解,上面的Test就是一个标记注解。
注解的可用的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。而且元素不能使用null作为默认值。注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。
比如
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
public String id();
public String userName() default "Hohohong";
}
这里则有两个元素,他们的类型是String型,第二个元素则设置了默认值。再看下前面@Subscrive注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
...
boolean sticky() default false;
...
int priority() default 0;
}
它保留在VM运行时,用于方法上。三个元素中,第一个元素的类型是Enum型,默认值则为ThreadMode.POSTING
,也比较好理解了。
定义好注解之后,就是如何使用了
public class TestUtil {
@Test(id="0" , userName = "Hong")
public void getData(String data){
Log.e("TestUtil", "getData: " + data );
}
@Test(id="1")
public void dataTest(){
Log.e("TestUtil", "dataTest" );
}
使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。
从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。
public static void main(String args[]){
trackTest(TestUtil.class);
}
public static void trackTest(Class<?> cls){
Method[] methods = cls.getMethods();
for(Method method : methods){
Test test = method.getAnnotation(Test.class);
if(test != null){
System.out.println("Found Test : id = " + test.id() + " userName = " + test.userName() );
}
}
}
这里就可以拿到每个注解方法上注解的一些属性。
关于注解更多的使用就不扩展开了。
三.EventBus的注解
还是前面那个@Subscribe
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
...
boolean sticky() default false;
...
int priority() default 0;
}
ThreadMode
是一个enum类型,里面定义了四种类型
public enum ThreadMode {
POSTING,
MAIN,
BACKGROUND,
ASYNC
}
对应的方法则为
onEventPostThread:代表这个方法会在当前发布事件的线程执行,也就是执行事件和发送事件都在同一个线程中。(默认是这个)
onEventMainThread:代表这个方法会在主线程执行
onEventBackgroundThread:如果发送事件的线程不是UI线程,则运行在该线程中。如果发送事件的是UI线程,则它运行在由EventBus维护的一个单独的线程池中,事件会加入后台任务队列,使用线程池一个接一个调用。
onEventAsync:运行在单独的工作线程中,不论发送事件的线程是否为主线程。跟BackgroundThread不一样,该模式的所有线程是独立的,因此适用于长耗时操作,例如网络访问。它也是使用线程池调用,注意没有BackgroundThread中的一个接一个。
3.1ThreadMode.POSTING
首先@Subscribe它里面ThreadMode默认值是POSTING,也就是说,执行事件和发送事件在同一个线程。
@Subscribe
public void onEventMainThread(String s){
Log.e(TAG, "Current Thread name: " + Thread.currentThread().getName() );
Log.e(TAG, "Current Thread is MainThread? : " + (Thread.currentThread() == Looper.getMainLooper().getThread()) );
tv.setText(s); //主线程才能更新UI,如果POST在子线程运行则报错
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
EventBus.getDefault().post("Service Test");
return super.onStartCommand(intent, flags, startId);
}
当然这里的方法名是随便的,如果发送事件在子线程的话可能会有点误区。结果为
E/MainActivity: Current Thread name: main
E/MainActivity: Current Thread is MainThread? : true
如果在子线程Post
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post("Service Test");
}
} , "childThread").start();
return super.onStartCommand(intent, flags, startId);
}
结果则为
E/MainActivity: Current Thread name: childThread
E/MainActivity: Current Thread is MainThread? : false
此时在方法里则不能进行UI更新操作。此时如果把它的模式设置为MAIN,则没问题
3.2 ThreadMode.MAIN
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(String s){
Log.e(TAG, "Current Thread name: " + Thread.currentThread().getName() );
Log.e(TAG, "Current Thread is MainThread? : " + (Thread.currentThread() == Looper.getMainLooper().getThread()) );
tv.setText(s);
}
结果则跟前面一样,MAIN则是无论发送事件是否在主线程,执行事件总是在主线程
E/MainActivity: Current Thread name: main
E/MainActivity: Current Thread is MainThread? : true
3.3 ThreadMode.BACKGROUND
来验证下BACKGROUND,前面介绍则是如果发送事件的线程不是UI线程,则运行在该线程中。如果发送事件的是UI线程,则它运行在由EventBus维护的一个单独的线程池中。延续前面的例子,发送事件是在子线程childThread中,执行事件方法我们指定为ThreadMode.BACKGROUND
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post("Service Test");
}
} , "childThread").start();
return super.onStartCommand(intent, flags, startId);
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onEventMainThread(String s){
Log.e(TAG, "Current Thread name: " + Thread.currentThread().getName() );
Log.e(TAG, "Current Thread is MainThread? : " + (Thread.currentThread() == Looper.getMainLooper().getThread()) );
tv.setText(s);
}
此时结果则为
E/MainActivity: Current Thread name: childThread
E/MainActivity: Current Thread is MainThread? : false
跟描述的一样
我们将发送事件的方法放到UI线程看下
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
EventBus.getDefault().post("Service Test");
return super.onStartCommand(intent, flags, startId);
}
结果则为
E/MainActivity: Current Thread name: pool-1-thread-1
E/MainActivity: Current Thread is MainThread? : false
可以看到它运行在EventBus维护的一个线程池中。
3.3 ThreadMode.ASYNC
和前面一样做测试,就不贴出代码,直接给出测试结果。
无论当前发送事件是否在主线程还是子线程,执行事件都是在EventBus的线程池中
E/MainActivity: Current Thread name: pool-1-thread-1
E/MainActivity: Current Thread is MainThread? : false
那么从这一点上看又与ThreadMode.BACKGROUND
有什么不用呢,前面说到,BACKGROUND里面的线程池会把当前事件添加到任务队列,一个执行完才到下一个,而ThreadMode.ASYNC
从名字上看就知道是异步的,也就是每个事件都可以同时并发执行。
我们看下测试用例
首先,我们一连发送3个事件
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
EventBus.getDefault().post("1 Service Test");
EventBus.getDefault().post("2 Service Test");
EventBus.getDefault().post("3 Service Test");
return super.onStartCommand(intent, flags, startId);
}
模式指定为BACKGROUND
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onEventMainThread(String s){
Log.e(TAG, "currrent String : " + s );
Log.e(TAG, "Current Thread name: " + Thread.currentThread().getName() );
Log.e(TAG, "Current Thread is MainThread? : " + (Thread.currentThread() == Looper.getMainLooper().getThread()) );
//tv.setText(s);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
看下结果输出
03-12 04:29:43.259 E/MainActivity: currrent String : 1 Service Test
03-12 04:29:43.259 E/MainActivity: Current Thread name: pool-1-thread-1
03-12 04:29:43.259 E/MainActivity: Current Thread is MainThread? : false
03-12 04:29:46.263 E/MainActivity: currrent String : 2 Service Test
03-12 04:29:46.263 E/MainActivity: Current Thread name: pool-1-thread-1
03-12 04:29:46.263 E/MainActivity: Current Thread is MainThread? : false
03-12 04:29:49.263 E/MainActivity: currrent String : 3 Service Test
03-12 04:29:49.263 E/MainActivity: Current Thread name: pool-1-thread-1
03-12 04:29:49.263 E/MainActivity: Current Thread is MainThread? : false
可以看到他们里面用的都是同一个线程,一个事件在执行,另一个事件则需要等待。
再看下指定为ThreadMode.ASYNC的结果
03-12 04:27:43.435 E/MainActivity: currrent String : 1 Service Test
03-12 04:27:43.435 E/MainActivity: Current Thread name: pool-1-thread-1
03-12 04:27:43.435 E/MainActivity: Current Thread is MainThread? : false
03-12 04:27:43.435 E/MainActivity: currrent String : 2 Service Test
03-12 04:27:43.435 E/MainActivity: Current Thread name: pool-1-thread-2
03-12 04:27:43.435 E/MainActivity: Current Thread is MainThread? : false
03-12 04:27:43.439 E/MainActivity: currrent String : 3 Service Test
03-12 04:27:43.439 E/MainActivity: Current Thread name: pool-1-thread-3
03-12 04:27:43.439 E/MainActivity: Current Thread is MainThread? : false
注意看时间和线程名字,可以看到他们几乎是同时执行的,而且各自运行在单独的线程之中。对比出来也比较明了了。
讲解完ThreadMode之后,@Subscribe还有其他两个属性
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
...
boolean sticky() default false; //是否是粘性操作
...
int priority() default 0; //优先级
}
- int priority:表示的是当前事件的优先级,设置该优先级的目的是,当一个事件有多个订阅者的时候,优先级高的会优先接收到事件。
@Subscribe(threadMode = ThreadMode.MAIN ,priority = 0)
public void priority0(String s){
Log.e(TAG, "priority0: " + s );
}
@Subscribe(threadMode = ThreadMode.MAIN ,priority = 50)
public void priority50(String s){
Log.e(TAG, "priority50: " + s );
}
@Subscribe(threadMode = ThreadMode.MAIN ,priority = 100 )
public void priority100(String s){
Log.e(TAG, "priority100: " + s );
}
发送一个事件之后,接受到的结果为
03-13 01:46:57.263 E/MainActivity: priority100: Service Test
03-13 01:46:57.263 E/MainActivity: priority50: Service Test
03-13 01:46:57.263 E/MainActivity: priority0: Service Test
如果此时去掉优先级的话,则顺序是无序的。
- boolean sticky:表示的是粘性事件,类似于Android的粘性广播,通俗讲就是当你发送事件的时机比注册时机早的时候,如果设置了sticky粘性,则可以在发送事件完毕后再注册,也可以收到事件,但对于同种事件类型(参数类型)的话只会接受到最近发送的粘性事件,以前的不会收到。我们来验证一下。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().postSticky("Stick Event 1");
EventBus.getDefault().postSticky("Stick Event 2");
EventBus.getDefault().postSticky(10);
EventBus.getDefault().postSticky(20);
}
@Subscribe(threadMode = ThreadMode.MAIN , sticky = true)
public void receiveEventString(String s){
Log.e(TAG, "receiveEventString: " + s );
}
@Subscribe(threadMode = ThreadMode.MAIN , sticky = true)
public void receiveEventInt(Integer i){
Log.e(TAG, "receiveEventInt: " + i );
}
可以看到这里注册的时机是在按钮点击之后,但之前消息已经是发送出去了。此时点击注册后,可以看到输出结果
E/MainActivity: receiveEvent: Stick Event 2
E/MainActivity: receiveEventInt: 20
粘性事件的内部,则是用了一个Map在保存这些粘性事件,而key则是这些粘性事件的参数类型,所以对于同个参数类型的话,最终只会保存最近那个使用的。
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
粘性事件的发送必须满足
* 发送事件必须是EventBus.getDefault().postSticky
* 方法的注解要设置stick = true
,而这个默认值则为false
四.小结
EventBus在3.0版本可说是做了大改,很多之前版本一些缺点都做了改正,提供了注解,性能有了很大的提升,其实是提供了4种ThreadMode来适应不同的网络情况,相比于Otto来说EventBus的网络请求功能更加丰富
ThreadMode.POST:代表这个方法会在当前发布事件的线程执行,也就是执行事件和发送事件都在同一个线程中。(默认是这个)
ThreadMode.MAIN:代表这个方法会在主线程执行
ThreadMode.BACKGROUND:如果发送事件的线程不是UI线程,则运行在该线程中。如果发送事件的是UI线程,则它运行在由EventBus维护的一个单独的线程池中,事件会加入后台任务队列,使用线程池一个接一个调用。
ThreadMode.ASYNC:运行在单独的工作线程中,不论发送事件的线程是否为主线程。跟BackgroundThread不一样,该模式的所有线程是独立的,因此适用于长耗时操作,例如网络访问。它也是使用线程池调用,注意没有BackgroundThread中的一个接一个。