文章基于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中的一個接一個。