最近在做Android P的手机适配,发现app在Android P的系统中出现了严重bug,这一bug就与广播有关,通过翻译官网资料如下,并按照官网提示解决问题。
广播概述
Android应用可以发送或接收来自Android系统和其他Android应用的广播消息,类似于 发布 - 订阅 设计模式。这些广播是在感兴趣的事件发生时发送的。例如,Android系统在发生各种系统事件时发送广播,例如系统启动或设备开始充电时。应用程序还可以发送自定义广播,例如,向其他应用程序通知他们可能感兴趣的内容(例如,某些新数据已被下载)。
应用可以注册以接收特定的广播。发送广播时,系统会自动将广播路由到已预订接收该特定广播类型的应用。
一般来说,广播可以用作应用程序之间和正常用户流程之外的消息传递系统。但是,您必须小心,不要滥用机会在后台响应广播和运行作业,这可能会导致系统性能降低
广播概述
Android应用可以发送或接收来自Android系统和其他Android应用的广播消息,类似于 发布 - 订阅 设计模式。这些广播是在感兴趣的事件发生时发送的。例如,Android系统在发生各种系统事件时发送广播,例如系统启动或设备开始充电时。应用程序还可以发送自定义广播,例如,向其他应用程序通知他们可能感兴趣的内容(例如,某些新数据已被下载)。
应用可以注册以接收特定的广播。发送广播时,系统会自动将广播路由到已预订接收该特定广播类型的应用。
一般来说,广播可以用作应用程序之间和正常用户流程之外的消息传递系统。但是,您必须小心,不要滥用机会在后台响应广播和运行作业,这可能会导致系统性能降低。
关于系统广播
系统在发生各种系统事件时自动发送广播,例如系统切入和切出飞行模式。系统广播被发送到订阅接收事件的所有应用程序。
广播消息本身包装在一个Intent
对象中,该对象的操作字符串标识发生的事件(例如android.intent.action.AIRPLANE_MODE
)。意图还可能包括捆绑到其额外领域的附加信息。例如,飞机模式意图包含一个布尔额外值,表示飞行模式是否打开。
有关如何读取意图并从意图获取操作字符串的更多信息,请参阅意向和意图过滤器。
有关系统广播操作的完整列表,请参阅BROADCAST_ACTIONS.TXT
Android SDK中的 文件。每个广播动作都有一个与其相关的常量字段。例如,常数的值 ACTION_AIRPLANE_MODE_CHANGED
是android.intent.action.AIRPLANE_MODE
。每个广播操作的文档可在其关联的常量字段中找到。
系统广播的变化
Android 7.0及更高版本不再发送以下系统广播。此优化会影响所有应用,而不仅仅是针对Android 7.0的应用。
ACTION_NEW_PICTURE
ACTION_NEW_VIDEO
定位到Android 7.0(API级别24)及更高版本的应用必须通过以下方式注册以下广播registerReceiver(BroadcastReceiver, IntentFilter)
。在清单中声明接收者不起作用。
从Android 8.0(API级别26)开始,系统对清单声明的接收方施加额外的限制。如果您的应用定位到API级别26或更高,则无法使用清单来声明大多数隐式广播的接收方(专门针对您的应用的广播)
接收广播
应用程序可以通过两种方式接收广播:通过清单声明的接收器和上下文注册的接收器。
清单声明的接收者
如果您在清单中声明广播接收器,系统会在广播发送时启动您的应用程序(如果应用程序尚未运行)。
注意:
如果您的应用程序的目标API级别为26或更高,则不能使用清单来声明隐式广播的接收器(专门不针对您的应用程序的广播),但可以免除该限制的一些隐式广播除外。在大多数情况下,您可以使用预定作业。
要在清单中声明广播接收器,请执行以下步骤:
在应用程序的清单中指定元素
android:name=".MyBroadcastReceiver" android:exported="true">
android:name="android.intent.action.BOOT_COMPLETED"/>
android:name="android.intent.action.INPUT_METHOD_CHANGED" />
意图过滤器指定您的接收器订阅的广播操作。
2.子类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 ();
2.通过以下方式创建IntentFilter
并注册接收器registerReceiver(BroadcastReceiver, IntentFilter)
:
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();
}
}
- 注意:要注册本地广播,请用
LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter)
替代。只要注册上下文有效,上下文注册的接收器就会接收广播。举个例子,如果你在Activity
上下文中注册 ,只要活动没有被破坏,你就会收到广播。如果您注册了应用程序上下文,只要应用程序正在运行,就会收到广播。
要停止接收广播,请使用unregisterReceiver(android.content.BroadcastReceiver)
。当您不再需要接收器或上下文不再有效时,请务必注销接收器。
注意你注册和注销接收者的位置,例如,如果你注册一个接收者来onCreate(Bundle)
使用活动的上下文,你应该在onDestroy()
取消注册以防止接收者从活动上下文中泄漏。如果你在onResume()
中注册一个接收器,你应该在
onPause()
中取消注册以防止多次注册(如果你不想在暂停时收到广播,这可以减少不必要的系统开销)。不要取消注册onSaveInstanceState(Bundle)
,因为如果用户移回到历史堆栈中,则不会调用它。
对过程状态的影响
您的状态BroadcastReceiver
(无论是否在运行)会影响其包含进程的状态,从而影响其被系统杀死的可能性。例如,当一个进程执行一个接收者(也就是说,当前在其onReceive()
方法中运行该代码)时,它被认为是一个前台进程。除非存在极大的记忆压力,否则系统会继续运行。
但是,一旦代码返回onReceive()
,BroadcastReceiver不再处于活动状态。接收者的主机进程变得与其中运行的其他应用程序组件一样重要。如果该进程仅托管清单声明的接收方(这是用户从未或最近未与之交互过的应用的常见情况),那么在返回时onReceive()
,系统将其进程视为低优先级进程,并可能将其终止为其他更重要的流程提供资源。
出于这个原因,您不应该从广播接收器开始长时间运行后台线程。之后onReceive()
,系统可以随时终止进程以回收内存,并且这样做会终止进程中运行的衍生线程。为了避免这种情况,您应该调用goAsync()
(如果您希望有更多时间在后台线程中处理广播),或者JobService
使用接收者从接收者调度a JobScheduler
,因此系统知道该过程继续执行主动工作。有关更多信息,请参阅进程和应用程序生命周期。
下面的代码片段显示了一个BroadcastReceiver
使用goAsync()
到它需要更多的时间来完成后旗onReceive()
完成。如果你想在你的工作中完成的工作onReceive()
足够长,导致UI线程错过一帧(> 16ms),这使得它更适合后台线程,这是特别有用的。
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(final Context context, final Intent intent) {
final PendingResult pendingResult = goAsync();
AsyncTask<String, Integer, String> asyncTask = new AsyncTask<String, Integer, String>() {
@Override
protected String doInBackground(String... params) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
Log.d(TAG, log);
// Must call finish() so the BroadcastReceiver can be recycled.
pendingResult.finish();
return data;
}
};
asyncTask.execute();
}
}
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","Notice me senpai!");
sendBroadcast(intent);
广播消息被包裹在一个
Intent
对象中。意图的动作字符串必须提供应用程序的Java包名称语法并唯一标识广播事件。您可以将附加信息附加到意图
putExtra(String, Bundle)
。您也可以通过调用
setPackage(String)
意图将广播限制在同一组织中的一组应用程序。
注意:尽管意图用于发送广播和开始活动startActivity(Intent)
,但这些操作完全不相关。广播接收器无法看到或捕获用于启动活动的意图; 同样,当你广播一个意图时,你不能找到或开始一个活动。
限制带有权限的广播
权限允许您将广播限制为拥有特定权限的应用程序集。您可以对广播的发送者或接收者执行限制。
用权限发送
当你用sendBroadcast(Intent, String)
或者 sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
,你可以指定一个权限参数。只有接收方已经请求了该许可在他们的清单中标记(并且随后在危险时被授予许可)可以接收广播。例如,下面的代码发送一个广播:
sendBroadcast(new Intent("com.example.NOTIFY"),
Manifest.permission.SEND_SMS);
要接收广播,接收应用程序必须请求权限,如下所示:
android:name="android.permission.SEND_SMS"/>
您可以指定现有的系统权限SEND_SMS
或使用该
元素定义自定义权限 。有关一般权限和安全性的信息,请参阅系统权限。
注意:自定义权限是在安装应用程序时注册的。定义自定义权限的应用程序必须安装在使用它的应用程序之前。
接收权限
如果您在注册广播接收机时指定一个权限参数(无论是在清单registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
中的
标签还是在 标签中),那么只有
在其清单中通过标签请求权限的广播机构 (如果存在危险,则随后被授予权限)可以发送接收者的意图。
例如,假设您的接收应用程序具有清单声明的接收方,如下所示:
android:name=".MyBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
android:name="android.intent.action.AIRPLANE_MODE"/>
或者您的接收应用程序有一个上下文注册的接收器,如下所示:
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
然后,为了能够向这些接收者发送广播,发送应用程序必须请求权限,如下所示:
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
。安排工作。有关更多信息,请参阅智能作业计划。
不要从广播接收机开始活动,因为用户体验很刺耳; 特别是如果有多个接收器的话。相反,请考虑显示通知。