BroadcastReceiver
零、资料
一、简介
Android 四大组件中的全局的监听器,BroadcastReceiver (广播接收器)。常用作监听/接收App发出的广播消息,并做出响应。常用如网络变化,强制下线等。
二、原理
1.设计模式
广播中使用观察者模式(消息的订阅、发布)。
2.组成
- 订阅者:广播接收器
- 发布者:广播发布者
- 消息中心:
AMS
(Activity Manager Service
) 系统自行管理。
3.流程
![](https://img.haomeiwen.com/i3087349/1e1d11b37e009b69.png)
4.说明
- 接收器通过
Binder
机制在AMS
注册。 - 发送者通过
Binder
机制向AMS
发送广播。 -
AMS
根据发送者要求,在已注册列表中,寻找符合IntentFilter/Permission
的接收器。 -
AMS
将发送到符合的接收器相应的消息循环队列中。 - 接收器通过消息循环拿到此广播,并回调
onReceive()
。
注意:广播发送者和广播接收器的执行是异步的,既广播发送者不会关心接收器是否收到。
三、生命周期
1.动态注册
- 当前
活动
注册时开启 - 当前
活动
解除时销毁
2.静态注册
-
app
周期 -
onReceive
执行后系统任意时间段销毁
四、使用
1.流程
![](https://img.haomeiwen.com/i3087349/557856b9aad2ba2c.png)
2.接收器定义
- 继承
BroadcastReceiver
并重写onReceive()
。 - 注意
- 接收器接收到广播后会自动调用
onReceive()
。 -
onReceive()
一般会涉及与其他组件交互,如发送Notification
、启动Service
等。 - 接收器默认为
UI
线程,所以onReceive()
方法不能执行耗时操作,易将导致ANR
。
- 接收器接收到广播后会自动调用
3.接收器注册
1.静态注册
-
注册方式:
AndroidManifest.xml
里<receive>
标签声明 -
属性说明:
// 是否能被系统实例化,能为true,否则为false。默认为true。 android:enabled=["true" | "false"] // 能否接收其他App的发出的广播,默认值是由`receiver`中有无`intent-filter`决定的。 // 如果有`intent-filter`,否则为false。默认为true。 android:exported=["true" | "false"] // 继承BroadcastReceiver子类的类名。 android:name=".mBroadcastReceiver" // 具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收。 android:permission="string" // BroadcastReceiver运行所处的进程,默认为app的进程,可以指定独立的进程。 // 注:Android四大基本组件都可以通过此属性指定自己的独立进程。 android:process="string" // 用于指定此广播接收器将接收的广播类型,下面用于接收网络状态改变时发出的广播。 <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter>
-
优缺点:当此
App
首次启动时,系统会自动实例化静态注册的广播接收器,并注册到系统中。耗电、占内存。
2.动态注册
-
注册方式:代码中调用
Context.registerReceiver()
-
具体代码:
// 在onResume()中注册 // 当此Activity实例化时,会动态注册到系统中。 @Override protected void onResume(){ super.onResume(); // 1.实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); // 2.设置接收广播的类型 intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE); // 3.动态注册:调用Context的registerReceiver()方法 registerReceiver(mBroadcastReceiver, intentFilter); } // 在onPause()中销毁 // unregisterReceiver(BroadcastReceiver) @Override protected void onPause() { super.onPause(); // 4.销毁在onResume()方法中的广播 unregisterReceiver(mBroadcastReceiver); }
-
注意:
- 动态广播最好在
活动
的onResume()
注册、onPause()
注销。 - 重复注册、重复注销也是不被允许的。
-
App
死亡前一定会执行onPause()
,所以在此注销防止内存泄露。 - 不在
onCreate()
&onDestory()
或onStart()
&onStop()
注册、注销是因为
当系统因为内存不足要回收活动
占用的资源时,活动
在执行完onPause()
后就会被销毁,此时onStop()
、onDestory()
就不会执行。当再回到此活动时,是从onCreate()
开始执行。
- 动态广播最好在
4.发送广播
sendBroadcast(intent)
-
显式广播(Explicit Broadcast):一般在知道目标组件名称的前提下去发送指定Intent调用。指定了要激活的组件,一般是在相同的应用内实现。
-
隐式广播(Implicit Broadcast):一般在未明确指出组件名称的前提下通过Intent Filter来实现。系统会根据 Intent Filter中的动作(action)、类别(category)、数据(URI和数据类型)找到最合适的组件。一般是用于在不同应用程序之间。
五、类型
1.标准广播 Normal Broadcast
一种完全异步执行的广播,广播发出之后,所有的广播接收器几乎都会在同一时刻收到,无先后顺序。效率会比较高,但无法被截断。
-
示意图
示意图
-
发送
Intent intent = new Intent(); // 对应BroadcastReceiver中intentFilter的action intent.setAction(BROADCAST_ACTION); // 发送广播 sendBroadcast(intent);
-
接收
<receiver // 此广播接收者类是xxxBroadcastReceiver android:name=".xxxBroadcastReceiver" > // 用于接收网络状态改变时发出的广播 <intent-filter> <action android:name="BROADCAST_ACTION" /> </intent-filter> </receiver>
2.系统广播 System Broadcast
Android 系统内置广播,无需发送广播,仅接收就可以。
- 对应的action
系统操作 | action |
---|---|
监听网络变化 | android.net.conn.CONNECTIVITY_CHANGE |
关闭或打开飞行模式 | 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 |
插入耳机时 | Intent.ACTION_HEADSET_PLUG |
未正确移除SD卡但已取出来时(正确移除方法:设置--SD卡和设备内存--卸载SD卡) | Intent.ACTION_MEDIA_BAD_REMOVAL |
插入外部储存装置(如SD卡) | Intent.ACTION_MEDIA_CHECKING |
成功安装APK | Intent.ACTION_PACKAGE_ADDED |
成功删除APK | Intent.ACTION_PACKAGE_REMOVED |
重启设备 | Intent.ACTION_REBOOT |
屏幕被关闭 | Intent.ACTION_SCREEN_OFF |
屏幕被打开 | Intent.ACTION_SCREEN_ON |
关闭系统时 | Intent.ACTION_SHUTDOWN |
重启设备 | Intent.ACTION_REBOOT |
3.有序广播 Ordered Broadcast
一种同步执行的广播,同一时刻只会有一个接收器收到广播,执行完毕后向下传递加工后的广播(或不传递,后面接收器收不到广播)。有先后顺序,可截断。
-
示意图
示意图
-
接收顺序
按照Priority
属性值从大-小排序,值相同时动态注册的广播优先。 -
特点
- 接收广播按顺序接收。
- 先接收的广播接收器可以对广播进行截断,即后接收的广播接收器不再接收到此广播。
- 先接收的广播接收器可以对广播进行修改,那么后接收的广播接收器将接收到被修改后的广播。
4.本地广播 Local Broadcast
方法一
-
android:exported="false"
,仅接收当前应用的广播。 - 在广播发送和接收时,增设相应权限
permission
,用于权限验证。 - 发送广播时指定该广播接收器所在的包名
intent.setPackage(packageName)
,此广播将只会发送到此包中的App
内与之相匹配的有效广播接收器中。
方法二
使用LocalBroadcastManager
,只支持动态注册。
// 注册应用内广播接收器
// 1.实例化 IntentFilter & BroadcastReceiver
xxBroadcastReceiver = new xxBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
// 2.实例化 LocalBroadcastManager 的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
// 3.设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
// 4.调用 LocalBroadcastManager 单一实例的 registerReceiver() 进行动态注册
localBroadcastManager.registerReceiver(xxBroadcastReceiver, intentFilter);
// 5.取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(xxBroadcastReceiver);
// 6.发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);
注意
对于不同注册方式的广播接收器的OnReceive(Context context, Intent intent)
中的context
返回值是不一样的。
- 静态注册 (全局广播 + 应用内广播) :
context
是ReceiverRestrictedContext
。 - 动态注册 (全局广播) :
context
是Activity Context
。 - 动态注册 (应用内广播 & LocalBroadcastManager) :
context
是Application Context
。 - 动态注册 (应用内广播 & 非LocalBroadcastManager) :
context
是Activity Context
。
六、Android O(8.0)
Android8.0后优化了电量,当App targetSDK >= 26,几乎禁止了所有的隐式广播的静态注册监听。
1.赦免清单
// Android 8.0 上不限制的隐式广播
/**
开机广播
Intent.ACTION_LOCKED_BOOT_COMPLETED
Intent.ACTION_BOOT_COMPLETED
*/
"保留原因:这些广播只在首次启动时发送一次,并且许多应用都需要接收此广播以便进行作业、闹铃等事项的安排。"
/**
增删用户
Intent.ACTION_USER_INITIALIZE
"android.intent.action.USER_ADDED"
"android.intent.action.USER_REMOVED"
*/
"保留原因:这些广播只有拥有特定系统权限的app才能监听,因此大多数正常应用都无法接收它们。"
/**
时区、ALARM变化
"android.intent.action.TIME_SET"
Intent.ACTION_TIMEZONE_CHANGED
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED
*/
"保留原因:时钟应用可能需要接收这些广播,以便在时间或时区变化时更新闹铃"
/**
语言区域变化
Intent.ACTION_LOCALE_CHANGED
*/
"保留原因:只在语言区域发生变化时发送,并不频繁。 应用可能需要在语言区域发生变化时更新其数据。"
/**
Usb相关
UsbManager.ACTION_USB_ACCESSORY_ATTACHED
UsbManager.ACTION_USB_ACCESSORY_DETACHED
UsbManager.ACTION_USB_DEVICE_ATTACHED
UsbManager.ACTION_USB_DEVICE_DETACHED
*/
"保留原因:如果应用需要了解这些 USB 相关事件的信息,目前尚未找到能够替代注册广播的可行方案"
/**
蓝牙状态相关
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED
BluetoothDevice.ACTION_ACL_CONNECTED
BluetoothDevice.ACTION_ACL_DISCONNECTED
*/
"保留原因:应用接收这些蓝牙事件的广播时不太可能会影响用户体验"
/**
Telephony相关
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
TelephonyIntents.ACTION_*_SUBSCRIPTION_CHANGED
TelephonyIntents.SECRET_CODE_ACTION
TelephonyManager.ACTION_PHONE_STATE_CHANGED
TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED
TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED
*/
"保留原因:设备制造商 (OEM) 电话应用可能需要接收这些广播"
/**
账号相关
AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION
*/
"保留原因:一些应用需要了解登录帐号的变化,以便为新帐号和变化的帐号设置计划操作"
/**
应用数据清除
Intent.ACTION_PACKAGE_DATA_CLEARED
*/
"保留原因:只在用户显式地从 Settings 清除其数据时发送,因此广播接收器不太可能严重影响用户体验"
/**
软件包被移除
Intent.ACTION_PACKAGE_FULLY_REMOVED
*/
"保留原因:一些应用可能需要在另一软件包被移除时更新其存储的数据;对于这些应用,尚未找到能够替代注册此广播的可行方案"
/**
外拨电话
Intent.ACTION_NEW_OUTGOING_CALL
*/
"保留原因:执行操作来响应用户打电话行为的应用需要接收此广播"
/**
当设备所有者被设置、改变或清除时发出
DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED
*/
"保留原因:此广播发送得不是很频繁;一些应用需要接收它,以便知晓设备的安全状态发生了变化"
/**
日历相关
CalendarContract.ACTION_EVENT_REMINDER
*/
"保留原因:由日历provider发送,用于向日历应用发布事件提醒。因为日历provider不清楚日历应用是什么,所以此广播必须是隐式广播。"
/**
安装或移除存储相关广播
Intent.ACTION_MEDIA_MOUNTED
Intent.ACTION_MEDIA_CHECKING
Intent.ACTION_MEDIA_EJECT
Intent.ACTION_MEDIA_UNMOUNTED
Intent.ACTION_MEDIA_UNMOUNTABLE
Intent.ACTION_MEDIA_REMOVED
Intent.ACTION_MEDIA_BAD_REMOVAL
*/
"保留原因:这些广播是作为用户与设备进行物理交互的结果:安装或移除存储卷或当启动初始化时(当可用卷被装载)的一部分发送的,因此它们不是很常见,并且通常是在用户的掌控下"
/**
短信、WAP PUSH相关
Telephony.Sms.Intents.SMS_RECEIVED_ACTION
Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION
注意:需要申请以下权限才可以接收
"android.permission.RECEIVE_SMS"
"android.permission.RECEIVE_WAP_PUSH"
*/
"保留原因:SMS短信应用需要接收这些广播"
2.官方推荐
官方对于隐式广播推荐2种方法
- 动态通过调用
Context.registerReceiver()
注册广播接收器而不是在清单中声明接收器。 - 使用JobScheduler
3.Android O 隐式广播修改
对于隐式广播可以修改为显式广播
-
平常隐式发送广播
// 隐式广播:Android8.0 targetSDK >= 26 受到限制 Intent intent = new Intent(); // 对应 BroadcastReceiver 中 intentFilter 的 action intent.setAction("ANDROID_O"); // 发送广播 sendBroadcast(intent);
-
修改为显示发送
-
发送本地
如果广播只是 App 自己发自己收,只需改为显式广播。Intent intent = new Intent(); // 指定Action intent.setAction("ANDROID_O"); // 指定包名 intent.setPackage(getPackageName()); sendBroadcast(intent);
-
发送所有APP
当想发给其他 App,而不知其包名时可通过pm把所有隐式注册了这个自定义广播的 App列出来,然后转成显式发送。Intent intent = new Intent(); // 指定Action intent.setAction("ANDROID_O"); PackageManager pm = getPackageManager(); List<ResolveInfo> matches = pm.queryBroadcastReceivers(intent, 0); for (ResolveInfo resolveInfo : matches) { intent.setPackage(resolveInfo.activityInfo.applicationInfo.packageName); sendBroadcast(intent); }
-
项目Demo
Demo地址 中的 Broadcast[module]
2019-11-12