BroadcastReceiver 介绍
BroadcastReceiver是Android开发4大组件之一。正如其名BroadcastReceiver作为广播接收者,用来监听系统全局的广播消息。广播(Broadcast)在Android系统中是在各组件之间传播数据的一种机制,这些组件可以位于不同的进程中。作为一个全局的广播监听组件,BroadcastReceiver可以很容易的实现系统中不同组件、不同应用之间的通信。
BroadcastReceiver 是对发送出来的 Broadcast 进行过滤、接受和响应的组件。而广播则被装入一个 Intent 对象,然后通过调用系统提供的 Context.sendBroadcast() 或 Context.sendOrderBroadcast() 将该 Intent 对象已广播的形式发送出去。发出的Intent对象需要包括一个action信息,Category信息以及一下其他extra信息。广播发出去之后,所有注册了该action的 BroadcastReceiver 则会收到发出的广播。BroadcastReceiver 通过检查接收的广播的action,Category等信息进行相应的处理。
BroadcastReceiver 接收广播的处理十分简单,在应用开发中,我们只用要重写BroadcastReceiver的onReceive(Context context, Intent intent)方法就可以。
class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Receiver Broadcast: " + intent.getAction());
}
}
BroadcastReceiver 的注册
- 静态注册
静态注册即在 AndroidManifest.xml 清单文件中为 BroadcastReceiver 进行注册,使用 < receiver > 标签声明,并在标签内用 < intent-filter > 标签设置过滤器。这种形式的 BroadcastReceiver 的生命周期伴随着整个应用,如果这种方式处理的是系统广播,那么不管应用是否在运行,该广播接收器都能接收到该广播(非系统app需要运行过一次,且有权限接收该广播)。
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.test"/>
</intent-filter>
</receiver>
- 动态注册
动态注册 BroadcastReceiver 是在代码中定义并设置好一个 IntentFilter 对象,然后在需要注册的地方调用 Context.registerReceiver() 方法,调用Context.unregisterReceiver() 方法取消注册
MyBroadcastReceiver mReceiver = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.test");
registerReceiver(mReceiver, filter);
unregisterReceiver(mReceiver);
广播发送
发送广播有两种方式:
- sendBroadcast 普通广播
- sendOrderedBroadcast 有序广播
- 普通广播
private static final String ACTION = "android.intent.action.test";
Intent intent = new Intent(ACTION);
sendBroadcast(intent);
发送普通广播,对于多个注册了该action的广播,都能一块接收到,并没有接收的先后顺序。也没有办法阻止另一个接收者接收这个广播。
- 有序广播
private static final String ACTION = "android.intent.action.test";
Intent intent = new Intent(ACTION);
sendOrderedBroadcast(intent, null);
发送有序广播, 有多个广播接收者,则按顺序接收广播,高优先级的先收到,然后是低优先级的。优先级一般在注册的时候通过 priority 属性来进行设置,优先级从-1000~1000,数越大,优先级越高。另外。优先级高的 Receiver 也能在处理完操作后向优先级低的 Receiver 传送处理结果。此外,高优先级的 BroadcastReceiver 也能调用 abortBroadcast() 方法截断广播,这样低优先级的广播接收器就无法接收到广播了。
<receiver android:name=".MyBroadcastReceiver1">
<intent-filter android:priority="100">
<action android:name="android.intent.action.test"/>
</intent-filter>
</receiver>
<receiver android:name=".MyBroadcastReceiver2">
<intent-filter android:priority="99">
<action android:name="android.intent.action.test"/>
</intent-filter>
</receiver>
class MyBroadcastReceiver1 extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver1";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.eq("android.intent.action.test")) {
String id = intent.getStringExtra("id");
Log.d(TAG, "Receiver Broadcast: " + action + ", id = " + id);
Bundle bundle = new Bundle();
bundle.putString("data", "hello, world!");
setResultExtras(bundle);
}
}
}
class MyBroadcastReceiver2 extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver2";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.eq("android.intent.action.test")) {
String id = intent.getStringExtra("id");
Log.d(TAG, "Receiver Broadcast: " + action + ", id = " + id);
String data = getResultExtras(true).getString("data");
Log.d(TAG, "getResultExtras data: " + data);
}
}
}
上面的例子中,MyBroadcastReceiver2即可接收的MyBroadcastReceiver1处理结果。
sendOrderedBroadcast(Intent intent, String receiverPermission)方法中的第二个参数用来设置接收者是否需要申请权限。如果参数为null,则接收者不需要申请权限就可以接收广播。如果不为空则需要申请权限,否则接收不到广播。
Intent intent = new Intent("android.intent.action.test");
intent.putExtra("id", 100);
sendOrderedBroadcast(intent, "test.permission.broadcast");
接收端需要申请权限才可以接收到该广播。
<permission android:protectionLevel="normal" android:name="test.permission.broadcast"/>
<uses-permission android:name="test.permission.broadcast" />
本地广播
前面说到的广播全都是属于系统全局广播,即发出的广播后可以被其他应用接收到,而且也可以接收到其他应用发送出的广播,这样可能会有不安全因素。因此,在某些情况下可以采用本地广播机制,使用这个机制发出的广播只能在应用内部进行传递,而且广播接收器也只能接收本应用内自身发出的广播。
本地广播拥有以下特点:
- 发送的广播只会在当前APP中传播,不会泄露给其它APP,确保了数据传输的安全。
- 其它APP的广播无法发送到本APP中,不用担心安全漏洞被其它APP所利用。
- 比系统全局广播更加高效。
本地广播是使用 LocalBroadcastManager 来对广播进行管理,LocalBroadcastManager是support V4包中的一个组件。
函数 | 作用 |
---|---|
LocalBroadcastManager.getInstance(this).registerReceiver(BroadcastReceiver, IntentFilter) | 注册Receiver |
LocalBroadcastManager.getInstance(this).unregisterReceiver(BroadcastReceiver); | 注销Receiver |
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent) | 发送异步广播 |
LocalBroadcastManager.getInstance(this).sendBroadcastSync(Intent) | 发送同步广播 |
本地广播的接收者与全局广播是一样的。只是注册和发送的方式改变了而已。另外,本地广播是无法通过静态注册的方式来接收的,因为静态注册广播主要是为了在程序未启动的情况下也能接收广播,而本地广播是应用自己发送的,此时应用肯定是启动的了。
public class TestActivity extends Activity {
private LocalBroadcastManager mLocalBroadcastManager;
private LocalReceiver mLocalReceiver;
private final String LOCAL_ACTION = "android.intent.action.local_test";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
mLocalReceiver = new LocalReceiver();
IntentFilter filter = new IntentFilter(LOCAL_ACTION);
mLocalBroadcastManager.registerReceiver(mLocalReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
mLocalBroadcastManager.unregisterReceiver(mLocalReceiver);
}
public void sendLocalBroadcast() {
Intent intent = new Intent(LOCAL_ACTION);
mLocalBroadcastManager.sendBroadcast(intent);
}
public static class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("LocalReceiver", "onReceive : " + intent.getAction());
}
}
}
生命周期
BroadcastReceiver 的生命周期与其他系统组件还是有些不一样的。在 AndroidManifest.xml 中注册的BroadcastReceiver , 每次收到一个 Intent , 也就是 onReceive 被回调的时候, 这个 BroadcastReceiver都是新创建出来的。也就是说, 出了 onReceive , 这个 BroadcastReceiver对象的生命周期就已经到头了, 这也是为什么我们不能在 onReceive 中进行一些异步操作的原因, 有可能异步操作还没完成, BroadcastReceiver所在的进程就被kill了。 所以,我们在接受到广播后,需要进程执行一些异步任务是,需要配合 Service 来实现。
而动态注册的 BroadcastReceiver 对象的生命其实是受我们控制的。既然是动态注册,那么我们的应用一定是处于运行状态的。此时,只要该进程不被kill掉,被注册的 BroadcastReceiver 都是在注册指定的实例中处理接收的广播数据的。
使用自定义权限
使用全局广播接收器存在一个问题,即系统内的任何应用均可触发我们的 Receiver 。通常情况下我们是不希望如此的。为了避免这种问题,可以有以下方式:
- 使用本地广播,但本地广播只允许本应用自己发出;
- 在 AndroidManifest.xml 清单文件中为 < receiver > 标签添加一个 android:exported="false" 属性,标明该 Receiver 仅限应用内部使用,这样的结果一样是无法接收到外部发来的广播。
- 使用自定义权限来对接收者进行过滤,只有声明了指定权限的 Receiver 才可以接收到该广播。
使用私有权限,首先需要在AndroidManifest.xml 清单文件中声明自定义权限。
<permission
android:name="com.test.permission.receiver"
android:protectionLevel="signature" />
声明自定义权限时必须同时指定 protectionLevel 属性值,系统根据该属性值确定自定义权限的使用方式
属性值 | 限定方式 |
---|---|
normal | 默认值。较低风险的权限,对其他应用,系统和用户来说风险最小。系统在安装应用时会自动批准授予应用该类型的权限,不要求用户明确批准(虽然用户在安装之前总是可以选择查看这些权限) |
dangerous | 较高风险的权限,请求该类型权限的应用程序会访问用户私有数据或对设备进行控制,从而可能对用户造成负面影响。因为这种类型的许可引入了潜在风险,所以系统可能不会自动将其授予请求的应用。例如,系统可以向用户显示由应用请求的任何危险许可,并且在继续之前需要确认,或者可以采取一些其他方法来避免用户自动允许 |
signature | 只有在请求该权限的应用与声明权限的应用使用相同的证书签名时,系统才会授予权限。如果证书匹配,系统会自动授予权限而不通知用户或要求用户的明确批准 |
signatureOrSystem | 系统仅授予Android系统映像中与声明权限的应用使用相同的证书签名的应用。请避免使用此选项,“signature”级别足以满足大多数需求,“signatureOrSystem”权限用于某些特殊情况 |
新建一个工程作为发送端,在它的 AndroidManifest.xml 文件中创建一个自定义权限,并声明该权限。
<permission android:name="com.test.permission.receiver" android:protectionLevel="signature" />
<uses-permission android:name="com.test.permission.receiver" />
发送含有该权限声明的 Broadcast,这样,只有使用相同证书签名且声明该权限的应用才能接收到该 Broadcast。
private final String PRIVATE_PERMISSION = "com.test.permission.receiver";
public void sendPermissionBroadcast(View view) {
Intent intent = new Intent("android.intent.action.test");
sendBroadcast(intent, PRIVATE_PERMISSION);
}
而接收端,则只需要在AndroidManifest.xml 文件中也声明该权限,就可以接收到该广播了。
<uses-permission android:name="com.test.permission.receiver" />
接收端注册广播:
private final String PRIVATE_PERMISSION = "com.test.permission.receiver";
private final String ACTION = "android.intent.action.test";
IntentFilter intentFilter1 = new IntentFilter(ACTION);
MyBroadcastReceiver receiver = new MyBroadcastReceiver();
registerReceiver(receiver, intentFilter1, PRIVATE_PERMISSION, null);
Android常用系统广播
广播名 | 说明 | 备注 |
---|---|---|
Intent.ACTION_BATTERY_LO | 电池电量低 | |
Intent.ACTION_BATTERY_OK | 电池电量充足 | |
Intent.ACTION_AIRPLANE_MODE_CHANGED | 关闭或打开飞行模式 | |
Intent.ACTION_BATTERY_CHANGED | 充电状态,或者电池的电量发生变化 | 电荷级别改变,只能在代码注册 |
Intent.ACTION_BATTERY_LOW | 电池电量低 | |
Intent.ACTION_BATTERY_OKAY | 电池电量充足 | 从电池电量低变化到饱满时会发出广播 |
Intent.ACTION_BOOT_COMPLETED | 在系统启动完成后,这个动作被广播一次 | 只有一次 |
Intent.ACTION_CAMERA_BUTTON | 按下照相时的拍照按键时发出的广播 | 硬件按键 |
Intent.ACTION_CLOSE_SYSTEM_DIALOGS | 当屏幕超时进行锁屏时,当用户按下电源按钮,长按或短按(不管有没跳出话框),进行锁屏 | |
Intent.ACTION_CONFIGURATION_CHANGED | 设备当前设置被改变时发出的广播 | 界面语言,设备方向,等 请参考Configuration.java |
Intent.ACTION_DATE_CHANGED | 设备日期发生改变时 | |
Intent.ACTION_HEADSET_PLUG | 在耳机口上插入耳机时发出的广播 | |
Intent.ACTION_INPUT_METHOD_CHANGED | 改变输入法时发出的广播 | |
Intent.ACTION_LOCALE_CHANGED | 设备当前区域设置已更改时发出的广播 | |
Intent.ACTION_MANAGE_PACKAGE_STORAGE | 表示用户和包管理所承认的低内存状态通知应该开始 | |
Intent.ACTION_MEDIA_BAD_REMOVAL | 未正确移除SD卡 | 扩展卡已经从SD卡插槽拔出,但是挂载点 (mount point) 还没解除 (unmount) |
Intent.ACTION_MEDIA_CHECKING | 插入外部储存装置 | 比如SD卡时,系统会检验SD卡,此时发出的广播 |
Intent.ACTION_MEDIA_EJECT | 已拔掉外部大容量储存设备发出的广播 | 不管有没有正确卸载 |
Intent.ACTION_MEDIA_MOUNTED | 插入SD卡并且已正确安装 | 扩展介质被插入而且已经被挂载 |
Intent.ACTION_MEDIA_NOFS | 拓展介质存在,但使用不兼容FS(或为空)的路径安装点检查介质包含在Intent.mData领域 | |
Intent.ACTION_MEDIA_REMOVED | 外部储存设备已被移除,扩展介质被移除 | 不管有没正确卸载,都会发出此广播 |
Intent.ACTION_MEDIA_SCANNER_FINISHED | 已经扫描完介质的一个目录 | |
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE | 请求媒体扫描仪扫描文件并将其添加到媒体数据库 | |
Intent.ACTION_MEDIA_SCANNER_STARTED | 开始扫描介质的一个目录 | |
Intent.ACTION_MEDIA_SHARED | 扩展介质的挂载被解除 (unmount) | 它已经作为 USB 大容量存储被共享 |
Intent.ACTION_PACKAGE_ADDED | 成功的安装APK | 数据包括包名(最新安装的包程序不能接收到这个广播) |
Intent.ACTION_PACKAGE_CHANGED | 一个已存在的应用程序包已经改变 | 包括包名 |
Intent.ACTION_PACKAGE_DATA_CLEARED | 清除一个应用程序的数据时发出的广播 | 清除包程序不能接收到这个广播 |
Intent.ACTION_PACKAGE_INSTALL | 触发一个下载并且完成安装时发出的广播 | 比如在电子市场里下载应用 |
Intent.ACTION_PACKAGE_REMOVED | 成功的删除某个APK之后发出的广播 | 正在被安装的包程序不能接收到这个广播 |
Intent.ACTION_PACKAGE_REPLACED | 替换一个现有的安装包时发出的广播(不管现在安装的APP比之前的新还是旧 | |
Intent.ACTION_PACKAGE_RESTARTED | 用户重新开始一个包 | 重新开始包程序不能接收到这个广播 |
Intent.ACTION_POWER_CONNECTED | 插上外部电源时发出的广播 | |
Intent.ACTION_POWER_DISCONNECTED | 已断开外部电源连接时发出的广播 | |
Intent.ACTION_REBOOT | 重启设备时的广播 | |
Intent.ACTION_SCREEN_OFF | 屏幕被关闭之后的广播 | |
Intent.ACTION_SCREEN_ON | 屏幕被打开之后的广播 | |
Intent.ACTION_SHUTDOWN | 关闭系统时发出的广播 | |
Intent.ACTION_TIMEZONE_CHANGED | 时区发生改变时发出的广播 | |
Intent.ACTION_TIME_CHANGED | 时间被设置时发出的广播 | |
Intent.ACTION_TIME_TICK | 当前时间已经变化(正常的时间流逝) | 每分钟都发送,在代码注册 |
Intent.ACTION_UID_REMOVED | 一个用户ID已经从系统中移除发出的广播 | |
Intent.ACTION_UMS_CONNECTED | 设备已进入USB大容量储存状态时发出的广播 | |
Intent.ACTION_UMS_DISCONNECTED | 设备已从USB大容量储存状态转为正常状态时发出的广播 | |
Intent.ACTION_WALLPAPER_CHANGED | 设备墙纸已改变时发出的广播 | |
Intent.ACTION_USER_PRESENT | 用户唤醒设备 | |
Intent.ACTION_NEW_OUTGOING_CALL | 拨打电话 |