一、广播机制与BroadcastReceiver
广播作为Android组件间的通信方式,可以使用在如下场景:
- APP内部的消息通信。
- 不同APP之间的消息通信。
- Android系统在特定情况下与APP之间的消息通信。
广播使用了观察者模式,基于消息的发布/订阅事件模型。广播将广播的发送者和接受者极大程度上解耦,使得系统能够方便集成,更易扩展。
BroadcastReceiver本质是一个全局监听器,用于监听系统全局的广播消息,方便实现系统中不同组件间的通信。
自定义广播接收器需要继承基类BroadcastReceiver,并实现抽象方法onReceive(context,intent)
。默认情况下,广播接收器也是运行在主线程,因此onReceiver()中不能执行太耗时的操作(不超过10s),否则将会产生ANR问题。onReceiver()方法中涉及与其他组件之间的交互时,可以使用发送Notification、启动Service等方式,最好不要启动Activity。
二、BroadcastReceiver类型
1.无序广播
也叫标准广播,是一种完全异步执行的广播。在广播发出之后,所有广播接收器几乎都会在同一时刻接收到这条广播消息,它们之间没有任何先后顺序,广播的效率较高。
调用SendBroadcast()
方法发送广播。无序广播不可以被拦截,若被拦截则会报错。
无序广播与广播接收器之间不能互传数据 。
2.有序广播
是一种同步执行的广播,在广播发出之后,同一时刻只有一个广播接收器能够收到这条广播消息,当其逻辑执行完后该广播接收器才会继续传递。
调用SendOrderedBroadcast()
方法来发送广播,同时也可调用abortBroadcast()
方法拦截该广播。可通过<intent-filter>
标签中设置android:property
属性来设置优先级,未设置时按照注册的顺序接收广播。
有序广播接受器间可以互传数据。
当广播接收器收到广播后,当前广播也可以使用setResultData
方法提奶家数据传给下一个接收器。使用getStringExtra
函数获取广播的原始数据,通过getResultData
方法取得上个广播接收器自己添加的数据,并可用abortBroadcast
方法丢弃该广播,使该广播不再被别的接收器接收到。
3.粘性广播
调用SendStickyBroadcast()
方法发送广播,使用时需要权限。特定是Intent会一直保留到广播事件结束,没有10s限制。一般用来确保重要的状态改变后的信息被持久保存,并且能随广播给新的BroadcastReceiver,比如电源的改变。
三、注册方式
1.静态注册
在AndroidManifest.xml
文件中配置。
<receiver android:name=".MyReceiver" android:exported="true">
<intent-filter>
<!-- 指定该BroadcastReceiver所响应的Intent的Action -->
<action android:name="android.intent.action.INPUT_METHOD_CHANGED"
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
两个重要属性需要关注:
(1)android:exported
其作用是设置此BroadcastReceiver能否接受其他APP发出的广播 ,当设为false时,只能接受同一应用的的组件或具有相同user ID的应用发送的消息。这个属性的默认值是由BroadcastReceiver中有无Intent-filter
决定的,如果有Intent-filter
,默认值为true,否则为false。
(2)android:permission
如果设置此属性,具有相应权限的广播发送方发送的广播才能被此BroadcastReceiver所接受;如果没有设置,这个值赋予整个应用所申请的权限。
2.动态注册
调用Context的registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
方法指定。
四、本地广播
本地广播机制使得发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接受来自本应用程序发出的广播,则安全性得到了提高。本地广播主要是使用了一个LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播接收器的方法。
开发者只要实现自己的BroadcastReceiver子类,并重写onReceive(Context context, Intetn intent)
方法即可。
当其他组件通过sendBroadcast()、sendStickyBroadcast()、sendOrderBroadcast()
方法发送广播消息时,如果该BroadcastReceiver也对该消息“感兴趣”,BroadcastReceiver的onReceive(Context context, Intetn intent)
方法将会被触发。
使用步骤:
1. 调用LocalBroadcastManager.getInstance()获得实例
2. 调用registerReceiver()方法注册广播
3. 调用sendBroadcast()方法发送广播
4. 调用unregisterReceiver()方法取消注册
注意事项:
1.本地广播无法通过静态注册方式来接受,相比起系统全局广播更加高效。
2.在广播中启动Activity时,需要为Intent加入FLAG_ACTIVITY_NEW_TASK标记,否则会报错,因为需要一个栈来存放新打开的Activity。
3.广播中弹出Alertdialog时,需要设置对话框的类型为TYPE_SYSTEM_ALERT,否则无法弹出。
4.不要在onReceiver()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceiver()方法运行了较长时间而没有结束时,程序就会报错。
五、系统广播
Android系统内置了多个系统广播,只要涉及手机的基本操作,基本上都会发出相应的系统广播,如开机启动、网络状态改变、拍照、屏幕关闭与开启、电量不足等。在系统内部当特定时间发生时,系统广播由系统自动发出。
常见系统广播的Intent的Action为如下值:
- 短信提醒:android.provider.Telephony.SMS_RECEIVED
- 电量过低:ACTION_BATIERY_LOW
- 电量发生改变:ACTION_BATTERY_CHANGED
- 连接电源:ACTION_POWER_CONNECTED
- 系统被关闭:ACTION_SHUTDOWN
- 系统启动完成:ACTION_BOOT_COMPLETED
从Android 7.0开始,系统不会再发送广播ACTION_NEW_PICTURE
和ACTION_NEW_VIDEO
,对于广播CONNECTIVITY_ACTION
必须在代码中使用registerReceiver方法注册接收器,在AndroidManifest文件中声明接收器不起作用。
从Android 8.0开始,对于大多数隐式广播,不能在AndroidManifest文件中声明接收器。
六、局部广播
局部广播的发送者和接受者都同属于一个APP,相比于全局广播具有以下优点:
- 其他的APP不会受到局部广播,不用担心数据泄露的问题。
- 其他APP不可能向当前的APP发送局部广播,不用担心有安全漏洞被其他APP利用。
- 局部广播比通过系统传递的全局广播的传递效率更高。
Android v4包中提供了LocalBroadcastManager
类,用于统一处理APP局部广播,使用方式与全局广播几乎相同,只是调用注册/取消注册广播接收器和发送广播偶读方法时,需要通过LocalBroadcastManager
类的getInstance()
方法获取的实例调用。
七、广播的安全性
Android系统中的广播可以跨进程直接通信,会产生以下两个问题:
- 其他APP可以接收到当前APP发送的广播,导致数据外泄。
- 其他APP可以向当前APP放广播消息,导致APP被非法控制。
(1)发送广播
- 发送广播时,增加相应的permission,用于权限验证。
- 在Android 4.0及以上系统中发送广播时,可以使用setPackage()方法设置接受广播的包名。
- 使用局部广播。
(2)接受广播
- 注册广播接收器时,增加相应的permission,用于权限验证。
- 注册广播接收器时,设置android:exported的值为false。
- 使用局部广播。
发送广播时,如果增加了permission,那接受广播的APP必须申请相应权限,这样才能收到对应的广播,反之亦然。