Android 廣播內容全知道 | 掘金技術徵文

承香墨影發表於2019-02-25

版權宣告:

本賬號釋出文章均來自公眾號,承香墨影(cxmyDev),版權歸承香墨影所有。

未經允許,不得轉載。

一、前言

Broadcast 是 Android 四大元件之一,與傳統意義上的電臺廣播類似,一個廣播需要有一個釋出者,以及任意多個接收者,並且它的特點也非常的明顯,釋出者只負責將廣播發布出去,而不去關心接收者是否能正常接收到廣播內容,也不關心接收者是如何處理廣播的。以這種形式來達到傳送者和接收者完全的解耦。

Broadcast 可以被大致分為三個角色:傳送者、接收者(BroadcastReceiver)以及承載 Broadcast 的 Intent 物件。接下來就這三個角色進行單獨講解。

二、Broadcast 的型別

在 Android 中,為了適應不同的場景,Broadcast 可以被分為:

  • 無序廣播。
  • 有序廣播。
  • 本地廣播。
  • Sticky 廣播。

先來看看對於不同型別廣播的特點。

1、無序廣播

無序廣播是完全非同步的,通過 Context.sendBroadcast() 方法來傳送,從效率上來看,還算是比較高的。

Android 廣播內容全知道 | 掘金技術徵文

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

Android 廣播內容全知道 | 掘金技術徵文

但是正如它的名稱一樣,無序廣播對所有廣播接收者(Receivers)而言,是無序的,也就是說,所有接收者無法確定接收時序的順序,這樣也導致了,無序廣播無法被停止。當它被髮送出去之後,它將通知所有這條廣播的接收者,直到沒有與之匹配的廣播接收者為止。

2、有序廣播

有序廣播通過 Context.sendOrderedBroadcast() 方法來傳送。有序廣播和無序廣播最大的不同,就是它可以允許接收者設定優先順序,它會按照接收者設定的優先順序依次傳播。而高優先順序的接收者,可以對廣播的資料進行處理或者停止掉此條廣播的繼續傳播。

Android 廣播內容全知道 | 掘金技術徵文

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

Android 廣播內容全知道 | 掘金技術徵文

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

Android 廣播內容全知道 | 掘金技術徵文

可以看到,這樣的 priority 的限定範圍,就是在 -1000 ~ 1000 之間,而如果不對其進行設定,它的預設值為 0。

前面也提到,高優先順序的接收者可以附加資料以及停止當前廣播的傳播。附加資料,可以通過 setResult() 方法來操作,同時也可以通過 getResult() 方法來獲取比自己更高優先順序的接收者設定的資料內容。而停止這條廣播繼續傳播,可以呼叫 abortBroadcast() 方法。

Android 廣播內容全知道 | 掘金技術徵文

3、Sticky廣播

Sticky 廣播和它的名字很像,它是一個具有粘性的廣播。它被髮出去之後,會一直滯留在系統中,直到有與之匹配的接收者,才會將其發出去。

Sticky 廣播,使用 Context.sendStickyBroadcast() 方法進行傳送廣播。

Android 廣播內容全知道 | 掘金技術徵文

從文件上可以看到,如果想要傳送一個 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 靜態註冊,是一種非常常用的註冊方式。

Android 廣播內容全知道 | 掘金技術徵文

這裡註冊了一個監聽輸入法改變的系統廣播的 BroadcastReceiver。

2、動態註冊

有一些情況下,我們因為一些限制,會需要使用到動態註冊監聽。

Context 中,為我們提供了動態註冊廣播接收者對應的 api。

Android 廣播內容全知道 | 掘金技術徵文

這幾行程式碼和上面靜態註冊的效果是一樣的。但是既然是動態註冊,可以在需要的時候進行廣播接收者的註冊,那麼在不需要的時候就需要對其進行取消。

動態取消廣播接收者的註冊,需要使用 Context.unregisterReceiver() 方法,它需要一個 BroadcastReceiver 物件作為引數,這就是我們之前用於註冊的 Receiver 物件。

3、動態註冊和靜態註冊有什麼區別?

理論上來說上來說,無論是使用靜態註冊,還是動態註冊,當這個廣播接收者被註冊上之後,他們的後續操作是一樣的。但是它們的註冊時機卻不同。

對於靜態註冊的接收者而言,實際上它在安裝到裝置中之後,就已經被註冊上了,只要有與它匹配的廣播被髮出來,它就是可以被啟用並處理廣播的,而對於動態註冊,只有當前 App 被啟動,並且執行到 registerReceiver() 方法之後,才會完成註冊,才能接收匹配的廣播。

既然如此,Android 為了一些效率和安全的原因,規定一些系統廣播無法被靜態註冊,例如:SCREEN_ON、SCREEN_OFF、TIME_TICK 等,這種觸發頻率比較高的系統廣播。這些廣播只允許動態註冊,使用靜態註冊的方式雖然不會報錯,但是也不會有效。

4、BroadcastReceiver

無論是使用那種註冊方式,我們都需要有一個 BroadcastReceiver 物件,它是用於實際去處理廣播的物件,它是一個抽象類,需要實現其內的方法 onReceive()。

Android 廣播內容全知道 | 掘金技術徵文

使用 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 會處於停止狀態,現在能確定的就是兩種狀態:

  • 首次安裝未啟動過。
  • 在工作管理員中,被『強行停止』後。

當然不排除有一些管理軟體會模擬『強行停止』的動作。

Android 廣播內容全知道 | 掘金技術徵文

Android 廣播內容全知道 | 掘金技術徵文
公眾號二維碼.jpg

相關文章