BroadcastReceiver 簡介
廣播用於發送通知消息,應用程序可以選擇接收自己感興趣的廣播,廣播的接收方式爲註冊 BroadcastReceiver,然後在其 onReceive 方法中處理接收到的廣播。
廣播分爲標準廣播
和有序廣播
。兩者的區別在於:標準廣播發出後,所有的 BroadcastReceiver 會同時收到,而有序廣播會按照 BroadcastReceiver 的優先級或註冊順序依次接收、依次處理,並且前面的 BroadcastReceiver 可以中斷廣播的繼續傳播。
廣播還可以按照另一個維度劃分爲顯式廣播
和隱式廣播
,隱式廣播指的是沒有具體指定發送給哪個應用程序的廣播。系統廣播大多數都是隱式廣播。
BroadcastReceiver 有兩種註冊方式:動態註冊
和靜態註冊
。動態註冊是指在代碼中註冊 BroadcastReceiver,靜態註冊指的是在 AndroidManifest 中註冊 BroadcastReceiver。動態註冊的 BroadcastReceiver 只有在程序運行時,代碼中調用 registerReceiver 註冊了之後才能收到廣播,而靜態註冊的廣播即使程序沒有啓動也能接收到廣播。
由於靜態註冊經常被濫用,所以自 Android 8.0 以後,所有的隱式廣播都不允許使用靜態註冊的方式來接收了。大多數系統廣播屬於隱式廣播,只有少數特殊的系統廣播仍然允許使用靜態註冊的方式來接收。這些特殊的系統廣播在這裏可以找到:https://developer.android.google.cn/guide/components/broadcast-exceptions.html
一、動態註冊
系統每隔一分鐘就會發出一條 Intent.ACTION_TIME_TICK
廣播,這個廣播的意思是系統時間發生了變化。接下來我們就用動態註冊 BroadcastReceiver 的方式來接收它。
新建 TimeChangeBroadcastReceiver:
class TimeChangeBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("~~~", "Time has changed")
}
}
MainActivity 中動態註冊:
class MainActivity : AppCompatActivity() {
private val receiver by lazy { TimeChangeBroadcastReceiver() }
private val intentFilter by lazy {
IntentFilter().apply {
addAction(Intent.ACTION_TIME_TICK)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
registerReceiver(receiver, intentFilter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
}
使用registerReceiver
和 unregisterReceiver
就完成了註冊和取消註冊,註冊時傳入的 intentFilter
用來指定接收廣播的類型。最多等待一分鐘就能在控制檯看到 Log:
~~~: Time has changed
監聽其他系統廣播的做法也是類似的,完整的系統廣播列表可以在 Intent 類中查看,也可以在 SDK 中查看,路徑爲:
SDK 路徑/platforms/任意 android api 版本/data/broadcast_actions.txt
二、靜態註冊
在上文的特殊系統廣播列表中,可以看到 Intent.ACTION_BOOT_COMPLETED
是可以靜態註冊的,這個廣播是指系統開機完成。接下來我們就用靜態註冊 BroadcastReceiver 的方式來接收它。
新建 BootCompleteBroadcastReceiver:
class BootCompleteBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("~~~", "boot complete")
}
}
在 AndroidManifest 中註冊這個 Receiver:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application ...>
...
<receiver android:name=".BootCompleteBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
需要注意的是,接收此靜態廣播必須聲明 RECEIVE_BOOT_COMPLETED
權限,這是爲了讓用戶知道我們需要接收開機完成的廣播,Android 希望所有應用都做到對用戶透明。安裝 app 後重啓手機,待系統系統完成後,就可以在控制檯看到 Log:
~~~: boot complete
三、發送自定義廣播
接下來我們來發送一條自定義的廣播,並接收它。
先新建一個類來接收我們的自定義廣播,新建 MyBroadcastReceiver:
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("~~~", "receive my broadcast")
}
}
靜態註冊一下:
<application ...>
...
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
</intent-filter>
</receiver>
</application>
然後我們可以在任何地方發送此廣播進行測試,比如 MainActivity 啓動時發送:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST").apply {
setPackage(packageName)
})
}
}
此時打開 MainActivity,就可以在控制檯看到 Log:
~~~: receive my broadcast
注意這裏我們使用了 setPackage
方法,傳入了我們這個應用的包名,表示這個廣播指定發送給我們的這個應用程序,只有這樣它纔是一個顯式廣播。上文已經說過,Android 8.0 以後,隱式廣播是無法通過靜態註冊接收到的。
本例也可以使用動態註冊的方式,只需刪除 AndroidManifest 中靜態註冊的代碼,再修改 MainActivity 如下:
class MainActivity : AppCompatActivity() {
private val receiver by lazy { MyBroadcastReceiver() }
private val intentFilter by lazy {
IntentFilter().apply {
addAction("com.wkxjc.myapplication.MY_BROADCAST")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
registerReceiver(receiver, intentFilter)
sendBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST"))
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
}
這樣就完成了動態註冊,動態註冊時我們就不需要使用 setPackage
方法了,運行效果和靜態註冊一樣。
四、有序廣播
intentFilter 可以設置 priority
屬性,表示 BroadcastReceiver 的優先級。發送有序廣播時,優先級高的 BroadcastReceiver 會先接收到,處理後再傳遞給優先級第二高的 BroadcastReceiver,以此類推。priority 默認是 0,官方規定 priority 的範圍應該在 (-1000, 1000) 區間,因爲系統會使用優先級 1000 這個值,使系統的一些廣播被最高優先級接收,我們不應該打破這個規定。但編譯器不會檢查 priority 的值是否在區間內,所以這個規定只是一個契約。
靜態註冊的 BroadcastReceiver 通過添加 android:priority 設置 priority 屬性,例如:
<receiver android:name=".HighPriorityBroadcastReceiver">
<intent-filter android:priority="100">
<action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
</intent-filter>
</receiver>
動態註冊的 BroadcastReceiver 通過給 intentFilter 設置,例如:
private val intentFilter by lazy {
IntentFilter().apply {
addAction("com.wkxjc.myapplication.MY_BROADCAST")
priority = 100
}
}
如果兩個 BroadcastReceiver 優先級相同,則先註冊的 BroadcastReceiver 會先接收到。對於靜態註冊,就受 AndroidManifest 中註冊的順序影響,對於動態註冊,就受代碼中調用 registerReceiver 的順序影響。
有序廣播的 BroadcastReceiver 中,可以調用 abortBroadcast 函數中斷此廣播。發送有序廣播的方式也很簡單。調用 sendOrderedBroadcast(yourIntent, null)
即可,這個函數的第二個參數是與權限有關的,我們暫時用不到,傳遞 null 即可。
接下來我們來測試驗證一下。
新建 HighPriorityBroadcastReceiver 類:
class HighPriorityBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("high", "receive")
abortBroadcast()
}
}
在這個類中,我們收到廣播後,打印日誌,然後將廣播中斷。
新建 LowPriorityBroadcastReceiver 類:
class LowPriorityBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("low", "receive")
}
}
這個類中僅僅打印一行日誌。
然後靜態註冊一下這兩個 BroadcastReceiver :
<application ...>
...
<receiver android:name=".LowPriorityBroadcastReceiver">
<intent-filter android:priority="0">
<action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
</intent-filter>
</receiver>
<receiver android:name=".HighPriorityBroadcastReceiver">
<intent-filter android:priority="100">
<action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
</intent-filter>
</receiver>
</application>
我們將兩個 BroadcastReceiver 的 priority 分別設置成了 100 和 0 。這裏我故意讓 LowPriorityBroadcastReceiver 先註冊,以驗證 priority 真的能改變接收次序。
在 MainActivity 中發送顯式廣播:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendOrderedBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST").apply {
setPackage(packageName)
}, null)
}
}
運行程序,控制檯 Log 如下:
high: receive
如果將 AndroidManifest 中,兩個 BroadcastReceiver 的 priority 屬性都去掉,則控制檯會輸出:
low: receive
high: receive
這是因爲 LowPriorityBroadcastReceiver 是先註冊的,並且 LowPriorityBroadcastReceiver 中沒有中斷廣播繼續傳播。所以我們驗證了 priority 確實能改變 BroadcastReceiver 的接收次序。並且 abortBroadcast 確實能中斷有序廣播的傳播。
如果需要在廣播中攜帶數據,只需在發送廣播時將數據傳到 intent 中,再從 BroadcastReceiver 的 onReceive 函數中的 intent 參數中取出即可。