1 理解廣播與廣播接收器
- 廣播事件處理屬於系統級的事件處理(一般事件處理是屬於View級的事件處理)
- 一個應用可以在發生特定事件時發送Broadcast, 系統中任何應用只要註冊了對應Receiver就會接收到此Broadcast
- 一個應用如果對某個廣播感興趣, 就可以註冊對應的Receiver來接收廣播
- 廣播事件機制是應用程序(進程間)之間通信的一種手段
2 相關API
Context:
- sendBroadcast(Intent intent) : 發送一般廣播
- sendOrderedBroadcast(Intent intent) : 發送有序廣播
- registerReceiver(receiver, intentFilter) : 註冊廣播接收器
- unRegisterReceiver(receiver) : 解註冊廣播接收器
BroadcastReceiver:
- onReceive(Context context, Intent intent) : 接收到廣播的回調
- abortBroadcast() : 中斷廣播的繼續傳播
- boolean isOrderedBroadcast() : 判斷是否是有序廣播
3 注意事項
1. Android 7.0 (API level 24) and higher don't send the following system broadcasts:
Also, apps targeting Android 7.0 and higher must register the CONNECTIVITY_ACTION
broadcast using registerReceiver(BroadcastReceiver, IntentFilter)
. Declaring a receiver in the manifest doesn't work.
也就是說7.0及以上系統不再發送ACTION_NEW_PICTURE和ACTION_NEW_VIDEO廣播,而且註冊
CONNECTIVITY_ACTION廣播必須動態註冊
2.
Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers.
If your app targets Android 8.0 or higher, you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that don't target your app specifically). You can still use a context-registered receiver when the user is actively using your app.
從8.0開始,不能用靜態註冊的方式註冊隱式廣播接收器
3.Beginning with Android 9 (API level 28), The NETWORK_STATE_CHANGED_ACTION
broadcast doesn't receive information about the user's location or personally identifiable data.
In addition, if your app is installed on a device running Android 9 or higher, system broadcasts from Wi-Fi don't contain SSIDs, BSSIDs, connection information, or scan results. To get this information, call getConnectionInfo()
instead.
從9.0開始, NETWORK_STATE_CHANGED_ACTION這個廣播中的信息不再包含用戶的位置和一些個人信息
4 測試用例
4.1 首先定義一個廣播接收器類
package com.example.broadcastreceivertest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class MyReceiver extends BroadcastReceiver {
public MyReceiver() {
Log.i("TAG", "MyReceiver: ");
}
@Override
public void onReceive(Context context, Intent intent) {
Log.i("TAG", "onReceive: ");
Log.i("TAG", "data: " + intent.getStringExtra("data"));
}
}
BroadcastReceiver的狀態影響其所在進程的狀態,進而影響這個進程被系統殺死的可能性。例如,一個進程包含一個BroadcastReceiver,並且這個BroadcastReceiver的onReceive()方法正在執行,那麼這個進程就被認爲是一個前臺進程,除了內存極度缺乏的情況下,這個進程將保持存活。
然而一旦這個BroadcastReceiver的onReceive()方法執行完畢,那麼這個BroadcastReceiver就不再活躍了,持有這個reveiver的進程的優先級就依賴於其他組件了(如activity,service等)。如果這個進程僅有一個靜態註冊的receiver,並且用戶沒有與這個進程進行交互或者最近沒有與這個進程進行交互,那麼系統就會認爲這個進程的優先級比較低,可能會殺死這個進程,騰出資源爲其他更重要的進程提供服務。
因此,不應該在onReceive()方法中開啓一個分線程去執行長時間的後臺任務,系統隨時可能殺死這個進程,並且結束掉依賴於這個進程的線程。爲了避免這種情況,在需要更多時間去處理任務的時候,可以使用goAsync()或者使用JobScheduler從接收器調度JobService,以便系統知道該進程繼續執行活動 工作。
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
final PendingResult pendingResult = goAsync();
Task asyncTask = new Task(pendingResult, intent);
asyncTask.execute();
}
private static class Task extends AsyncTask {
private final PendingResult pendingResult;
private final Intent intent;
private Task(PendingResult pendingResult, Intent intent) {
this.pendingResult = pendingResult;
this.intent = intent;
}
@Override
protected String doInBackground(String... strings) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
return log;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
// Must call finish() so the BroadcastReceiver can be recycled.
pendingResult.finish();
}
}
}
4.2 然後註冊廣播接收器
4.2.1 靜態註冊
8.0以後這樣註冊隱式廣播接收不到消息
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="android.intent.action.test_data"/>
</intent-filter>
</receiver>
4.2.2 動態註冊
IntentFilter intentFilter = new IntentFilter("android.intent.action.test_data");
if (myReceiver == null) {
myReceiver = new MyReceiver();
}
registerReceiver(myReceiver, intentFilter);
動態註冊的廣播必須在context(比如activity)死亡前反註冊,否則應用會crash,如果使用applicationContext註冊,則廣播接收器的生命週期就是app存活的週期
@Override
protected void onDestroy() {
unregisterReceiver(myReceiver);
super.onDestroy();
}
4.3 發送廣播
4.3.1 發送普通廣播
Intent intent = new Intent("android.intent.action.test_data");
intent.putExtra("data","這是我的測試數據");
sendBroadcast(intent);
sendBroadcast(Intent)方法發送的廣播是不帶有順序的,幾乎所有有效註冊了的廣播接收器都能收到此廣播,也被稱爲正常廣播。
4.3.2 發送有序廣播
Intent intent = new Intent("android.intent.action.test_data");
intent.putExtra("data","這是我的測試數據");
sendBroadcast(intent);
sendOrderedBroadcast(Intent,String)方法一次向一個接收器發送廣播。 當每個接收器依次執行時,它可以將結果傳播到下一個接收器,或者它可以完全中止廣播,以便它不會傳遞給其他接收器。 可以使用匹配的intent-filter的android:priority屬性來控制運行的訂單接收器;priority的值越大優先級越高, 具有相同優先級的接收器將以任意順序運行。
4.3.3 發送本地廣播
Intent intent = new Intent("android.intent.action.test_data");
intent.putExtra("data","這是我的測試數據");
LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(intent);
LocalBroadcastManager.sendBroadcast方法將廣播發送到與發送方位於同一應用程序中的接收方。 如果不需要跨應用程序發送廣播,最好使用本地廣播。 實現效率更高(無需進程間通信),也不需要擔心與其他應用程序能夠接收或發送廣播相關的任何安全問題。
5 用權限來限制廣播
權限允許將廣播限制爲具有特定權限的應用程序集, 可以對廣播的發送者或接收者實施限制。
5.1 發送帶權限的廣播
當調用sendBroadcast(Intent,String)或sendOrderedBroadcast(Intent,String,BroadcastReceiver,Handler,int,String,Bundle)時,您可以指定權限參數。 只有那些已經在其清單文件中申請了權限的接收者(並且如果它是危險的,則隨後需要動態授權)可以接收廣播。 例如,以下代碼發送廣播:
sendBroadcast(new Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS);
要接收廣播,接收應用必須請求權限,如下所示:
<uses-permission android:name="android.permission.SEND_SMS"/>
可以指定現有系統權限(如SEND_SMS)或使用<permission>元素定義自定義權限。
注意:安裝應用程序時會註冊自定義權限。 必須在使用該應用程序之前安裝定義自定義權限的應用程序。
5.2 接收帶權限的廣播
如果在註冊廣播接收器時指定了權限參數(使用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>
或者接收應用程序有像如下方式動態註冊廣播接收器:
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
然後,爲了能夠向這些接收者發送廣播,發送應用必須請求許可,如下所示:
<uses-permission android:name="android.permission.SEND_SMS"/>
也就是說無論是發送方還是接收方都必須像上面的代碼這種方式,在清單文件中授權權限才能正常收發廣播,如果是危險權限還需要動態授予才行。
6 一些安全注意事項和最佳做法
- 如果不需要嚮應用程序外部的組件發送廣播,則使用支持庫中提供的LocalBroadcastManager發送和接收本地廣播。 LocalBroadcastManager效率更高(無需進程間通信),並且不用考慮與其他應用程序能夠接收或發送您的廣播相關的任何安全問題。
- 如果許多應用在清單文件中註冊接收相同的廣播,則可能導致系統啓動大量應用,從而對設備性能和用戶體驗產生重大影響。爲避免這種情況,優先使用上下文註冊而不是靜態註冊,Android系統本身會強制使用上下文註冊的接收器。例如,CONNECTIVITY_ACTION廣播僅傳遞給上下文註冊的接收器。
-
不要使用隱式意圖廣播敏感信息。任何註冊廣播接收器的應用都可以讀取該信息。有三種方法可以控制誰可以接收您的廣播:
1. 您可以在發送廣播時指定權限。
2. 在Android 4.0及更高版本中,您可以在發送廣播時指定包含setPackage(String)的包。系統將廣播限制爲與包匹配的應用程序集。
3. 您可以使用LocalBroadcastManager發送本地廣播。 -
當您註冊接收器時,任何應用都可以向您的應用接收器發送潛在的惡意廣播。有三種方法可以限制應用收到的廣播:
1. 您可以在註冊廣播接收器時指定權限。
2. 對於清單聲明的接收器,您可以在清單中將android:exported屬性設置爲“false”。接收方不接收來自應用程序之外的來源的廣播。
3. 您可以將自己限制爲僅使用LocalBroadcastManager進行本地廣播。 -
廣播操作的命名空間是全局的。確保操作名稱和其他字符串都寫在您擁有的命名空間中,否則您可能會無意中與其他應用程序發生衝突
-
因爲接收者的onReceive(Context,Intent)方法在主線程上運行,所以它應該快速執行並返回。如果需要執行長時間運行的工作,請注意生成線程或啓動後臺服務,因爲系統可能會在onReceive()返回後終止整個進程。
建議在接收者的onReceive()方法中調用goAsync()並將BroadcastReceiver.PendingResult傳遞給後臺線程。這使得從onReceive()返回後廣播保持活動狀態。但是,即使採用這種方法,系統也希望您能夠非常快速地完成廣播(10秒以內)。它允許您將工作移動到另一個線程,以避免故障主線程。