筆記概述
-
EventBus簡介
-
EventBus方法介紹
-
EventBus實際運用
EventBus簡介
EventBus主頁:http://greenrobot.org/eventBus/
github項目地址中關于EventBus的簡介:
Event bus for Android and Java that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality.
即,
Event 簡化了活動、碎片、進程、服務等之間的通訊方式;
使APP項目用更少的代碼量實現更好的質量;
關于EventBus的優勢
- 簡化組件間的通訊方式
- 解耦合事件發送者和接收者
- 使活動、碎片和后臺的線程實現更高的執行效率
- 防止復雜的有錯誤(傾向)的依賴以及生命周期的問題
- 讓你的代碼簡潔
- 運行快
- 庫小
- EventBus主頁簡潔:
EventBus is an open-source library for Android and Java using the publisher/subscriber pattern for loose coupling. EventBus enables central communication to decoupled classes with just a few lines of code – simplifying the code, removing dependencies, and speeding up app development.
即,
EventBus是一個開源庫,
使用發布/訂閱
機制來對代碼進行解耦。
簡化項目的集中通訊(僅僅通過幾行代碼就可以解耦各個類),
移除了一些不必要的依賴,加速移動應用的開發。
關于大項目,如果還是用Java/Android原生的調用,
兩個Activity之間的通訊,
還用startActivity()/startActivityForResult()
這類通信方式的話,
代碼會非常的冗余
,
例如Activity和Fragment之間的通訊就需要不斷地調用相關的函數
;
使用EventBus
可以解除這些耦合;
否則如果代碼耦合性非常大的話,
會大大增加后期維護的難度!
EventBus架構
Publisher
調用post()方法,
把Event發送到EventBus;EventBus(類似于快遞中心)
分發Publisher發布的Event
給對應的Subscriber(訂閱者);Subscriber接收Event;
EventBus概述
EventBus是一個Android端優化的
publish/subscribe消息總線;簡化了應用程序內各組件間、組件與后臺線程間的通訊;
舉例一個EventBus可簡化代碼的場景:
請求網絡時候,等網絡返回時通過Handler或Broadcast通知UI;
兩個Fragment之間需要通過Listener通訊;
以上都可以用EventBus來代替;-
EventBus作為一個消息總線,有三個主要的元素:
Event:事件
Event
可以是任意對象,
用來描述傳遞的數據
或事件類型
;
一般Event是由開發者按照需求自己定義
的,
里面封裝要傳遞的事件類型和數據
;Subscriber:事件訂閱者,接收待定的事件
在Event中,使用約定
來制定事件訂閱者
以簡化使用
;
在3.0之前,EventBus還沒使用注解
的方式,
消息處理的方法
也僅限于:
onEvent
、onEventMainThread
、onEventBackgroundThread
、onEventSync
,
分別代表四種線程模型
;
在3.0之后,消息處理的方法
可以隨便取名
,
但是需要
添加一個注解@Subscribe,
并且要指定線程的模型;Publisher:事件發布者,用于通知
Subscriber
有事件發生
可以在任意線程、任意位置
發送事件,
直接調用EventBus
的post(Object)
方法即可;調用
EventBus.getDefault()
方法,實例化EventBus對象
EventBus線程模型
ThreadMode
ThreadMode指定了會調用的函數,
只能有以下四種(因為每個訂閱事件都是和一個線程模型相關的):
PostThread、 BackgroundThread、 MainThread、 Async
PostThread: 在相同的進程中做EventBus通信
事件的處理和事件的發送在相同的進程,
所以事件的處理時間
不應太長,
不然會影響事件的發送線程
;
而這個線程可能是UI線程
;對應的函數名是
onEvent
,
一般在UI線程
使用,
如果堵塞時間較長
則會影響其他線程的刷新
,
引起界面的卡頓
;
打個比方說你在UI線程中卡了兩秒等下UI就不動,不刷新了
相關地舉一個案例
- 這里有兩個Activity:
按下Activity1中的Button,
會跳轉到Activity2;
按下Activity2中的button,
會通過EventBus去通知Activity1;
Activity1會通過OnEvent接收,
如果接收到Activity2發送過來消息,
然后觸發Toast;
接下來新建一個項目,根據官方GitHub添加依賴,
implementation 'org.greenrobot:eventbus:3.1.1'
下面是主布局(Activity1):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/bt_toAc2"
android:text="跳轉到Activity2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
- 然后根據需求定義Event(類似于Module類),
這里沒什么特別需求,
定義一個簡單的Event就可以了:
public class MyEvent {
public String msg;
public MyEvent() {}
public MyEvent(String msg) {
this.msg = msg;
}
}
-
接下來,
要在我們這個“Activity1”里面注冊 OnEvent ,
這個OnEvent 是在跟 發送事件的線程 同一個線程里面 接收事件的,
我們這里雖然是分開兩個Activity,
但是Activity本身就都是在主線程里面的;
所以這里 事件的發送(在Activity2), 事件的接收(在Activity1)
都在同一個線程中;
即,以上所說的PostThread線程類型中,
事件的發送 跟 事件的接收 是在同一個線程里面的;
下面注冊一個onEvent(),
如果接收到Activity2發送過來消息,觸發Toast;
/**
* 注冊onEvent(),
* 注意寫上注解!
*/
@Subscribe
public void onEvent(MyEvent event) {
popOutToast("接收到Event:" + event.msg);
}
/**
* 封裝彈出短時Toast提示
* @param text 企圖彈出的文本內容
*
*/
private void popOutToast(String text) {
Toast.makeText(MainActivity.this,text,Toast.LENGTH_SHORT).show();
}
- 使用EventBus的接收方法的活動,需要在onCreate中注冊
EventBus.getDefault().register(this);
- 反注冊
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
- 整個Activity1的java代碼:
public class MainActivity extends AppCompatActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//使用EventBus的接收方法的活動,需要注冊
EventBus.getDefault().register(this);
mButton = findViewById(R.id.bt_toAc2);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
/**
* 注冊onEvent(),
* 注意寫上注解!
*/
@Subscribe
public void onEvent(MyEvent event) {
popOutToast("接收到Event:" + event.msg);
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
/**
* 封裝彈出短時Toast提示
* @param text 企圖彈出的文本內容
*
*/
private void popOutToast(String text) {
Toast.makeText(MainActivity.this,text,Toast.LENGTH_SHORT).show();
}
}
創建第二個活動SecondActivity,布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/bt_sendMsg"
android:text="發送消息"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
java:
public class SecondActivity extends AppCompatActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mButton = findViewById(R.id.bt_sendMsg);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(new MyEvent("洞妖洞妖我是棟二!!!"));
}
});
}
}
運行效果圖:
跳到Activity2,點擊按鈕,彈出Toast:
補充
- 以上這種場景,
如果不用EventBus,
我們可能需要用到Handler + Runnable的方式,
但是如果有十個Activity向Activity1發消息,
我們就需要寫十個Handler了,
這樣子相當繁瑣;
而使用EventBus,
這里只要稍微用代碼注冊一下就可以了,
明顯方便很多,
一個方post、一個onEvent,也很輕松地解耦了;
- 另外一個需要注意的地方就是,
EventBus.getDefault().register(this);
系列的注冊與反注冊代碼,
同onEvent()
系列的接收函數是緊密綁定的;
用時缺一不可,不用時存一不可,同生同滅;
也就是說一個活動注冊onEvent()
系列的接收函數了,
則必須用EventBus.getDefault().register(this);
去注冊,
不然會報錯;
而一個活動它沒有寫onEvent()
系列的接收函數,
卻用EventBus.getDefault().register(this);
去注冊了,
同樣也會報錯!
- onEvent()處理時間比較長,會導致線程堵塞;
如以下再onEvent()中掛起線程3秒,模擬3秒處理時間:
@Subscribe
public void onEvent(MyEvent event) {
//消耗時間模擬
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
popOutToast("接收到Event:" + event.msg);
}
接著運行項目的話,
會發現我們在Activity2中點擊發送消息的按鈕之后,
要等到3秒鐘,主線程才會刷新UI(彈出Toast),
這樣子在實際運用中用戶體驗很差;
MainThread
- 其機制同onEvent()其實是差不多的,
即發送
和接收
都是在同一個線程
主線程
/UI線程
中進行;
使用:基于PostThread
的代碼,
加多一行(threadMode = ThreadMode.MAIN)
即可:
//MainThread
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MyEvent event) {
//消耗時間模擬
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
popOutToast("接收到Event:" + event.msg);
}
BackgroundThread
事件的處理會在一個
后臺線程
中執行,
對應的函數名是onEventBackgroundThread
;雖然名字是BackgroundThread,
事件的處理是在后臺線程,
但事件的處理時間還是不宜太長;如果發送事件的線程是在后臺線程,
會直接執行事件;如果當前線程是UI線程,
事件會被加到一個隊列中,
由一個線程依次處理這些事件,
如果某個事件處理時間太長,
會阻塞隊列中 排在后面的事件的派發或處理;
圖解
對于PostThread和MainThread
一次執行
-
當只有一個線程的時候,
宏觀上來說,其代碼便是一次執行的:
post(發送)和onEvent()是在同一個線程中去跑的,
一個線程里面的話,
對于BackgroundThread
一一對應
- 這里有兩個線程,UI主線程和后臺線程,
這個后臺線程
專門
用來
處理onEventBackgroundThread
方法及其對應的事件
的;
也就是說,
比如現在主線程里面有一個post
,
它會對應執行到后臺的一個onEventBackgroundThread()
;
順序執行,前者執行,后者等待阻塞
一個前臺線程的post
會對應執行到一個后臺線程的onEventBackgroundThread()
;
來了第二個post,
就對應第二個onEventBackgroundThread()
;-
后臺線程中的
onEventBackgroundThread()
;
是按照post
的順序依次執行的;
如果前面一個post對應的onEventBackgroundThread()
;沒有執行完,
這時候又post
了一下,
那么對應的后面的這個onEventBackgroundThread()
會等待前面一個onEventBackgroundThread()
執行完,它才執行;
- 來個例子,修改MainActivity代碼:
//BackgroundThread
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onEvent(MyEvent event) {
Log.d(TAG, "onEvent Start!!!!!!!! ");
//消耗時間模擬
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "onEvent End!!!!!!!! ");
// popOutToast("接收到Event:" + event.msg);
}
-
接著運行代碼,
老規矩,Activity1跳轉到Activity2,
點擊“發送信息”按鈕,
連續點擊兩次(根據以上SecondActivity中寫的按鈕點擊事件,
這里可以理解成連續post兩次,一前一后),
觀察logcat: 我們可以觀察到兩對Start和End是順序執行的;
執行時候沒有交叉,先第一對,后第二對;
這里也便驗證了以上理論——
即,
一一對應,一個post對應一個event,
event順序執行,
前post者對應的event執行中,
則后post者對應的event等待阻塞;其實把代碼改成
MainThread
的,
再運行,連續點擊三次,
同樣是能體現一一對應,順序執行,前者執行,后者等待阻塞的特性:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MyEvent event) {
Log.d(TAG, "onEvent Start!!!!!!!! ");
//消耗時間模擬
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "onEvent End!!!!!!!! ");
// popOutToast("接收到Event:" + event.msg);
}
- 但區別在于,
Main是執行在主線程,
而Background是執行在后臺線程,
而且我們前面說過,
在主線程中執行占用資源多、占用時間長
的任務是不合適的,
既不規范,也影響體驗;
PostThread
/MainThread
缺點:
執行在主線程,
事件的個數,事件的耗時,
都需要做比較嚴格的限制
;
BackgroundThread
缺點:
運行在后臺線程,不占用主線程資源,
比PostThread
/MainThread
好那么一點,
但是還是沒有解決——
多個(>= 2
個)事件時,
一次處理一個,依次處理,
前者執行,后者等待阻塞
的問題,
不適合事件
中有耗時較長的任務
;
Async adj.異步的;
sync n.同時,同步;
-
事件處理會在單獨的線程中執行,
主要用于在后臺線程中執行耗時操作,
每個事件會開啟一個線程
(程序初始化時,已經幫我們創建好一個線程池,
每次POST一下框架都會去取一個線程來執行
),
但最好限制線程的數目
(線程過多,CPU使用大,設備耗電快);
每次POST一下框架都會去取一個線程來執行
,
線程池中線程之間互相不干擾
,可以同時運行
;
更改代碼:
//AsyncThread
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onEvent(MyEvent event) {
Log.d(TAG, "onEvent Start!!!!!!!! ");
//消耗時間模擬
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "onEvent End!!!!!!!! ");
}
執行效果:這次我們可以看到,
事件的處理就沒有——
一次處理一個,依次處理,前者執行中
,后者等待阻塞
的特性了,
因為各個post的事件,
都有各自獨立的線程去處理,
所以事件的處理運行是同時的、異步的;
小結
- PostThread:發送和接收在同一個線程;
- MainThread:發送和接收同在主線程;
(應用范圍上被PostThread包括) - BackgroundThread
PostThread
/MainThread
缺點:
一般執行在主線程,
事件的個數,事件的耗時,
都需要做比較嚴格的限制
;
BackgroundThread
缺點:
運行在后臺線程,不占用主線程資源,
比PostThread
/MainThread
好那么一點,
但是還是沒有解決——
多個(>= 2
個)事件時,
一次處理一個,依次處理,
前者執行,后者等待阻塞
的問題,
不適合事件
中有耗時較長的任務
;
以上三種線程都是不適合跑耗時操作的;
Async
adj.異步的;
核心:異步,同時,高效
;
- 事件處理會在單獨的線程中執行,
主要用于在后臺線程中執行耗時操作,
每個事件會開啟一個線程
(程序初始化時,已經幫我們創建好一個線程池,
每次POST一下框架都會去取一個線程來執行
),
但最好限制線程的數目
(線程過多,CPU使用大,設備耗電快);
每次POST一下框架都會去取一個線程來執行
,
線程池中線程之間互相不干擾
,可以同時運行
補充:
優先級(
priority
)就是字面上的意義,
值越高,優先級越高;-
sticy
即粘性發送,
發送方法
即EventBus.getDefault().postSticky(new MyEvent());
注銷粘性event
的方法EventBus.getDefault().removeStickyEvent(new MyEvent());
發送(postSticky
)的時候,
項目中有多少Fragment、Activity等載體,
事件MyEvent就發送多少份;
粘性
其意義在于,
無論項目中載體類中
是否使用EventBus.getDefault().register(this);
對EventBus注冊過,
都會對其發送事件,
若載體注冊了,則接收處理該粘性事件;
若載體未注冊,則該粘性事件會緩存起來,
一旦載體注冊,馬上接收處理事件
;
需要占用一定量的緩存資源,
所以一般使用較少;
- 另外一個需要注意的地方就是,
EventBus.getDefault().register(this);
系列的注冊與反注冊代碼,
同onEvent()
系列的接收函數是緊密綁定的;
用時缺一不可,不用時存一不可,同生同滅;
也就是說一個活動注冊onEvent()
系列的接收函數了,
則必須用EventBus.getDefault().register(this);
去注冊,
不然會報錯;
而一個活動它沒有寫onEvent()
系列的接收函數,
卻用EventBus.getDefault().register(this);
去注冊了,
同樣也會報錯!
使用技巧
-
事件只需要傳遞一個
狀態
/指令
,無需傳遞數據時,
event自定義類內容可以為空;
比如一個只需要傳遞“清空位置信息列表”這個指令的事件,
可以這么定義:
即
無需傳遞數據,
僅僅event類的類名已經具備傳遞的事件、指令意義; -
一個
Fragment
或者Activity
需要接收處理
多個Event
時候,
通過建立多個注解方法
,并以不同的event 形參
,
來區分
處理,接收到不同的event
時,該做的對應的邏輯
;