Android 四大組件(三) —— BroadcastReceiver 知識體系

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)
    }
}

使用registerReceiverunregisterReceiver 就完成了註冊和取消註冊,註冊時傳入的 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 參數中取出即可。

參考文章

《第一行代碼》(第三版)- 第 6 章 全局大喇叭,詳解廣播機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章