Android应用可以从 Android系统 和 其他Android应用 发送或接收广播消息,类似于 发
布-订阅 设计模式。当感兴趣的事件发生时,发送这些广播。例如,Android系统在发生各种系统事件时
发送广播,例如系统启动或设备开始充电时。例如,应用程序还可以发送自定义广播,以通知其他应用程序他
们可能感兴趣的内容(例如,已下载了一些新数据)。
应用可以注册以接收特定广播。当发送广播时,系统自动将广播路由到已订阅接收该特定类型广播的应用。
一般而言,广播可以用作跨应用程序和普通用户流程之外的消息传递系统。但是,您必须小心,不要滥用机会
响应广播并在后台运行可能导致系统性能降低的作业。
通过以上介绍可以知道:在Android系统中,广播(Broadcast)是在组件之间传播数据的一种机制,这些组件
可以位于不同的进程中,起到进程间通信的作用。
关于系统广播
当系统发生各种系统事件时,系统会自动发送广播,例如当系统进出飞行模式时。系统广播将发送到订阅接收
事件的所有应用程序。
广播消息本身包装在一个 Intent 对象中,该对象的动作字符串标识发生的事件(例如 android.intent.action.AIRPLANE_MODE)。意图还可以包括捆绑到其额外字段中的附加信息。例如,
飞行模式意图包括一个布尔额外值,用于指示飞行模式是否打开。
有关如何读取意图并从意图获取操作字符串的更多信息,请参阅意图和意图过滤器。
有关系统广播操作的完整列表,请参阅Android SDK中的 BROADCAST_ACTIONS.TXT 文件。每个广播动作都有一个与之相关的常量字段。例如,常量的值 ACTION_AIRPLANE_MODE_CHANGED是 android.intent.action.AIRPLANE_MODE。每个广播操作的文档都在其关联的常量字段中提供。
系统广播的变化
随着Android平台的发展,它会定期更改系统广播的行为方式。如果您的应用针对Android 7.0(API级别24)或更高版本,或者如果它安装在运行Android 7.0或更高版本的设备上,请记住以下更改。
Android 9
从Android 9(API级别28)开始, NETWORK_STATE_CHANGED_ACTION 广播不会收到有关 用户位置或个人身份数据的信息 。
此外,如果您的应用安装在运行Android 9或更高版本的设备上,则来自Wi-Fi的系统广播不包含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及更高版本的应用必须CONNECTIVITY_ACTION使用注册广播registerReceiver(BroadcastReceiver, IntentFilter)。在清单中声明接收器不起作用。
接收广播
应用程序可以通过两种方式接收广播:通过 清单声明的接收器 和 上下文注册的接收器。
清单声明的接收器
如果您在清单中声明了广播接收器,系统会在发送广播时启动您的应用(如果应用尚未运行)。
⚠️注意:如果您的应用程序的目标是API级别26或更高级别,则不能使用清单来声明隐式广播的接收者(特定于您的应用程序的广播),除了一些免于该限制的隐式广播。在大多数情况下,您可以使用预定作业。
要在清单中声明广播接收器,请执行以下步骤:
在应用清单中指定元素。 1
2
3
4
5
6<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过滤器指定接收者订阅的广播操作。
- 子类BroadcastReceiver并实现onReceive(Context, Intent)。以下示例中的广播接收器记录并显示广播的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15private const val TAG = "MyBroadcastReceiver"
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
StringBuilder().apply {
append("Action: ${intent.action}\n")
append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
toString().also { log ->
Log.d(TAG, log)
Toast.makeText(context, log, Toast.LENGTH_LONG).show()
}
}
}
}
系统软件包管理器在安装应用程序时注册接收器。然后,接收器成为应用程序的单独入口点,这意味着如果应用程序当前未运行,系统可以启动应用程序并发送广播。
系统创建一个新的BroadcastReceiver组件对象来处理它接收的每个广播。 此对象仅在调用onReceive(Context,Intent)期间有效。 一旦您的代码从此方法返回,系统会认为该组件不再处于活动状态。
上下文注册的接收器
要使用上下文注册接收器,请执行以下步骤:
创建一个实例BroadcastReceiver。
1
val br: BroadcastReceiver = MyBroadcastReceiver()
IntentFilter通过调用registerReceiver(BroadcastReceiver, IntentFilter)以下命令创建并注册接收器:
1
2
3
4val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)
⚠️注意:要注册本地广播,请调用LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter)。
只要注册上下文有效,上下文注册的接收器就会接收广播。例如,如果您在Activity上下文中注册 ,只要Activity未被销毁,您就会收到广播。如果您在应用程序上下文中注册,则只要应用程序正在运行,您就会收到广播。
- 要停止接收广播,请调用unregisterReceiver(android.content.BroadcastReceiver)。当您不再需要接收器或上下文不再有效时,请务必取消注册接收器。
请注意注册和取消注册接收器的位置,例如,如果使用活动的上下文在onCreate(Bundle)中注册接收器,则应在onDestroy()中取消注册,以防止接收器泄漏到活动上下文之外。 如果在onResume()中注册接收器,则应在onPause()中注销它以防止多次注册(如果您不希望在暂停时接收广播,这可以减少不必要的系统开销)。 不要在onSaveInstanceState(Bundle)中取消注册,因为如果用户在历史堆栈中向后移动,则不会调用此方法。
对过程状态的影响
BroadcastReceiver的状态(无论是否正在运行)会影响其包含进程的状态,从而影响其被系统杀死的可能性。例如,当进程执行接收器(即,当前在其onReceive()方法中运行代码)时,它被认为是前台进程。除极端内存压力外,系统保持运行。
但是,一旦您的代码从onReceive()返回,BroadcastReceiver就不再处于活动状态。接收方的主机进程与其中运行的其他应用程序组件一样重要。如果该进程仅承载清单声明的接收者(用户从未或最近未与之交互的应用程序的常见情况),则从onReceive()返回时,系统将其进程视为低优先级进程并且可能杀死它以使资源可用于其他更重要的过程。
因此,您不应该从广播接收器开始长时间运行后台线程。在onReceive()之后,系统可以随时终止进程以回收内存,并且这样做会终止在进程中运行的生成线程。要避免这种情况,您应该调用goAsync()(如果您希望在后台线程中处理广播更多时间)或使用JobScheduler从接收器调度JobService,以便系统知道该进程继续执行活动工作。有关更多信息,请参阅进程和应用程序生命周期。
以下代码段显示了一个BroadcastReceiver,它使用goAsync()标记在onReceive()完成后需要更多时间才能完成。 如果要在onReceive()中完成的工作足够长,导致UI线程错过一个帧(> 16ms),使其更适合后台线程,则此功能尤其有用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31private 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() {
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)
// Must call finish() so the BroadcastReceiver can be recycled.
pendingResult.finish()
}
}
}
发送广播
Android为应用发送广播提供了三种方式:
- sendOrderedBroadcast(Intent,String)方法一次向一个接收器发送广播。 当每个接收器依次执行时,它可以将结果传播到下一个接收器,或者它可以完全中止广播,以便它不会传递给其他接收器。 可以使用android:priority属性来控制匹配intent-filter顺序接收器; 具有相同优先级的接收器将以任意顺序运行。
- sendBroadcast(Intent)方法以未定义的顺序向所有接收器发送广播。这称为正常广播。这更有效,但意味着接收器无法从其他接收器读取结果,传播从广播接收的数据或中止广播。
- LocalBroadcastManager.sendBroadcast方法将广播发送到与发送者位于同一应用程序中的接收者。如果您不需要跨应用程序发送广播,请使用本地广播。实现效率更高(无需进程间通信),您无需担心与其他应用程序能够接收或发送广播相关的任何安全问题。
以下代码段演示了如何通过创建Intent和调用来发送广播sendBroadcast(Intent)。1
2
3
4
5Intent().also { intent ->
intent.setAction("com.example.broadcast.MY_NOTIFICATION")
intent.putExtra("data", "Notice me senpai!")
sendBroadcast(intent)
}
广播消息包含在Intent对象中。 intent的操作字符串必须提供应用程序的Java包名称语法,并唯一标识广播事件。 您可以使用putExtra(String,Bundle)将其他信息附加到intent。 您还可以通过调用intent上的setPackage(String)将广播限制为同一组织中的一组应用程序。
⚠️注意:尽管意图用于发送广播和使用startActivity(Intent)启动活动,但这些操作完全不相关。 广播接收器无法查看或捕获用于启动活动的意图; 同样,当您广播意图时,您无法找到或开始活动。
限制具有权限的广播
权限允许您将广播限制为具有特定权限的应用程序集。您可以对广播的发送者或接收者实施限制。
发送权限
当您调用sendBroadcast(Intent,String)或sendOrderedBroadcast(Intent,String,BroadcastReceiver,Handler,int,String,Bundle)时,您可以指定权限参数。 只有那些已经在其清单中请求带有标签的许可的接收者(并且如果它是危险的,则随后被授予许可)可以接收广播。 例如,以下代码发送广播:1
sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)
要接收广播,接收应用必须请求权限,如下所示:1
<uses-permission android:name="android.permission.SEND_SMS"/>
您可以指定现有的系统权限SEND_SMS也可以使用该
⚠️注意:安装应用程序时会注册自定义权限。 必须在使用该应用程序的应用程序之前安装定义自定义权限的应用程序。
接收权限
如果您在注册广播接收器时指定了权限参数(在清单中带有registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)或 带有
例如,假设您的接收应用程序具有清单声明的接收器,如下所示:1
2
3
4
5
6<receiver android:name=".MyBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE"/>
</intent-filter>
</receiver>
或者您的接收应用程序有一个上下文注册的接收器,如下所示:1
2var filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null )
然后,为了能够向这些接收者发送广播,发送应用必须请求许可,如下所示:1
<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。有关更多信息,请参阅智能作业计划。
- 不要从广播接收器开始活动,因为用户体验很不稳定; 特别是如果有多个接收器。相反,请考虑显示通知。
原文:
[1]. https://developer.android.com/guide/components/broadcasts
参考:
[1]. https://www.jianshu.com/p/f348f6d7fe59