
2.4 BroadCastReceiver
Android系统的四大组件还包括BroadCastReceiver,这种组件就是一种全局的监听器,用于监听系统全局的广播消息。由于BroadCastReceiver是一种全局的监听器,因此它可以非常方便地实现系统中不同组件之间的通信。例如,我们希望客户端程序与startService()方法启动的Service之间通信,就可以借助于BroadCastReceiver来实现。
2.4.1 BroadCastReceiver简介
BroadCastReceiver用于接收程序(包括用户开发的程序和系统内建的程序)所发出的BroadCast Intent,与应用程序启动Activity、Service相同的是,程序启动BroadCastReceiver也只需要两步。
1)创建需要启动的BroadCastReceiver的Intent。
2)调用Context的sentBroadCast()或sendOrderedBroadCast()方法开启动指定的BroadCastReceiver。
使用BroadCastReceiver时需要注意以下几点:
● 当应用程序发出一个BroadCast Intent之后,所有匹配该Intent的BroadCastReceiver都有可能被启动。
● 与Activity、Service具有完整的生命周期不同,BroadCastReceiver本质上只是一个系统级的监听器——专门负责监听各程序所发出的BroadCast。实现BroadCastReceiver的方法十分简单,只要重写BroadCastReceiver的onReceive(Contextcontext, Intentintent)方法即可。
● 一旦实现了BroadCastReceiver,接下来就应该指定该BroadCastReceiver能匹配的Intent。
2.4.2 BroadCastReceiver生命周期
BroadcastReceiver的生命周期从对象调用它开始,到onReceiver方法执行完成后结束。每次广播被接收后会重新创建BroadcastReceiver对象,并在onReceiver方法中执行完就销毁,如果BroadcastReceiver的onReceiver方法中不能在10秒内执行完成,Android会出现ANR异常。所以不要在BroadcastReceiver的onReceiver方法中执行耗时的操作。
1)BroadCastReceiver的生命周期很短暂,当接收到广播的时候创建,当onReceive()方法结束后销毁。
2)正因为BroadCastReceiver的声明周期很短暂,所以不要在广播接收器中去创建子线程做耗时的操作,因为广播接收者被销毁后,这个子进程就会成为空进程,很容易被杀死。
3)因为BroadCastReceiver是运行在主线程的,所以不能直接在BroadCastReceiver中去做耗时的操作,否则就会出现ANR异常。
建议:耗时较长的工作最好放到Service中去完成。
2.4.3 BroadCastReceiver的类型
1.普通广播(Normal Broadcast)
普通广播对于多个接收者来说是完全异步的,通常每个接收者都无须等待即可以接收到广播,接收者相互之间不会有影响。对于这种广播,接收者无法终止广播,即无法阻止其他接收者的接收动作。
发送广播通过context.sendBroadcast()方法,消息发送效率较高,但无法保证接收消息的先后顺序。
2.有序广播(Ordered Broadcast)
有序广播比较特殊,它每次只发送到优先级较高的接收者那里,然后由优先级高的接收者再传播到优先级低的接收者那里,优先级高的接收者有能力终止这个广播。
发送广播通过context.sendOrderedBroadcast()方法,可以保证接收消息的先后顺序按照消息优先级高低来进行发送。
3.本地广播(Local Broadcast)
前两种广播都是全局广播,所有应用都可以接收到广播消息,这样存在一定的安全隐患,而本地广播只在进程内传播,可以有效地保证数据的安全。
发送广播可以通过LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播的接收器的方法,主要代码如下:


4.系统广播(Local Broadcast)
Android内置了很多系统广播,当使用广播时,只需要在注册广播接收者时定义相关的Action即可,并不需要收到发送广播,当系统有相关操作(如开机、电池电量不足、拍照、网络变化等)时就会自动开启广播。
例如消息推送服务,需要实现开机启动的功能。要实现这个功能,就可以订阅系统“启动完成”这条广播,接收到这条广播后就可以启动自己的服务了。主要配置如下:

同时从安全角度考虑,系统要求必须声明接收开机启动广播的权限,于是再声明使用下面的权限,代码如下:

2.4.4 BroadCastReceiver实现机制
广播接收器注册的方式分为两种:静态注册、动态注册。
1.静态注册
在AndroidManifest.xml里通过<receive>标签声明。
它的优点:不受其他组件生命周期影响,即使应用程序被关闭,也能接收广播;缺点:耗电,占内存;适用场景:需要时刻监听的广播。静态注册代码如下:


注册示例:

当此App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。
2.动态注册
在代码中通过调用Context的registerReceiver()方法进行动态注册BroadcastReceiver。它的优点是:灵活,不耗电,易控,省内存;缺点:需要手动注销;适用场景:需要特定时候监听的广播。具体代码如下:

对于动态广播来说,有注册必然就有注销,需要成对出现。重复注册注销或者注册忘了注销都不行,后者会报Are you missing a call to unregisterReceiver()?错误,虽然不至于让应用崩溃,但是会导致内存泄露。
BroadCastReceiver注册好后,必须在接收到广播后才会被调用,因此,首先要发送广播。在Android中提供了两种发送广播的方式。
1)sendBroadCast():发送Normal Broadcast。
2)sendOrderedBroadCast():发送Ordered Broadcast。
普通广播(Normal Broadcast):Normal Broadcast是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,消息传递的效率比较高。但缺点是接收者不能将结果传递给下一个接收者,并且无法终止Broadcast Intent的广播。

【例2-9】 布局文件

对于设置条件,我们需要在AndroidManifest.xml中相应的目标下配置相同的条件,具体会在代码中说明。接下来附上主要代码。
新建项目,在MainActivity中添加下面代码,下面代码的主要功能是在主Activity中实现发送广播部分,主要代码如下:

新建一个类Receiver,用于接收发送出来的消息:

最后还有最重要的一步,在AndroidManifest.xml配置Receiver类的广播接收意图:

至此,对于普通广播的实现就完成了,运行后,在输出入日志中可以看到如下信息:

有序广播(Ordered BroadCast):该广播的接收者将按预先声明的优先级(-1000~1000)依次接收广播。有序广播接收者可以终止广播的传播(通过调用abortBroadCast()方法),广播的传播一旦终止,后面的接收者就无法接收到广播。另外,广播接收者可以加入自己的数据传递给下一个接收者(通过setResultExtras(Bundle bundle)方法)。
优先级的设置:通常是在AndroidManifest.xml中注册广播地址时,通过android:priority属性设置广播接收的优先级。该属性值的范围是-1000~1000,数值越大,优先级越高。例如:

发送有序广播:

发送有序广播的第二个参数是一个权限参数,如果为null则表示不要求BroadcastReceiver声明指定的权限。如果不为null,则表示接收者若想要接收此广播,需要声明指定的权限(为了安全)。
以上发送有序广播的代码需要在AndroidManifest.xml中自定义一个权限:

然后声明使用了此权限:

终止广播需要在优先级高的BroadcastReceiver的onReceiver()方法中添加代码:

则广播将不会再继续往下传播,即在低优先级的BroadcastReceiver中将不会再接收到广播消息。
【例2-10】 建立MainActivity

代码中指定了Intent的Action属性,再调用sendOrderedBroadcast()方法来发送有序广播。对于有序广播,它会按优先级依次触发每个BroadcastReceiver的onReceiver()方法。
第一个BroadcastReceiver代码如下:

MyReceiver不仅处理了它所接收的消息,而且向处理结果中存入了key为first的消息,这个消息将可以被第二个BroadcastReceiver解析出来。
abortBroadcast()用于取消广播,如果这条代码生效,那么优先级比MyReceiver低的BroadcastReceiver都将不会被触发。
在AndroidManifest.xml中部署该BroadcastReceiver,并指定其优先级为20,代码如下:


接下来提供第二个BroadcastReceiver,将会解析前一个BroadcastReceiver存入的key为first的消息,代码如下:

解析出前一个BroadcastReceiver存入结果中的key为first的消息。
在AndroidManifest.xml中配置MyReceiver2的优先级为0,代码如下:

先注释掉abortBroadcast(),点击发送有序广播按钮,可以看到先显示第一个广播接收器中的内容,再显示第二个广播接收器中的内容。
运行程序,其结果如图2-13~图2-15所示。

图2-13 运行首页

图2-14 消息存入

图2-15 消息接收
根据上面的配置可以看出,该程序中包含两个Broadcast Receiver,其中MyReceiver的优先级更高,MyReceiver2的优先级略低。