android BroadcastReceiver之无序广播、有
发送无序广播
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
sendBroadcast(intent);
随机将给定的意图广播给所有感兴趣的BroadcastReceivers。 这个调用是异步的; 它立即返回,您将继续在接收器运行时执行。任何app注册都可以接收,可以使用
intent.setPackage(String)
指定接收应用。没有传播任何结果来自接收者,接收者不能中止广播。 如果你想要允许接收方传播结果或中止广播,您必须使用以下命令发送有序广播sendOrderedBroadcast(Intent,String)
发送有序广播
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
sendOrderedBroadcast(intent,null);
一次向一个接收器发送广播。当接收器逐个顺序执行时,接收器可以向下传递结果,也可以完全中止广播,使其不再传递给其他接收器。接收器的运行顺序可以通过匹配的 intent-filter 的 android:priority 属性来控制;具有相同优先级的接收器将按随机顺序运行。
关于 android:priority 的取值范围,官网给出的是 -1000 ~ 1000 ,但是看到很多人设置成2147483647(Integer.MAX_VALUE)这个值,可能因为 android:priority 的属性值是 integer 类型,系统会拿这个值和其他值做比较,结果怎么都是它最大了。
<receiver android:name=".receiver.MyReceiver1"
android:protectionLevel="normal"
>
<intent-filter
android:priority="100">
<action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
代码中设置priority
IntentFilter intentFilter = new IntentFilter("com.sqw.testBroadcastReceiver.say.hi");
intentFilter.setPriority(100);
中断有序广播
public class MyReceiver2 extends BroadcastReceiver {
private static final String TAG = "MyReceiver2";
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"MyReceiver2 收到广播",Toast.LENGTH_SHORT).show();
Log.e(TAG, "onReceive: MyReceiver2 收到广播");
//终止广播像低优先级传递
abortBroadcast();
}
}
发送本地广播
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi.local");
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
BroadcastReceiver的设计初衷是全局性,可接收来自本应用和其他应用发过来的intent广播。这也同时给app带来了一定的安全风险。为了解决这个问题,LocalBroadcastManager横空出世。LocalBroadcastManager 只会将广播限定在当前应用程序中。LocalBroadcastManager 发送的广播不会离开你的应用程序,同样也不会接收来自其它应用程序的广播,因此你可以放心的在 LocalBroadcastManager 中传播敏感信息。同时由于LocalBroadcastManager不需要用到跨进程机制,因此相对 BroadcastReceiver 而言要更为高效。LocalBroadcastManager只在动态广播时使用,静态广播不能使用LocalBroadcastManager。
另外本地广播注册和反注册
方式与有序无序广播
不一样,需要用到 LocalBroadcastManager.getInstance(context).registerReceiver()
LocalReceiver1 localReceiver1 = new LocalReceiver1();
IntentFilter intentFilter = new IntentFilter("com.sqw.testBroadcastReceiver.say.hi.local");
//注册本地广播
LocalBroadcastManager.getInstance(context).registerReceiver(localReceiver1,intentFilter);
if(localReceiver1 != null){
//反注册本地广播
LocalBroadcastManager.getInstance(context).unregisterReceiver(localReceiver1);
}
粘性广播
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi.sticky");
sendStickyBroadcast(intent);
粘性广播通过 Context.sendStickyBroadcast() 函数来发送,用此函数发送的广播会一直滞留,当有匹配此广播的广播接收器被注册后,该广播接收器就会收到此条广播。使用此函数发送广播时,需要获得
BROADCAST_STICKY
权限:<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
sendStickyBroadcast() 只保留最后一条广播,并且一直保留下去,这样即使已经有广播接收器处理了该广播,当再有匹配的广播接收器被注册时,此广播仍会被接收。如果你只想处理一遍该广播,可以通过removeStickyBroadcast()
函数实现。
粘性广播在
Android5.0(API 21)
的时候就不推荐使用了,他们不提供安全性(任何人可以访问它们),没有保护(任何人都可以修改它们)以及许多其他问题。推荐使用非粘性广播,所以下文不再讨论粘性广播。
广播的安全性
因为只要注册了广播接收者,就可以收到广播,比如A、B接收者都注册了广播,当你只想向A接收者发送广播时,结果B接收者也能收到广播,也许数据就被泄露了,这是我们都不愿意看到的。所以BroadcastRecevier提供了限制接收者的方法,就是在发送时指定接收者权限receiverPermission
。
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
String receiverPermission = "com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION";
sendBroadcast(intent,receiverPermission);
当然有序广播也可以指定接收者权限receiverPermission, 本地广播是不可以的。
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
String receiverPermission = "com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION";
sendOrderedBroadcast(intent,receiverPermission);
要说明一下的是这里的receiverPermission
,是一个自定义权限
,需要在清单文件中定义后才能使用。如果没有声明自定义权限
,发送的广播将没有符合要求接收者。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.noahedu.my">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--自定义权限-->
<permission android:name="com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name="MyApplication"
android:theme="@style/AppTheme">
...
</application>
</manifest>
这里的 <uses-permission />
、 <permission />
可能大家会不怎么明白,这里说一下。
<uses-permission />
:这个是申请权限。
比如要访问网络就使用<uses-permission android:name="android.permission.INTERNET"/>
之后应用才有访问网络的权限。
<uses-permission android:name="android.permission.SEND_SMS"/>
声明要使用这个权限后就可以发送短信了,当然这是个危险权限,android6.0之后就需要动态申请了。
<permission />
这个是定义权限。就是声明一个新的权限,使用方法就如上面发送有权限限定的广播,应该还有其他使用方法,这里我暂时不知道。
当然这里也可以直接写系统定义的权限,这样可以不需要自定义权限,当然自定义权限安全性更高。
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
sendBroadcast(intent,Manifest.permission.SEND_SMS);
这里不讨论系统权限,还是使用自定义权限,那既然有定义权限,就有使用权限的地方,是的,如果你想接收到有权限限定的广播,就必须申请使用发送广播时定义的权限,也就是使用 <uses-permission />
,这里申请上面发送广播时定义的权限。
<uses-permission android:name="com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION"/>
这里你会想到一个问题,即使是在应用内发送广播,应用内难道也要申请权限吗?是的,也要申请权限
,不然也是收不到广播的。
这样之后,再按正常流程注册广播,就可以收到有权限的广播了。
限制广播发送者
可以限制接收者是否能收到广播,当然也有限制发送者的。
限制接收者是,发送的时候限定了权限。就是接收的地方,要声明权限,才能收到广播。
那么限制发送者,也就是在接收的地方定义权限了,在发送的地方就要申请权限。具体请看
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.noahedu.testmyservice">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--定义权限-->
<permission android:name="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<!--android:permission 在广播接收者中使用定义的权限-->
<receiver android:name=".receiver.ServiceReceiver"
android:permission="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"
>
<intent-filter>
<action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
</application>
</manifest>
这里receiver
的android:permission
属性可以使用定义的权限。这样发送者必须申明使用这个权限,发送的广播这里才能收到。
<uses-permission android:name="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"/>
发送的应用,清单文件这样申请使用这个权限之后,发送广播,接收方就可以收到了。
简单总结一下就是:
- (发/收)自定义权限
(<permission />)
- (发/收)设置自定义权限
sendOrderedBroadcast(Intent,String) 或 android:permission
- (收/发)申请使用自定义权限
<uses-permission />
到这里,其实大家也会想到一个问题,那就是可以双向绑定,这样安全性会更高!代码就不贴了
protectionLevel
但是这样还是不够安全,如果有人反编译了代码,就会发现自定义权限了,数据还是可能会泄漏,那有没有更高级的方法呢,如果是一个公司的两个app,使用相同的签名,可以将安全级别提升到签名级别,使用android:protectionLevel
<receiver android:name=".receiver.ServiceReceiver"
android:permission="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"
android:protectionLevel="signature">
<intent-filter>
<action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
这样只有具有相同签名的app,并且申请了对应权限,这里才会收到广播了。安全性会提高很多。
如果只是在本应用内使用,推荐使用本地广播LocalBroadcastManager
,效率会更高。
广播的性能问题
如果很多app注册了开机广播<action android:name="android.intent.action.BOOT_COMPLETED"/>
,那么系统会一个一个启动这些app进程,这样势必会消耗很多系统资源,对用户体验造成严重影响。这种情况建议动态注册。
安全注意事项和最佳做法
-
如果您不需要向应用以外的组件发送广播,则可以使用支持库中提供的 LocalBroadcastManager 来收发本地广播。LocalBroadcastManager 效率更高(无需进行进程间通信),并且您无需考虑其他应用在收发您的广播时带来的任何安全问题。本地广播可在您的应用中作为通用的发布/订阅事件总线,而不会产生任何系统级广播开销。
-
如果有许多应用在其清单中注册接收相同的广播,可能会导致系统启动大量应用,从而对设备性能和用户体验造成严重影响。为避免发生这种情况,请优先使用上下文注册而不是清单声明。有时,Android 系统本身会强制使用上下文注册的接收器。例如,CONNECTIVITY_ACTION 广播只会传送给上下文注册的接收器。
-
请勿使用隐式 intent 广播敏感信息。任何注册接收广播的应用都可以读取这些信息。您可以通过以下三种方式控制哪些应用可以接收您的广播:
- 您可以在发送广播时指定权限。
- 在 Android 4.0 及更高版本中,您可以在发送广播时使用 setPackage(String) 指定软件包。系统会将广播限定到与该软件包匹配的一组应用。
- 您可以使用 LocalBroadcastManager 发送本地广播。
-
当您注册接收器时,任何应用都可以向您应用的接收器发送潜在的恶意广播。您可以通过以下三种方式限制您的应用可以接收的广播:
- 您可以在注册广播接收器时指定权限。
- 对于清单声明的接收器,您可以在清单中将 android:exported 属性设置为“false”。这样一来,接收器就不会接收来自应用外部的广播。
- 您可以使用 LocalBroadcastManager 限制您的应用只接收本地广播。
-
广播操作的命名空间是全局性的。请确保在您自己的命名空间中编写操作名称和其他字符串,否则可能会无意中与其他应用发生冲突。
-
由于接收器的 onReceive(Context, Intent) 方法在主线程上运行,因此它会快速执行并返回。如果您需要执行长时间运行的工作,请谨慎生成线程或启动后台服务,因为系统可能会在 onReceive() 返回后终止整个进程。如需了解详情,请参阅对进程状态的影响。要执行长时间运行的工作,我们建议:
- 在接收器的
onReceive()
方法中调用 goAsync(),并将 BroadcastReceiver.PendingResult 传递给后台线程。这样,在从onReceive()
返回后,广播仍可保持活跃状态。不过,即使采用这种方法,系统仍希望您非常快速地完成广播(在 10 秒以内)。为避免影响主线程,它允许您将工作移到另一个线程。 - 使用 JobScheduler 调度作业。如需了解详情,请参阅智能作业调度。
- 在接收器的
-
请勿从广播接收器启动 Activity,否则会影响用户体验,尤其是有多个接收器时。相反,可以考虑显示通知。
参考:
goole官方-广播概览
Android四大组件之——广播
Android 广播权限保护
Android 基础知识3:四大组件之 Broadcast(广播)