目录
文章来源于此:官方的才是正宗的
广播概述
Android应用程序可以从Android系统和其他Android应用程序发送或接收广播消息,类似于发布-订阅设计模式。这些广播是在感兴趣的事件发生时发送的。例如,当各种系统事件发生时,例如系统启动或设备开始充电时,Android系统发送广播。应用程序也可以发送定制的广播,例如,通知其他应用程序他们可能感兴趣的东西(例如,一些新的数据已经下载)。
应用程序可以注册接收特定的广播。当广播被发送时,系统会自动将广播路由到订阅了接收特定类型广播的应用程序。
一般而言,广播可以用作跨应用程序和普通用户流程之外的消息传递系统。但是,您必须小心,不要滥用响应广播和其在后台运行的机会,这会导致系统性能变慢。
系统广播
当系统发生各种系统事件时,系统会自动发送广播,例如当系统进出飞机模式时。系统广播将发送到订阅接收事件的所有应用程序。
广播消息本身包装在一个Intent对象中,该对象的动作字符串标识发生的事件。Intent还可以包括捆绑到其额外字段中的附加信息。例如,飞行模式意图包括一个布尔额外值,用于指示飞行模式是否打开。
系统广播的变化
随着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)及更高版本不发送以下系统广播:
(1)ACTION_NEW_PICTURE
(2)ACTION_NEW_VIDEO
此外,针对Android 7.0及更高版本的应用必须CONNECTIVITY_ACTION使用注册广播registerReceiver(BroadcastReceiver, IntentFilter)。在清单中声明接收器不起作用。
注册广播的两种方式
静态注册(在清单文件中注册)
如果在清单中声明广播接收器,则在发送广播时,系统将启动应用程序(如果应用程序尚未运行)。
注意:如果您的应用程序的API级别为26或更高,则不能使用清单声明隐式广播的接收器,除非有一些隐式广播免除了该限制。在大多数情况下,您可以使用scheduled jobs。
在清单文件中注册,有如下步骤:
(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>
(2)在代码中,则需要继承BroadcastReceiver,实现onReceive(Context, Intent)方法:
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
// do sth.
}
}
安装应用程序时,系统包管理器会注册接收器。然后接收器将成为应用程序的单独入口点,这意味着如果应用程序当前未运行,系统可以启动应用程序并发送广播。
系统创建一个新的BroadcastReceiver组件对象来处理它接收的每个广播。此对象仅在调用onReceive(Context, Intent)期间有效。一旦您的代码从此方法返回,系统就会认为该组件不再处于活动状态。
动态注册(在代码中注册)
(1)创建一个BroadcastReceiver 实例
BroadcastReceiver br = new MyBroadcastReceiver();
(2)创建一个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)
上下文注册的接收器只要其注册context有效,就会接收广播。例如,如果您在Activity context中注册,那么只要Activity没有被破坏,您就会收到广播。如果您注册Application context,那么只要应用程序运行,您就会收到广播。
(3)要停止接收广播,请调用UnregisterReceiver(android.content.broadcastReceiver)。
注意:当您不再需要接收器或context不再有效时,请确保注销接收器。注意注册接收器和注销接收器的位置——
例如,如果在OnCreate(bundle)中使用Activity的context注册接收器,则应在OnDestroy()中注销它,以防止接收器从Activity的context中泄漏。
如果您在onResume()中注册了一个接收器,您应该在onPause()中注销它,以防止多次注册它(如果您不想在暂停时接收广播,这可以减少不必要的系统开销)。
不要在OnSaveInstanceState(bundle)中注销,因为如果用户移回历史堆栈中,则不会调用此函数。
对过程状态的影响
广播接收器的状态(不管它是否正在运行)会影响其包含进程的状态,这反过来会影响它被系统杀死的可能性。例如,当一个进程执行一个接收者(即当前在其onReceive()方法中运行代码)时,它被认为是一个前台进程。系统保持进程运行,除非在极端内存压力的情况下。
但是,一旦代码从onReceive()返回,则BroadcastReceiver将不再处于活动状态。广播接收器的主进程只会与运行在其中的其他应用程序组件一样重要。如果该进程只有一个在清单文件中声明的广播接收器(对于用户从未或最近未与之交互的应用程序来说,这是一种常见情况),那么在从onReceive()返回时,系统会将其进程视为低优先级进程,并可能会杀死它以使资源可用于其他更重要的进程。
因此,您不应该从广播接收器启动长时间运行的后台线程。在onReceive()之后,系统可以在任何时候终止进程以回收内存,并且这样做会终止进程中运行的生成线程。为了避免这种情况,您应该调用goAsync()(如果您希望有更多的时间在后台线程中处理广播),或者使用JobScheduler从广播接收器中调度JobService,这样系统就知道进程继续执行Activity的工作。
以下代码段显示了一个BroadcastReceiver,它使用goAsync()来标记在OnReceive()完成后,还需要更多时间才能完成:
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();
}
}
}
发送广播
(1)sendOrderedBroadcast(Intent, String) 方法一次向一个接收器发送广播。当每个接收器依次执行时,它可以将结果传播到下一个接收器,或者完全中止广播,这样就不会将结果传递给其他接收器。使用android:priority属性(匹配意向过滤器)可以控制订单接收者的运行;具有相同优先级的接收者将以任意顺序运行。
(2)sendBroadcast(Intent)方法以未定义的顺序向所有接收器发送广播。这称为正常广播。这更有效,但意味着接收器无法从其他接收器读取结果、传播从广播接收到的数据或中止广播。
(3)LocalBroadcastManager.sendBroadcast方法将广播发送到与发送方位于同一应用程序中的接收器。如果您不需要跨应用程序发送广播,请使用本地广播。实现更高效(不需要进程间通信),并且您不需要担心与其他能够接收或发送广播的应用程序相关的任何安全问题。
下面的代码片段演示如何通过创建Intent并调用sendBroadcast(intent)来发送广播:
Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data","Notice me senpai!");
sendBroadcast(intent);
广播消息包装在Intent对象中。
Intent的action字符串必须提供应用程序的Java包名称语法并唯一标识广播事件。
您可以使用putExtra(String, Bundle)将附加信息附加到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)或使用<permission>元素定义自定义权限。
注意:自定义权限在安装应用程序时注册。定义自定义权限的应用程序必须在使用它的应用程序之前安装。
接收权限
如果在注册广播接收器(使用 registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) 或清单文件中的<receiver>标记注册)时指定了权限参数,则只有使用清单中的<uses permission>标记请求权限的广播者才能向广播接收器发送广播意图。
例如,假设您的接收应用程序有一个在清单文件声明的广播接收者,如下所示:
<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"/>
注意事项和最佳实践
以下是发送和接收广播的一些安全注意事项和最佳做法:
(1)如果您不需要向应用程序外部的组件发送广播,则使用支持库中提供的LocalBroadcastManager发送和接收本地广播。LocalBroadcastManager效率更高(无需进程间通信),您也可以避免考虑与其他应用程序间能够接收或发送您的广播而产生的任何安全问题。本地广播可以在您的应用中用作通用发布/订阅事件总线,而无需系统广播的任何开销。
(2)如果许多应用已注册在其清单中接收相同的广播,则可能导致系统启动大量应用,从而对设备性能和用户体验产生重大影响。 为避免这种情况,请优先使用上下文动态注册而不是清单文件中声明。有时,Android系统本身会强制使用上下文动态注册的接收器。 例如,CONNECTIVITY_ACTION广播仅传递给上下文动态注册的接收器。
(3)不要使用隐式意图广播敏感信息。 任何注册接收广播的应用都可以读取该信息。 有三种方法可以控制谁可以接收您的广播:
1,您可以在发送广播时指定权限。
2,在Android 4.0及更高版本中,您可以在发送广播时指定包含setPackage(String)的包。 系统将广播限制为与包匹配的应用程序集。
3,您可以使用LocalBroadcastManager发送本地广播。
(4)当您注册广播接收器时,任何应用都可以向您的广播接收器发送潜在的恶意广播。 有三种方法可以限制应用收到的广播:
1,您可以在注册广播接收器时指定权限。
2,对于清单声明的接收器,您可以在清单中将android:exported属性设置为“false”。 接收方不接收来自应用程序之外的来源的广播。
3,您可以将自己限制为仅使用LocalBroadcastManager进行本地广播。
(5)广播action是全局的。 确保action名称和其他字符串都写在您拥有的命名空间中,否则您可能会无意中与其他应用程序发生冲突。
(6)因为接收者的onReceive(Context,Intent)方法在主线程上运行,所以它应该执行并快速返回。 因为系统可以在onReceive()返回后终止整个进程,所以如果您需要执行长时间运行的工作,请小心生成线程或启动后台服务。 此时,我们建议:
1,在接收者的onReceive()方法中调用goAsync()并将BroadcastReceiver.PendingResult传递给后台线程。 这使得从onReceive()返回后广播保持活动状态。 但是,即使采用这种方法,系统也希望您能够非常快速地完成广播(10秒以内)。 它允许您将工作移动到另一个线程,以避免故障主线程。
2,使用JobScheduler安排作业。
(7)不要从广播接收器开始Activity,因为用户体验很不稳定,特别是如果有多个广播接收器的情况下。 相反,请考虑显示通知。
终于终于把广播看完了,耗时了一周的这一篇帖子,真是艰辛。