Android四大組件之——廣播

本文主要簡單介紹一下關於broadcast的一些基本概念,以及運用broadcast的方法和新版系統相關一些改動等。內容主要基於Andorid官方文檔。android

廣播概述

在Android系統中, 應用與 Android 系統或者其餘 應用之間能夠相互收發廣播消息,相似於發佈-訂閱設計模式類似。設計模式

這些廣播會在所關注的事件發生時發送。舉例來講,Android 系統會在發生各類系統事件時發送廣播,例如系統啓動或設備開始充電時。再好比,應用能夠發送自定義廣播來通知其餘應用它們可能感興趣的事件(例如,一些新數據已下載)。安全

應用能夠註冊接收特定的廣播。廣播發出後,系統會自動將廣播傳送給贊成接收這種廣播的應用。bash

通常來講,廣播可做爲跨應用和普通用戶流以外的消息傳遞系統。可是,必須當心的是,不要濫用在後臺響應廣播和運行做業的機會,由於這會致使系統變慢。併發

廣播的發送

Android 爲應用提供了三種發送廣播的方法:app

  • sendOrderedBroadcast(Intent, String)

一次向一個接收器發送廣播。當接收器逐個順序執行時,接收器能夠向下傳遞結果,也能夠徹底停止廣播,使其再也不傳遞給其餘接收器。接收器的運行順序能夠經過匹配的 intent-filter 的 android:priority 屬性來控制;具備相同優先級的接收器將按隨機順序運行。async

  • sendBroadcast(Intent)

會按隨機的順序向全部接收器發送廣播。這稱爲常規廣播。這種方法效率更高,但也意味着接收器沒法從其餘接收器讀取結果,沒法傳遞從廣播中收到的數據,也沒法停止廣播。ide

  • LocalBroadcastManager.sendBroadcast

會將廣播發送給與發送器位於同一應用中的接收器。若是不須要跨應用發送廣播,則應使用本地廣播。這種實現方法的效率更高(無需進行進程間通訊),並且無需擔憂其餘應用在收發當前廣播時帶來的任何安全問題。性能

須要注意的是:廣播消息封裝在 Intent 對象中。Intent 的操做字符串必須提供應用的 Java 軟件包名稱語法,並惟一標識廣播事件。可使用 putExtra(String, Bundle) 向 intent 附加其餘信息。也能夠對 intent 調用 setPackage(String),將廣播限定到同一組織中的一組應用。ui

Android應用發送帶有權限限制廣播的方法:

  • sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
  • sendBroadcast(Intent, String)

接收器若要接收此廣播,則必須經過其清單中的標記請求該權限(能夠指定現有的系統權限(如 SEND_SMS),也可使用 <permission> 元素定義自定義權限)。

例若有以下帶有權限限制廣播的發送:

sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)
複製代碼

那麼相應的,要接收此廣播,接收方應用必須請求以下權限:

<uses-permission android:name="android.permission.SEND_SMS"/>
複製代碼

廣播的接收

應用能夠經過兩種方式接收廣播:清單文件中聲明接收器和上下文註冊的接收器。

1、清單文件中接收廣播

若是在清單文件中聲明瞭廣播接收器,系統會在廣播發出後啓動應用(若是應用還沒有運行)。可是若是軟件當前針對的系統版本爲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) 期間有效。一旦今後方法返回代碼,系統便會認爲該組件再也不活躍。

2、上下文註冊的接收器

要使用上下文註冊接收器,須要執行如下步驟:

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) 中註銷,由於若是用戶在歷史記錄堆棧中後退,則不會調用此方法。

3、帶有權限的廣播接收

若是在註冊廣播接收器時指定了權限參數(經過 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

從 Android 9(API 級別 28)開始,NETWORK_STATE_CHANGED_ACTION 廣播再也不接收有關用戶位置或我的身份數據的信息。 此外,若是應用安裝在搭載 Android 9 或更高版本的設備上,則經過 WLAN 接收的系統廣播不包含 SSID、BSSID、鏈接信息或掃描結果。要獲取這些信息,須要調用 getConnectionInfo()。

  • Android 8.0

從 Android 8.0(API 級別 26)開始,系統對清單聲明的接收器施加了額外的限制。 若是應用以 Android 8.0 或更高版本爲目標平臺,那麼對於大多數隱式廣播(沒有明確針對當前應用的廣播),不能使用清單來聲明接收器。當用戶正在活躍地使用當前應用時,仍可以使用上下文註冊的接收器。

  • Android 7.0

Android 7.0(API 級別 24)及更高版本不發送如下系統廣播: ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO。此外,以 Android 7.0 及更高版本爲目標平臺的應用必須使用 registerReceiver(BroadcastReceiver, IntentFilter) 註冊 CONNECTIVITY_ACTION 廣播。沒法在清單中聲明接收器。

有關於廣播的安全注意事項和最佳作法

  • 若是不須要嚮應用之外的組件發送廣播,則可使用支持庫中提供的 LocalBroadcastManager 來收發本地廣播。LocalBroadcastManager 效率更高(無需進行進程間通訊),而且無需考慮其餘應用在收發當前廣播時帶來的任何安全問題。本地廣播可在當前應用中做爲通用的發佈/訂閱事件總線,而不會產生任何系統級廣播開銷。
  • 若是有許多應用在其清單中註冊接收相同的廣播,可能會致使系統啓動大量應用,從而對設備性能和用戶體驗形成嚴重影響。爲避免發生這種狀況,應優先使用上下文註冊而不是清單聲明。有時,Android 系統自己會強制使用上下文註冊的接收器。例如,CONNECTIVITY_ACTION 廣播只會傳送給上下文註冊的接收器。
  • 請勿使用隱式 intent 廣播敏感信息。任何註冊接收廣播的應用均可以讀取這些信息。能夠經過如下三種方式控制哪些應用能夠接收當前廣播
1.在發送廣播時指定權限。
    2.在 Android 4.0 及更高版本中,能夠在發送廣播時使用 setPackage(String)。 指定包名,系統會將廣播限定到與該包名匹配的一組應用。
    3.使用 LocalBroadcastManager 發送本地廣播。
複製代碼
  • 當註冊接收器時,任何應用均可以向當前應用的接收器發送潛在的惡意廣播。能夠經過如下三種方式限制當前應用能夠接收的廣播
1.能夠在註冊廣播接收器時指定權限。
    2.對於清單聲明的接收器,能夠在清單中將 android:exported 屬性設置爲「false」。這樣一來,接收器就不會接收來自應用外部的廣播。
    3.使用 LocalBroadcastManager 限制當前應用只接收本地廣播。
複製代碼
  • 廣播操做的命名空間是全局性的。請確保在本身的命名空間中編寫操做名稱和其餘字符串,不然可能會無心中與其餘應用發生衝突。
  • 因爲接收器的 onReceive(Context, Intent) 方法在主線程上運行,所以它會快速執行並返回。若是須要執行長時間運行的工做,請謹慎生成線程或啓動後臺服務,由於系統可能會在 onReceive() 返回後終止整個進程。 可使用goAsync()標識當前接收器須要更多時間處理任務。
  • 請勿從廣播接收器啓動 Activity,不然會影響用戶體驗,尤爲是有多個接收器時。相反,能夠考慮顯示通知。
相關文章
相關標籤/搜索