Android官方文档学习笔记
Broadcasts(广播)
Android 应用程序可以从 Android 系统和其他 Android 应用程序发送或接收广播消息,类似于 发布订阅 设计模式。当感兴趣的事件发生时发送这些广播。例如,Android 系统会在各种系统事件发生时发送广播,例如系统启动或设备开始充电时。应用程序还可以发送自定义广播,例如,通知其他应用程序他们可能感兴趣的内容(例如,已下载一些新数据)。
应用程序可以注册以接收特定的广播。发送广播时,系统会自动将广播路由到已订阅接收该特定类型广播的应用程序。
一般来说,广播可以用作跨应用程序和正常用户流程之外的消息传递系统。但是,您必须小心不要滥用响应广播和在后台运行作业的机会,这可能会导致系统性能下降,如以下视频中所述。
关于系统广播
当系统发生各种系统事件时,例如系统进入和退出飞行模式时,系统会自动发送广播。系统广播发送到所有订阅接收事件的应用程序。
广播消息本身包装在一个Intent 对象中,该对象的操作字符串标识发生的事件(例如 android.intent.action.AIRPLANE_MODE)。意图还可以包括捆绑到其额外字段中的附加信息。例如,飞行模式意图包含一个额外的布尔值,指示飞行模式是否开启。
有关系统广播操作的完整列表,请参阅BROADCAST_ACTIONS.TXTAndroid SDK 中的 文件。每个广播操作都有一个与之关联的常量字段。例如,常量的 ACTION_AIRPLANE_MODE_CHANGED值为 android.intent.action.AIRPLANE_MODE。每个广播操作的文档在其关联的常量字段中可用。
对系统广播的更改
随着 Android 平台的发展,它会定期更改系统广播的行为方式。如果您的应用面向 Android 7.0(API 级别 24)或更高版本,或者安装在运行 Android 7.0 或更高版本的设备上,请记住以下更改。
安卓 9
从 Android 9(API 级别 28)开始, NETWORK_STATE_CHANGED_ACTION 广播不会接收有关用户位置或个人身份数据的信息。
此外,如果您的应用安装在运行 Android 9 或更高版本的设备上,则来自 Wi-Fi 的系统广播不包含 SSID、BSSID、连接信息或扫描结果。要获取此信息,请getConnectionInfo() 改为致电 。
安卓8.0
从 Android 8.0(API 级别 26)开始,系统对清单声明的接收器施加了额外的限制。
如果您的应用面向 Android 8.0 或更高版本,则不能使用清单为大多数隐式广播(不专门针对您的应用的广播)声明接收器。当用户正在使用您的应用程序时,您仍然可以使用 上下文注册接收器。
安卓7.0
Android 7.0(API 级别 24)及更高版本不发送以下系统广播:
- ACTION_NEW_PICTURE
- ACTION_NEW_VIDEO
此外,面向 Android 7.0 及更高版本的应用程序必须CONNECTIVITY_ACTION使用registerReceiver(BroadcastReceiver, IntentFilter). 在清单中声明接收者不起作用。
接受广播
应用程序可以通过两种方式接收广播:通过清单声明的接收器和上下文注册的接收器。
清单声明的接收者
如果您在清单中声明广播接收器,则系统会在发送广播时启动您的应用程序(如果应用程序尚未运行)。
注意:如果您的应用面向 API 级别 26 或更高级别,则不能使用清单来声明隐式广播(不专门针对您的应用的广播)的接收器,除了少数不受该限制的隐式广播。在大多数情况下,您可以改用计划作业。
要在清单中声明广播接收器,请执行以下步骤:
- <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>
意图过滤器指定您的接收器订阅的广播操作。
- 子类BroadcastReceiver并实现onReceive(Context, Intent)。以下示例中的广播接收器记录并显示广播的内容:
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
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);
Toast.makeText(context, log, Toast.LENGTH_LONG).show();
}
}
系统包管理器在安装应用程序时注册接收器。然后,接收器成为应用程序的单独入口点,这意味着如果应用程序当前未运行,系统可以启动应用程序并传送广播。
系统创建一个新的BroadcastReceiver组件对象来处理它接收到的每个广播。此对象仅在调用 期间有效onReceive(Context, Intent)。一旦您的代码从此方法返回,系统就会认为该组件不再处于活动状态。
上下文注册接收器
要使用上下文注册接收器,请执行以下步骤:
- 创建 的实例BroadcastReceiver。
BroadcastReceiver br = new MyBroadcastReceiver();
- 创建一个IntentFilter并通过调用注册接收器registerReceiver(BroadcastReceiver, IntentFilter):
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);
注意:要注册本地广播,请LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter)改为调用。
- 只要他们的注册上下文有效,上下文注册的接收器就会接收广播。例如,如果您在Activity上下文中注册 ,只要活动没有被销毁,您就会收到广播。如果您在应用程序上下文中注册,只要应用程序正在运行,您就会收到广播。
- 要停止接收广播,请调用unregisterReceiver(android.content.BroadcastReceiver)。当您不再需要接收器或上下文不再有效时,请务必取消注册接收器。
- 请注意您注册和取消注册接收器的位置,例如,如果您onCreate(Bundle)使用活动的上下文注册接收器,则应将其取消注册onDestroy()以防止将接收器从活动上下文中泄漏。如果您在 中注册接收器onResume(),则应取消注册onPause()以防止多次注册(如果您不想在暂停时接收广播,这可以减少不必要的系统开销)。不要在 中取消注册onSaveInstanceState(Bundle),因为如果用户移回历史堆栈,则不会调用此方法。
对进程状态的影响
您的状态BroadcastReceiver(无论是否正在运行)会影响其包含进程的状态,这反过来又会影响其被系统杀死的可能性。例如,当一个进程执行一个接收者(即当前正在其onReceive() 方法中运行代码)时,它被认为是一个前台进程。系统保持进程运行,除非在极端内存压力的情况下。
但是,一旦您的代码从 返回onReceive(),BroadcastReceiver 就不再处于活动状态。接收者的主机进程变得与在其中运行的其他应用程序组件一样重要。如果该进程仅托管清单声明的接收器(用户从未或最近未与之交互的应用程序的常见情况),则在从 返回时onReceive(),系统会将其进程视为低优先级进程并可能将其杀死以为其他更重要的流程提供资源。
因此,您不应从广播接收器启动长时间运行的后台线程。之后onReceive(),系统可以随时杀死进程以回收内存,并在这样做时终止进程中运行的衍生线程。为避免这种情况,您应该调用goAsync()(如果您想要更多时间在后台线程中处理广播)或使用 调度JobService来自接收器的JobScheduler,以便系统知道该进程继续执行活动工作。有关更多信息,请参阅流程和应用程序生命周期。
下面的代码片段显示一个BroadcastReceiver使用goAsync()到标志,它需要更多的时间后才能完成onReceive()完成。如果您要在您的工作中完成的工作onReceive()时间长到导致 UI 线程丢失一帧(> 16 毫秒),从而使其更适合后台线程,这将特别有用。
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<String, Integer, String> {
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();
}
}
}
发送广播
Android 为应用程序提供了三种发送广播的方式:
- 该sendOrderedBroadcast(Intent, String) 方法一次向一个接收器发送广播。当每个接收器依次执行时,它可以将结果传播到下一个接收器,或者它可以完全中止广播以便它不会传递给其他接收器。可以使用匹配的意图过滤器的 android:priority 属性控制运行的顺序接收器;具有相同优先级的接收器将按任意顺序运行。
- 该sendBroadcast(Intent)方法以未定义的顺序向所有接收器发送广播。这称为正常广播。这更有效,但意味着接收器不能从其他接收器读取结果、传播从广播接收的数据或中止广播。
- 该LocalBroadcastManager.sendBroadcast方法将广播发送到与发送方位于同一应用程序中的接收方。如果您不需要跨应用发送广播,请使用本地广播。实现效率更高(不需要进程间通信),您无需担心与其他应用程序能够接收或发送您的广播相关的任何安全问题。
以下代码片段演示了如何通过创建 Intent 并调用sendBroadcast(Intent).
Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data", "Nothing to see here, move along.");
sendBroadcast(intent);
广播消息被包装在一个Intent对象中。Intent 的操作字符串必须提供应用程序的 Java 包名称语法并唯一标识广播事件。您可以使用 将附加信息附加到意图putExtra(String, Bundle)。您还可以通过调用setPackage(String)Intent将广播限制为同一组织中的一组应用程序。
注意:虽然意图用于发送广播和启动活动startActivity(Intent),但这些操作完全无关。广播接收器无法看到或捕获用于启动活动的意图;同样,当您广播意图时,您无法找到或启动活动。
使用权限限制广播
权限允许您将广播限制为拥有特定权限的应用程序集。您可以对广播的发送者或接收者实施限制。
使用权限发送
当您调用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使用该元素定义自定义权限 。
注意:安装应用程序时会注册自定义权限。定义自定义权限的应用程序必须在使用它的应用程序之前安装。
有权限接收
如果您在注册广播接收器时指定了一个权限参数(在您的清单registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)中的标签中或在 标签中),那么只有在其清单中使用标签请求许可(并且随后在危险时被授予许可)的广播公司 才能发送接收者的意图。
例如,假设您的接收应用程序有一个清单声明的接收器,如下所示:
<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”/ >
安全注意事项和最佳实践
以下是发送和接收广播的一些安全注意事项和最佳实践:
- 如果您不需要向应用程序外部的组件发送广播LocalBroadcastManager,则使用支持库中提供的 发送和接收本地广播。的LocalBroadcastManager效率要高得多(不需要进程间通信),并可以让你避免有关与其他应用程序能够接收或发送你的广播任何安全问题的思考。本地广播可以用作应用程序中的通用发布/订阅事件总线,而不会产生任何系统范围广播的开销。
- 如果许多应用程序已注册在其清单中接收相同的广播,则可能会导致系统启动大量应用程序,从而对设备性能和用户体验造成重大影响。为了避免这种情况,最好使用上下文注册而不是清单声明。有时,Android 系统本身会强制使用上下文注册的接收器。例如,CONNECTIVITY_ACTION广播仅传递给上下文注册的接收器。
- 不要使用隐式意图广播敏感信息。任何注册接收广播的应用程序都可以读取这些信息。可以通过三种方式控制谁可以接收您的广播:
- 您可以在发送广播时指定权限。
- 在Android 4.0及更高版本,可以指定一个 包与 setPackage(String)发送广播时。系统将广播限制为与包匹配的应用程序集。
- 您可以使用 发送本地广播LocalBroadcastManager。
- 当您注册接收器时,任何应用程序都可能向您应用程序的接收器发送潜在的恶意广播。可以通过三种方法来限制您的应用接收的广播:
- 您可以在注册广播接收器时指定权限。
- 对于清单声明的接收器,您可以在清单中将 android:exported 属性设置为“false”。接收器不会从应用程序外部的来源接收广播。
- 您可以使用 将自己限制为仅本地广播LocalBroadcastManager。
- 广播操作的命名空间是全局的。确保操作名称和其他字符串写在您拥有的命名空间中,否则您可能会无意中与其他应用程序冲突。
- 因为接收者的onReceive(Context, Intent)方法在主线程上运行,所以它应该快速执行并返回。如果您需要执行长时间运行的工作,请注意生成线程或启动后台服务,因为系统可以在onReceive()返回后杀死整个进程 。有关更多信息,请参阅对进程状态的影响要执行长时间运行的工作,我们建议:
- 调用goAsync()接收者的onReceive()方法并将 传递BroadcastReceiver.PendingResult给后台线程。这使广播在从 返回后保持活动状态onReceive()。但是,即使采用这种方法,系统也希望您能很快(不到 10 秒)完成广播。它确实允许您将工作转移到另一个线程以避免主线程出现故障。
- 使用JobScheduler. 有关更多信息,请参阅智能作业调度。
- 不要从广播接收器开始活动,因为用户体验不和谐;尤其是在接收器不止一个的情况下。相反,请考虑显示通知。