本文主要簡單介紹一下關於broadcast的一些基本概念,以及運用broadcast的方法和新版系統相關一些改動等。內容主要基於Andorid官方文檔。android
在Android系統中, 應用與 Android 系統或者其餘 應用之間能夠相互收發廣播消息,相似於發佈-訂閱設計模式類似。設計模式
這些廣播會在所關注的事件發生時發送。舉例來講,Android 系統會在發生各類系統事件時發送廣播,例如系統啓動或設備開始充電時。再好比,應用能夠發送自定義廣播來通知其餘應用它們可能感興趣的事件(例如,一些新數據已下載)。安全
應用能夠註冊接收特定的廣播。廣播發出後,系統會自動將廣播傳送給贊成接收這種廣播的應用。bash
通常來講,廣播可做爲跨應用和普通用戶流以外的消息傳遞系統。可是,必須當心的是,不要濫用在後臺響應廣播和運行做業的機會,由於這會致使系統變慢。併發
Android 爲應用提供了三種發送廣播的方法:app
一次向一個接收器發送廣播。當接收器逐個順序執行時,接收器能夠向下傳遞結果,也能夠徹底停止廣播,使其再也不傳遞給其餘接收器。接收器的運行順序能夠經過匹配的 intent-filter 的 android:priority 屬性來控制;具備相同優先級的接收器將按隨機順序運行。async
會按隨機的順序向全部接收器發送廣播。這稱爲常規廣播。這種方法效率更高,但也意味着接收器沒法從其餘接收器讀取結果,沒法傳遞從廣播中收到的數據,也沒法停止廣播。ide
會將廣播發送給與發送器位於同一應用中的接收器。若是不須要跨應用發送廣播,則應使用本地廣播。這種實現方法的效率更高(無需進行進程間通訊),並且無需擔憂其餘應用在收發當前廣播時帶來的任何安全問題。性能
須要注意的是:廣播消息封裝在 Intent 對象中。Intent 的操做字符串必須提供應用的 Java 軟件包名稱語法,並惟一標識廣播事件。可使用 putExtra(String, Bundle) 向 intent 附加其餘信息。也能夠對 intent 調用 setPackage(String),將廣播限定到同一組織中的一組應用。ui
Android應用發送帶有權限限制廣播的方法:
接收器若要接收此廣播,則必須經過其清單中的標記請求該權限(能夠指定現有的系統權限(如 SEND_SMS),也可使用 <permission>
元素定義自定義權限)。
例若有以下帶有權限限制廣播的發送:
sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)
複製代碼
那麼相應的,要接收此廣播,接收方應用必須請求以下權限:
<uses-permission android:name="android.permission.SEND_SMS"/>
複製代碼
應用能夠經過兩種方式接收廣播:清單文件中聲明接收器和上下文註冊的接收器。
若是在清單文件中聲明瞭廣播接收器,系統會在廣播發出後啓動應用(若是應用還沒有運行)。可是若是軟件當前針對的系統版本爲26或更高,那麼就不能使用清單爲隱式廣播(沒有明確針對當前應用的廣播)聲明接收器,但一些不受此限制的隱式廣播除外。
經過清單文件形式聲明廣播接收器,須要以下幾個步驟:
1.在應用清單中指定<receiver>
元素。
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
複製代碼
Intent 過濾器指定接收器所訂閱的廣播操做。
2.建立 BroadcastReceiver 子類並實現 onReceive(Context, Intent)。如下示例中的廣播接收器會記錄並顯示廣播的內容:
private const val TAG = "MyBroadcastReceiver"
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//接收到廣播事件
dosomething...
}
}
複製代碼
須要注意的是:
- 系統會在軟件安裝時註冊接收器。而後,該接收器會成爲應用的一個獨立入口點,這意味着若是應用當前未運行,系統能夠啓動應用併發送廣播。
- 系統會建立新的 BroadcastReceiver 組件對象來處理它接收到的每一個廣播。此對象僅在調用 onReceive(Context, Intent) 期間有效。一旦今後方法返回代碼,系統便會認爲該組件再也不活躍。
要使用上下文註冊接收器,須要執行如下步驟:
1.建立 BroadcastReceiver 的實例:
val br: BroadcastReceiver = MyBroadcastReceiver()
複製代碼
2.建立 IntentFilter 並調用 registerReceiver(BroadcastReceiver, IntentFilter) 來註冊接收器:
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)
複製代碼
3.要中止接收廣播,須要調用 unregisterReceiver(android.content.BroadcastReceiver)。當再也不須要接收器或上下文再也不有效時,務必註銷接收器。
須要注意的是:註冊和註銷接收器的位置,比方說,若是使用 Activity 上下文在 onCreate(Bundle) 中註冊接收器,則應在 onDestroy() 中註銷,以防接收器從 Activity 上下文中泄露出去。若是在 onResume() 中註冊接收器,則應在 onPause() 中註銷,以防屢次註冊接收器(若是不想在暫停時接收廣播,這樣能夠減小沒必要要的系統開銷)。請勿在onSaveInstanceState(Bundle) 中註銷,由於若是用戶在歷史記錄堆棧中後退,則不會調用此方法。
若是在註冊廣播接收器時指定了權限參數(經過 registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) 或清單中的<receiver>
標記指定),則廣播方必須經過其清單中的<uses-permission>
標記請求該權限(若是存在危險,則會被授予該權限),才能向該接收器發送 Intent。
好比在清單文件中標識了以下接收器:
<receiver android:name=".MyBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE"/>
</intent-filter>
</receiver>
複製代碼
或者接收方具備以下所示的上下文註冊的接收器:
var filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null )
複製代碼
那麼,發送方應用必須請求以下權限,才能向這些接收器發送廣播:
<uses-permission android:name="android.permission.SEND_SMS"/>
複製代碼
BroadcastReceiver 的狀態(不管它是否在運行)會影響其所在進程的狀態,而其所在進程的狀態又會影響它被系統終結的可能性。例如,當進程執行接收器(即當前在運行其 onReceive() 方法中的代碼)時,它被認爲是前臺進程。除非遇到極大的內存壓力,不然系統會保持該進程運行。
可是,一旦從 onReceive() 返回代碼,BroadcastReceiver 就再也不活躍。接收器的宿主進程變得與在其中運行的其餘應用組件同樣重要。也就是說若是當前進程僅僅是用來管理廣播接收器,那麼一旦接收器再也不活躍,那麼當前進程系統會將其視爲低優先級進程,並可能會將其終止,以便將資源提供給其餘更重要的進程使用。
既然當接收器再也不活躍(onReceive()方法執行完畢)時,當前進程就有可能面臨被系統回收的風險。那麼,就不該當在廣播接收器啓動長時間運行的後臺線程。onReceive() 完成後,系統能夠隨時終止進程來回收內存,在此過程當中,也會終止進程中運行的派生線程。可是若是的確須要開啓後臺線程來處理一些耗時操做,那麼能夠經過調用goAsync(),讓系統知道該進程將繼續活躍地工做。
使用 goAsync() 方法標記它在 onReceive() 完成後須要更多時間才能完成:
private const val TAG = "MyBroadcastReceiver"
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val pendingResult: PendingResult = goAsync()
val asyncTask = Task(pendingResult, intent)
asyncTask.execute()
}
private class Task(
private val pendingResult: PendingResult,
private val intent: Intent
) : AsyncTask<String, Int, String>() {
override fun doInBackground(vararg params: String?): String {
val sb = StringBuilder()
sb.append("Action: ${intent.action}\n")
sb.append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
return toString().also { log ->
Log.d(TAG, log)
}
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
// 必須調用finish(),以即可以回收BroadcastReceiver
pendingResult.finish()
}
}
}
複製代碼
隨着 Android 平臺的發展,會不按期地更改系統廣播的行爲方式。若是應用以 Android 7.0(API 級別 24)或更高版本爲目標平臺,或者安裝在搭載 Android 7.0 或更高版本的設備上,那麼要注意如下更改:
從 Android 9(API 級別 28)開始,NETWORK_STATE_CHANGED_ACTION 廣播再也不接收有關用戶位置或我的身份數據的信息。 此外,若是應用安裝在搭載 Android 9 或更高版本的設備上,則經過 WLAN 接收的系統廣播不包含 SSID、BSSID、鏈接信息或掃描結果。要獲取這些信息,須要調用 getConnectionInfo()。
從 Android 8.0(API 級別 26)開始,系統對清單聲明的接收器施加了額外的限制。 若是應用以 Android 8.0 或更高版本爲目標平臺,那麼對於大多數隱式廣播(沒有明確針對當前應用的廣播),不能使用清單來聲明接收器。當用戶正在活躍地使用當前應用時,仍可以使用上下文註冊的接收器。
Android 7.0(API 級別 24)及更高版本不發送如下系統廣播: ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO。此外,以 Android 7.0 及更高版本爲目標平臺的應用必須使用 registerReceiver(BroadcastReceiver, IntentFilter) 註冊 CONNECTIVITY_ACTION 廣播。沒法在清單中聲明接收器。
1.在發送廣播時指定權限。
2.在 Android 4.0 及更高版本中,能夠在發送廣播時使用 setPackage(String)。 指定包名,系統會將廣播限定到與該包名匹配的一組應用。
3.使用 LocalBroadcastManager 發送本地廣播。
複製代碼
1.能夠在註冊廣播接收器時指定權限。
2.對於清單聲明的接收器,能夠在清單中將 android:exported 屬性設置爲「false」。這樣一來,接收器就不會接收來自應用外部的廣播。
3.使用 LocalBroadcastManager 限制當前應用只接收本地廣播。
複製代碼