Android BroadcastReceiver
Posted Eli Shaw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android BroadcastReceiver相关的知识,希望对你有一定的参考价值。
一、BroadCastReceiver 简介广播接收者(BroadcastReceiver)用于接收广播 Intent,广播 Intent 的发送是通过调用 Context.sendBroadcast()、Context.sendOrderedBroadcast() 来实现的。通常一个广播 Intent 可以被订阅了此 Intent 的多个广播接收者所接收。
广播是一种广泛运用的在应用程序之间传输信息的机制。而 BroadcastReceiver 是对发送出来的广播进行过滤接收并响应的一类组件;
来自普通应用程序,如一个应用程序通知其他应用程序某些数据已经下载完毕。
BroadcastReceiver 自身并不实现图形用户界面,但是当它收到某个通知后,BroadcastReceiver 可以启动 Activity 作为响应,或者通过 NotificationMananger 提醒用户,或者启动 Service 等等。
二、BroadCastReceiver 实现
一、注册
BroadcastReceiver 有两种注册方法,一种是静态注册(即在 androidManifest.xml 中注册),另一种是动态注册(即使用代码注册)
1.静态注册
⑴.静态注册需要自定义一个 BroadcastReceiver 类(继承 BroadcastReceiver 就好),如下:
public class StaticReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
⑵.然后在 AndroidManifest.xml 中注册。
<!-- 注册自定义静态广播接收器 -->
<receiver android:name="com.xjl.brd.receiver.StaticReceiver">
<intent-filter>
<action android:name="com.byread.static" />
</intent-filter>
</receiver>
receiver的属性:
<receiver android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</receiver>
android:exported —— 此 broadcastReceiver 能否接收其他 App 的发出的广播,这个属性默认值有点意思,其默认值是由 receiver 中有无 intent-filter 决定的,如果有 intent-filter,默认值为 true,否则为 false。(同样的,activity/service 中的此属性默认值一样遵循此规则)同时,需要注意的是,这个值的设定是以 application 或者application user id 为界的,而非进程为界(一个应用中可能含有多个进程);
android:name —— 此 broadcastReceiver 类名;
android:permission —— 如果设置,具有相应权限的广播发送方发送的广播才能被此 broadcastReceiver 所接收;
android:process —— broadcastReceiver 运行所处的进程。默认为 app 的进程。可以指定独立的进程(Android 四大基本组件都可以通过此属性指定自己的独立进程)
⑶.在需要调用广播的地方调用:
private static final String ACTION_STATIC = "com.byread.static"; // 静态注册的Action
Log.e(TAG, "发送自定义静态注册广播消息");
Intent intent = new Intent();
intent.setAction(ACTION_STATIC);
intent.putExtra("msg", "接收静态注册广播成功!");
sendBroadcast(intent);
2.动态注册(即使用代码注册)
⑴先实例一个 BroadcastReceiver。
private static final String ACTION_DYNAMIC = "com.byread.dynamic"; // 动态注册的Action
/**
* 动态注册的 Receiver
*/
private BroadcastReceiver mDynamicReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.e("MainActivity", "接收自定义动态注册广播消息");
if (intent.getAction().equals(ACTION_DYNAMIC)) {
String msg = intent.getStringExtra("msg");
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
};
⑵在 onStart() 方法中实现广播的注册
@Override
protected void onStart() {
super.onStart();
// 注册自定义动态广播消息
IntentFilter filter_dynamic = new IntentFilter();
filter_dynamic.addAction(ACTION_DYNAMIC);
registerReceiver(mDynamicReceiver, filter_dynamic);
}
⑶使用代码注册的广播应当在 onPause() 方法中注销。
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mDynamicReceiver);
unregisterReceiver(mSystemReceiver);
}
⑷.在需要使用广播的地方调用:
Log.e(TAG, "发送自定义动态注册广播消息");
Intent intent2 = new Intent();
intent2.setAction(ACTION_DYNAMIC);
intent2.putExtra("msg", "接收动态注册广播成功!");
sendBroadcast(intent2);
3.注册小结
⑴.动态注册的 BroadcastReceiver,使用比较方便,在代码中随时可以加入。但是要注意,必须适时的 unregister,否则会有内存泄漏。同时,其生命周期是有限的,在使用时要注意。
⑵.静态注册的 BroadcastReceiver,需要在 manifest 文件中标出,并继承 BroadcastReceiver 类。此方法生成的对象,只在调用 onReceive() 时有效,一旦返回,就无效。一定程度上可以认为,此 receiver 一直有效。
⑶.动态注册广播接收器还有一个特点,就是当用来注册的 Activity 关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕 app 本身未启动,该 app 订阅的广播在触发时也会对它起作用
4.系统广播
系统广播,由系统发出的广播,即 Android 手机自己发出的广播,如电量,网络,信息等状态的广播。系统广播与广播的注册无关,系统广播支持静态,动态注册。
三、生命周期
每次广播到来时,会重新创建 BroadcastReceiver 对象,并且调用 onReceive() 方法,执行完以后,该对象即被销毁 onReceive() 生命周期只有 10 秒左右,如果在 onReceive() 内做超过十秒的事情,就会报ANR(Application No Response) 程序无响应的错误信息,如果需要完成一项比较耗时的工作,应该通过发送 Intent 给 Service, 由Service 来完成。这里不能使用子线程来解决,因为 BroadcastReceiver 的生命周期很短,子线程可能还没有结束 BroadcastReceiver 就先结束了 .BroadcastReceiver 一旦结束,此时 BroadcastReceiver 的所在进程很容易在系统需要内存时被优先杀死,因为它属于空进程 ( 没有任何活动组件的进程 )。如果它的宿主进程被杀死,那么正在工作的子线程也会被杀死。所以采用子线程来解决是不可靠的
四、广播发送及广播类型
经常说“发送广播”和“接收”,表面上看广播作为 Android 广播机制中的实体,实际上这一实体本身是并不是以所谓的“广播”对象存在的,而是以“意图”(Intent)去表示。定义广播的定义过程,实际就是相应广播“意图”的定义过程,然后通过广播发送者将此“意图”发送出去。被相应的 BroadcastReceiver 接收后将会回调 onReceive() 函数。
下段代码片段显示的是一个普通广播的定义过程,并发送出去。其中 setAction(..) 对应于 BroadcastReceiver 中的intentFilter 中的 action。
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.putExtra("name", "qqyumidi");
sendBroadcast(intent);
根据广播的发送方式,可以将其分为以下几种类型:
1.Normal Broadcast:普通广播
2.System Broadcast: 系统广播
3.Ordered broadcast:有序广播
4.Sticky Broadcast:异步广播(粘性广播)(在 android 5.0/api 21中 deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated)
5.Local Broadcast:App 应用内广播
下面分别总结下各种类型的发送方式及其特点。
⑴.Normal Broadcast:普通广播
此处将普通广播界定为:开发者自己定义的 intent,以 context.sendBroadcast_"AsUser"(intent, ...) 形式。具体可以使用的方法有:
sendBroadcast(intent)
sendBroadcast(intent, receiverPermission)
sendBroadcastAsUser(intent, userHandler)
sendBroadcastAsUser(intent, userHandler,receiverPermission)。
普通广播会被注册了的相应的感兴趣(intent-filter 匹配)接收,且顺序是无序的。如果发送广播时有相应的权限要求,BroadCastReceiver 如果想要接收此广播,也需要有相应的权限。
⑵.System Broadcast: 系统广播
Android系统中内置了多个系统广播,只要涉及到手机的基本操作,基本上都会发出相应的系统广播。如:开启启动,网络状态改变,拍照,屏幕关闭与开启,点亮不足等等。每个系统广播都具有特定的 intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的 BroadcastReceiver 接收。系统广播在系统内部当特定事件发生时,有系统自动发出。别忘记添加相应的系统权限。
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 注册静态系统WIFI广播接收器 -->
<receiver android:name="com.xjl.brd.receiver.SystemReceiver" >
<intent-filter>
<action android:name="android.net.wifi.RSSI_CHANGED" />
<action android:name="android.net.wifi.STATE_CHANGE" />
<action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
</intent-filter>
</receiver>
⑶.Ordered broadcast:有序广播
一、优缺点
优点:
1,按优先级的不同,优先 Receiver 可对数据进行处理,并传给下一个 Receiver
2,通过 abortBroadcast 可终止广播的传播缺点:效率低
二、发送广播的方法:sendOrderedBroadcast()
三、优先接收到 Broadcast 的 Receiver 可通过 setResultExtras(Bundle) 方法将处理结果存入 Broadcast 中,下一个 Receiver 通过 Bundle bundle=getResultExtras(true)方法获取上一个 Receiver 传来的数据
四、priority 属性设置优先级,值越大越先收到广播。取值范围是:-1000 到 1000
有序广播的有序广播中的“有序”是针对广播接收者而言的,指的是发送出去的广播被 BroadcastReceiver 按照先后循序接收。有序广播的定义过程与普通广播无异,只是其的主要发送方式变为:sendOrderedBroadcast(intent, receiverPermission, ...)。
对于有序广播,其主要特点总结如下:
1.多个具当前已经注册且有效的 BroadcastReceiver 接收有序广播时,是按照先后顺序接收的,先后顺序判定标准遵循为:将当前系统中所有有效的动态注册和静态注册的 BroadcastReceiver 按照 priority 属性值从大到小排序,对于具有相同的 priority 的动态广播和静态广播,动态广播会排在前面。
2.先接收的 BroadcastReceiver 可以对此有序广播进行截断,使后面的 BroadcastReceiver 不再接收到此广播,也可以对广播进行修改,使后面的 BroadcastReceiver 接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。
注:有序广播也可以用 sendBroadcast 发送,不过不建议这样使用。
⑷.Sticky Broadcast:粘性广播(在 android 5.0/api 21 中 deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated)。
即使用 sendStickyBroadcast() 发送出的广播。
使用场景是,广播没有注册之前,先发出广播,然后再注册广播。普通广播没办法再接收,异步广播可以再接收。
需要注意的是,如果想要使用 sendStickyBroadcast() 广播,需要添加 连续广播 权限,即:
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
该权限用意为:允许一个程序收到广播后快速收到下一个广播
⑸.Local Broadcast:App应用内广播(此处的 App 应用以 App 应用进程为界)
由前文阐述可知,Android 中的广播可以跨进程甚至跨 App 直接通信,且注册是 exported 对于有 intent-filter 的情况下默认值是 true,由此将可能出现安全隐患如下:
1.其他 App 可能会针对性的发出与当前 App intent-filter 相匹配的广播,由此导致当前 App 不断接收到广播并处理;
2.其他 App 可以注册与当前 App 一致的 intent-filter 用于接收广播,获取广播具体信息。
无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是:
1.对于同一 App 内部发送和接收广播,将 exported 属性人为设置成 false,使得非本 App 内部发出的此广播不被接收;
2.在广播发送和接收时,都增加上相应的 permission,用于权限验证;
3.发送广播时,指定特定广播接收器所在的包名,具体是通过 intent.setPackage(packageName) 指定在,这样此广播将只会发送到此包中的 App 内与之相匹配的有效广播接收器中。
App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个 App。实际的业务需求中,App 应用内广播确实可能需要用到。同时,之所以使用应用内广播时,而不是使用全局广播的形式,更多的考虑到的是 Android广播机制中的安全性问题。
相比于全局广播,App 应用内广播优势体现在:
1.因广播数据在本应用范围内传播,你不用担心隐私数据泄露的问题。
2.不用担心别的应用伪造广播,造成安全隐患。
3.相比在系统内发送全局广播,它更高效。
为此,Android v4 兼容包中给出了封装好的 LocalBroadcastManager 类,用于统一处理 App 应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调 context 变成了LocalBroadcastManager 的单一实例。
代码片段如下:
⑴.在全局部位声明 LocalBroadcastManager 属性,并声明一个动态广播
private LocalBroadcastManager mLocalBroadcastManager; //应用内广播
/**
* 实例一个 LocalBroadcastManager 动态注册的广播
*/
private BroadcastReceiver mLocalReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "接收 LocalBroadcastManager 动态注册广播消息");
if (intent.getAction().equals(ACTION_LOCAL)) {
String tMsg = intent.getStringExtra("msg");
Toast.makeText(context, tMsg, Toast.LENGTH_SHORT).show();
}
}
};
⑵.在 onStart() 方法中实例与注册广播
protected void onStart() {
super.onStart();
//注册 LocalBroadcastManager 动态广播消息
IntentFilter tFilterLocal=new IntentFilter();
tFilterLocal.addAction(ACTION_LOCAL);
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
mLocalBroadcastManager.registerReceiver(mLocalReceiver, tFilterLocal);
}
⑶.在需要调用广播的地方使用 LocalBroadcastManager 发送广播
Log.e(TAG, "使用 LocalBroadcastManager 发送广播消息");
Intent tIntentLocal = new Intent();
tIntentLocal.setAction(ACTION_LOCAL);
tIntentLocal.putExtra("msg", "接收 LOCAL 动态注册广播成功!");
//这里使用 LocalBroadcastManager 发送广播,使用 sendBroadcast 无效
mLocalBroadcastManager.sendBroadcast(tIntentLocal);
⑷.在 onPause() 方法中注销广播
@Override
protected void onPause() {
super.onPause();
//注销广播
mLocalBroadcastManager.unregisterReceiver(mLocalReceiver);
}
五、不同注册方式的广播接收器回调onReceive(context, intent)中的context具体类型
1.对于静态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是ReceiverRestrictedContext;
2.对于全局广播的动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Activity Context;
3.对于通过LocalBroadcastManager动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Application Context。
注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册的ContextReceiver才有可能接收到(静态注册或其他方式动态注册的ContextReceiver是接收不到的)。
六、不同 Android API 版本中广播机制相关 API 重要变迁
1.Android5.0/API level 21开始粘滞广播和有序粘滞广播过期,以后不再建议使用;
2.”静态注册的广播接收器即使app已经退出,主要有相应的广播发出,依然可以接收到,但此种描述自Android 3.1开始有可能不再成立“
Android 3.1开始系统在Intent与广播相关的flag增加了参数,分别是FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES。
FLAG_INCLUDE_STOPPED_PACKAGES:包含已经停止的包(停止:即包所在的进程已经退出)
FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已经停止的包
主要原因如下:
自 Android3.1 开始,系统本身则增加了对所有 app 当前是否处于运行状态的跟踪。在发送广播时,不管是什么广播类型,系统默认直接增加了值为 FLAG_EXCLUDE_STOPPED_PACKAGES 的 flag,导致即使是静态注册的广播接收器,对于其所在进程已经退出的 app,同样无法接收到广播。
详情参加Android官方文档:http://developer.android.com/about/versions/android-3.1.html#launchcontrols
由此,对于系统广播,由于是系统内部直接发出,无法更改此intent flag值,因此,3.1开始对于静态注册的接收系统广播的BroadcastReceiver,如果App进程已经退出,将不能接收到广播。
但是对于自定义的广播,可以通过复写此flag为FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的BroadcastReceiver,即使所在App进程已经退出,也能能接收到广播,并会启动应用进程,但此时的BroadcastReceiver是重新新建的。
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.putExtra("name", "qqyumidi");
sendBroadcast(intent);
注1:对于动态注册类型的 BroadcastReceiver,由于此注册和取消注册实在其他组件(如 Activity)中进行,因此,不受此改变影响。
注2:在3.1以前,相信不少 app 可能通过静态注册方式监听各种系统广播,以此进行一些业务上的处理(如即时 app 已经退出,仍然能接收到,可以启动 service 等..),3.1 后,静态注册接受广播方式的改变,将直接导致此类方案不再可行。于是,通过将 Service 与 App 本身设置成不同的进程已经成为实现此类需求的可行替代方案。
以上内容多是来自网络,我只是起到再总结的作用,下面是我自己总结广播的一个Demo
http://download.csdn.net/detail/xiaojinlai123/9472598
以上是关于Android BroadcastReceiver的主要内容,如果未能解决你的问题,请参考以下文章
Android_组件_BroadcastReceiver基础
Android BroadcastReceiver广播:基本使用
Android -- BroadCastReceiver的简单使用