版權聲明:
本賬號發布文章均來自公眾號,承香墨影(cxmyDev),版權歸承香墨影所有。
未經允許,不得轉載。
一、前言
Broadcast 是 Android 四大組件之一,與傳統意義上的電臺廣播類似,一個廣播需要有一個發布者,以及任意多個接收者,并且它的特點也非常的明顯,發布者只負責將廣播發布出去,而不去關心接收者是否能正常接收到廣播內容,也不關心接收者是如何處理廣播的。以這種形式來達到發送者和接收者完全的解耦。
Broadcast 可以被大致分為三個角色:發送者、接收者(BroadcastReceiver)以及承載 Broadcast 的 Intent 對象。接下來就這三個角色進行單獨講解。
二、Broadcast 的類型
在 Android 中,為了適應不同的場景,Broadcast 可以被分為:
- 無序廣播。
- 有序廣播。
- 本地廣播。
- Sticky 廣播。
先來看看對于不同類型廣播的特點。
1、無序廣播
無序廣播是完全異步的,通過 Context.sendBroadcast()
方法來發送,從效率上來看,還算是比較高的。

在 sendBroadcast()
方法中,還有一個第二個參數為 String 類型的重載方法,它是用來設定接收者的權限的,這個權限可以是系統權限,也可以是自定義的權限。

但是正如它的名稱一樣,無序廣播對所有廣播接收者(Receivers)而言,是無序的,也就是說,所有接收者無法確定接收時序的順序,這樣也導致了,無序廣播無法被停止。當它被發送出去之后,它將通知所有這條廣播的接收者,直到沒有與之匹配的廣播接收者為止。
2、有序廣播
有序廣播通過 Context.sendOrderedBroadcast()
方法來發送。有序廣播和無序廣播最大的不同,就是它可以允許接收者設定優先級,它會按照接收者設定的優先級依次傳播。而高優先級的接收者,可以對廣播的數據進行處理或者停止掉此條廣播的繼續傳播。

想要設定有序廣播的優先級,需要在 IntentFilter 中進行設定。在 AndroidManifest.xml 中,使用 android:priority
屬性設置,在代碼中,可以通過 IntentFilter.setPriority()
方法設定。這取決于 Broadcast 的注冊方式。

可以看到,它的取值是有限定范圍的,需要在 SYSTEM_LOW_PRIORITY 和 SYSTEM_HIGH_PRIORITY 之間。

可以看到,這樣的 priority 的限定范圍,就是在 -1000 ~ 1000 之間,而如果不對其進行設定,它的默認值為 0。
前面也提到,高優先級的接收者可以附加數據以及停止當前廣播的傳播。附加數據,可以通過 setResult()
方法來操作,同時也可以通過 getResult() 方法來獲取比自己更高優先級的接收者設置的數據內容。而停止這條廣播繼續傳播,可以調用 abortBroadcast()
方法。

3、Sticky廣播
Sticky 廣播和它的名字很像,它是一個具有粘性的廣播。它被發出去之后,會一直滯留在系統中,直到有與之匹配的接收者,才會將其發出去。
Sticky 廣播,使用 Context.sendStickyBroadcast()
方法進行發送廣播。

從文檔上可以看到,如果想要發送一個 Sticky 廣播,需要具有 BROADCAST_STICKY 權限,這個可以在 AndroidManifest.xml 中進行注冊,而如果沒有此權限,則會拋出 SecurityException 異常。
對于系統而言,只會保留最后一條 Sticky 廣播,并且會一直保留下去,也就是說,如果我們發送的 Sticky 廣播不被取消,當有一個接收者的時候就會收到它,再來一個還是能收到。所有我們需要在合適的實際,調用 removeStickyBoradcast()
方法,將其取消掉。
從上面的方法文檔中也可以看到 StickyBroadcast 已經被標記為 @Deprecated
,出于一些安全的考慮,已經將其標記為廢棄,不再推薦使用。我們作為開發者,對于一些被標記為 @Depracated
的方法,使用起來還是需要謹慎的。
4、本地廣播
前面介紹的廣播,都是全局的,只要被發出去之后,所有注冊了此廣播的 App ,都可以接受到它,這樣就帶來了安全的隱患。而有時候,我們只是想讓自己的 App 進程內使用,而無需將廣播公布出去。那么就可以使用本地廣播。
本地廣播是 Android Support v4 : 21 版本才新增的廣播類型,它使用 LocalBroadcastManager (以下簡稱 LBM)類來管理。
LocalBroadcast 的使用非常的簡單,只需要將 Broadcast 的對應 API,替換為 LBM 為我們提供的 API 即可。
LBM 是一個單例對象,可以使用 LocalBroadcastManager.getInstance(Context )
方法獲取到。在 Context 中定義的和 Broadcast 相關的方法,在 LBM 中都有對應的 API 。非常有意思的是,LBM 為了區分異步和同步,使用了 sendBroadcast()
和 sendBroadcastSync()
方法來做為區分。
三、注冊廣播接收者方式
在 Android 中 ,Broadcast 有兩種注冊方式:
- AndroidManifest.xml 靜態注冊。
- 代碼中動態注冊。
1、靜態注冊
在 AndroidManifest.xml 靜態注冊,是一種非常常用的注冊方式。
這里注冊了一個監聽輸入法改變的系統廣播的 BroadcastReceiver。
2、動態注冊
有一些情況下,我們因為一些限制,會需要使用到動態注冊監聽。
Context 中,為我們提供了動態注冊廣播接收者對應的 api。
這幾行代碼和上面靜態注冊的效果是一樣的。但是既然是動態注冊,可以在需要的時候進行廣播接收者的注冊,那么在不需要的時候就需要對其進行取消。
動態取消廣播接收者的注冊,需要使用 Context.unregisterReceiver()
方法,它需要一個 BroadcastReceiver 對象作為參數,這就是我們之前用于注冊的 Receiver 對象。
3、動態注冊和靜態注冊有什么區別?
理論上來說上來說,無論是使用靜態注冊,還是動態注冊,當這個廣播接收者被注冊上之后,他們的后續操作是一樣的。但是它們的注冊時機卻不同。
對于靜態注冊的接收者而言,實際上它在安裝到設備中之后,就已經被注冊上了,只要有與它匹配的廣播被發出來,它就是可以被激活并處理廣播的,而對于動態注冊,只有當前 App 被啟動,并且執行到 registerReceiver()
方法之后,才會完成注冊,才能接收匹配的廣播。
既然如此,Android 為了一些效率和安全的原因,規定一些系統廣播無法被靜態注冊,例如:SCREEN_ON、SCREEN_OFF、TIME_TICK 等,這種觸發頻率比較高的系統廣播。這些廣播只允許動態注冊,使用靜態注冊的方式雖然不會報錯,但是也不會有效。
4、BroadcastReceiver
無論是使用那種注冊方式,我們都需要有一個 BroadcastReceiver 對象,它是用于實際去處理廣播的對象,它是一個抽象類,需要實現其內的方法 onReceive()。
使用 BroadcastReceiver 就可以接受到與之匹配的廣播,廣播是通過 IntentFilter 為過濾條件來匹配的,我們可以通過 onReceiver() 方法中的 intent 對象,來獲取到接收到的廣播的相關數據。
四、查缺補漏
到這里,基本上 Broadcast 的相關內容就講解清楚了。但是實際使用中,有時候還是會碰到問題,這里單獨用一個小結來分析碰到的問題,有新的問題會持續更新。
1、被停止的 App 無法接收 Broadcast
對于 Broadcast 的 api ,在 Android api level 11 (Android 3.1)之后有過調整。新增了兩個 FLAG,用來控制 Broadcast 是否對處于停止狀態的 App 起作用。
這兩個 FLAG 為:
- FLAG_INCLUDE_STOPPED_PACKAGES:表示包含未啟動的 App。
- FLAG_EXCLUDE_STOPPED_PACKAGES:表示不包含未啟動的 App。
而加了這兩個 flag 的版本之后,系統會默認向所有 Broadcast 的 Intent 增加 FLAG_EXCLUDE_STOPPED_PACKAGES 這個 flag,這樣做是為了防止喚醒已經被停止的 App 來處理這個廣播,這樣可以節約很多不必要的資源浪費。可以看到 ,Android 為了優化效率,一直是在做努力的,在 最新的 Android O 上也做了大的優化。
而這樣導致如果 App 處于停止的狀態下,默認就不會接收到廣播的。那么有沒有辦法解決這個問題?如果廣播的發送方我們可以控制,只需要為廣播增加 FLAG_INCLUDE_STOPPED_PACKAGES 即可,如果沒發控制,暫時也沒什么好的辦法讓被停止的 App 接收到這部分廣播。
那么,我們還需要確定,什么情況下,App 會處于停止狀態,現在能確定的就是兩種狀態:
- 首次安裝未啟動過。
- 在任務管理器中,被『強行停止』后。
當然不排除有一些管理軟件會模擬『強行停止』的動作。