《Android 核心组件深度系列 · 第 3 篇 BroadcastReceiver》
如果说 Activity 是「眼睛」,Service 是「心脏」, 那 BroadcastReceiver(广播接收器)就是 Android 系统的「神经系统」。
它负责感知系统或应用间的事件变化: 网络变化、电量变化、应用安装、音乐播放完毕......都离不开它。
今天这篇,我们彻底讲懂------BroadcastReceiver 到底是什么、怎么工作、以及怎么优雅地用。
一、什么是 BroadcastReceiver?
一句话定义:
BroadcastReceiver 是 Android 中的消息接收机制,用于在应用或系统之间传递广播事件。
它可以理解为一个订阅-发布(Pub/Sub)模型:
- 发布者(Sender) :发送广播事件
- 接收者(Receiver) :监听并响应事件
举个例子
当用户连接 WiFi 时:
- 系统发送广播:
android.net.conn.CONNECTIVITY_CHANGE
- 你的 App 收到 → 可以弹提示 "网络已连接"
二、广播的分类
类型 | 说明 | 是否跨进程 |
---|---|---|
系统广播 | 由系统发送(如电量、网络) | 是 |
应用广播 | App 内部自定义 | 否(默认) |
有序广播(Ordered) | 按优先级逐个接收,可中断 | 是 |
无序广播(Normal) | 同时接收,互不影响 | 是 |
本地广播(Local) | 仅在当前 App 内发送 | 否 |
三、动态注册 vs 静态注册
3.1 动态注册(代码中注册)
动态注册需要在代码中手动注册和注销,适合需要灵活控制生命周期的场景。
kotlin
class MainActivity : AppCompatActivity() {
private val networkReceiver = NetworkReceiver()
override fun onStart() {
super.onStart()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
// Android 14+ 需要指定 flag
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
registerReceiver(networkReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(networkReceiver, filter)
}
}
override fun onStop() {
super.onStop()
unregisterReceiver(networkReceiver)
}
}
NetworkReceiver 实现:
kotlin
class NetworkReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = cm.activeNetwork
if (network == null) {
Log.d("NetworkReceiver", "网络已断开")
return
}
val capabilities = cm.getNetworkCapabilities(network)
if (capabilities == null) {
Log.d("NetworkReceiver", "无法获取网络能力")
return
}
when {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ->
Log.d("NetworkReceiver", "WiFi 已连接")
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ->
Log.d("NetworkReceiver", "移动网络已连接")
else ->
Log.d("NetworkReceiver", "其他网络类型")
}
} else {
// Android 6.0 以下的旧方法
@Suppress("DEPRECATION")
val networkInfo = cm.activeNetworkInfo
if (networkInfo?.isConnected == true) {
Log.d("NetworkReceiver", "网络已连接:${networkInfo.typeName}")
} else {
Log.d("NetworkReceiver", "网络已断开")
}
}
}
}
特点:
- App 运行时才能接收
- 适合 UI 相关场景
- 必须在对应的生命周期方法中注销,否则会内存泄漏
3.2 静态注册(写在 Manifest)
静态注册可以让 App 在未启动时也能接收广播,但 Android 8.0 以后对此做了严格限制。
xml
<manifest>
<!-- 需要的权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application>
<!-- Android 12+ 必须显式声明 exported -->
<receiver
android:name=".BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>
BootReceiver 实现:
kotlin
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
Log.d("BootReceiver", "设备启动完成")
// 可以启动你的后台服务
// context.startForegroundService(Intent(context, YourService::class.java))
}
}
}
特点:
- App 未启动也能接收(Android 8.0 以前)
- Android 8.0+ 对大多数隐式广播做了限制
- 仍可接收的系统广播包括:
BOOT_COMPLETED
、LOCALE_CHANGED
、TIMEZONE_CHANGED
等
3.3 Android 版本适配重点
Android 8.0(API 26):
- 禁止在 Manifest 中静态注册大部分隐式广播
- 解决方案:改用动态注册
Android 12(API 31):
- 必须显式声明
android:exported
属性 - 否则会导致 App 安装失败
Android 13(API 33):
- 如果在 Receiver 中发送通知,需要申请
POST_NOTIFICATIONS
权限
kotlin
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
// 在 Activity 中请求权限
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
REQUEST_CODE_NOTIFICATION
)
}
}
Android 14(API 34):
- 动态注册时必须指定
RECEIVER_EXPORTED
或RECEIVER_NOT_EXPORTED
flag
四、自定义广播实战
4.1 普通广播(无序广播)
定义接收器:
kotlin
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val msg = intent.getStringExtra("msg")
Log.d("MyReceiver", "收到广播:$msg")
Toast.makeText(context, "收到广播:$msg", Toast.LENGTH_SHORT).show()
}
}
Manifest 注册:
ini
<receiver
android:name=".MyReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.tony.broadcast.TEST" />
</intent-filter>
</receiver>
发送广播:
kotlin
val intent = Intent("com.tony.broadcast.TEST")
intent.putExtra("msg", "Hello Broadcast!")
sendBroadcast(intent)
4.2 有序广播(Ordered Broadcast)
有序广播允许多个接收器按优先级顺序接收,并且:
- 高优先级可以修改数据后传递给下一个
- 高优先级可以中断传递(调用
abortBroadcast()
)
定义两个接收器:
kotlin
class ReceiverA : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("ReceiverA", "我是高优先级,先收到")
// 修改数据并传递
setResultData("ReceiverA 处理过的数据")
// 如果取消注释下面这行,ReceiverB 将收不到广播
// abortBroadcast()
}
}
class ReceiverB : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val data = getResultData()
Log.d("ReceiverB", "我是低优先级,收到数据:$data")
}
}
Manifest 配置(注意 priority):
ini
<receiver android:name=".ReceiverA" android:exported="false">
<intent-filter android:priority="100">
<action android:name="com.tony.broadcast.ORDERED" />
</intent-filter>
</receiver>
<receiver android:name=".ReceiverB" android:exported="false">
<intent-filter android:priority="50">
<action android:name="com.tony.broadcast.ORDERED" />
</intent-filter>
</receiver>
发送有序广播:
kotlin
val intent = Intent("com.tony.broadcast.ORDERED")
sendOrderedBroadcast(
intent,
null, // receiverPermission
null, // resultReceiver(最终接收器)
null, // scheduler
Activity.RESULT_OK, // initialCode
"初始数据", // initialData
null // initialExtras
)
执行顺序:
- ReceiverA 先收到(priority=100)
- ReceiverA 可以修改数据或中断
- ReceiverB 后收到(priority=50)
4.3 本地广播替代方案
LocalBroadcastManager
已在 API 33(Android 13)废弃,官方推荐使用以下方案:
方案一:使用 LiveData
kotlin
// 定义事件总线
object EventBus {
private val _event = MutableLiveData<String>()
val event: LiveData<String> = _event
fun post(msg: String) {
_event.postValue(msg)
}
}
// 发送事件
EventBus.post("Hello LiveData!")
// 接收事件
EventBus.event.observe(this) { msg ->
Log.d("EventBus", "收到:$msg")
}
方案二:使用 SharedFlow(推荐)
kotlin
// 定义事件总线
object EventBus {
private val _events = MutableSharedFlow<String>()
val events = _events.asSharedFlow()
suspend fun emit(event: String) {
_events.emit(event)
}
}
// 发送事件
lifecycleScope.launch {
EventBus.emit("Hello SharedFlow!")
}
// 接收事件
lifecycleScope.launch {
EventBus.events.collect { msg ->
Log.d("EventBus", "收到:$msg")
}
}
对比:
diff
BroadcastReceiver:
- 优点:系统级支持,可跨进程
- 缺点:性能开销大,需要序列化
SharedFlow:
- 优点:轻量级,性能好,支持背压
- 缺点:仅限同一进程
五、安全性最佳实践
5.1 防止广播劫持
恶意 App 可能伪装成你的广播发送者,注入恶意数据。
解决方案:使用自定义权限
定义权限:
xml
<manifest>
<!-- 定义一个签名级权限 -->
<permission
android:name="com.tony.CUSTOM_PERMISSION"
android:protectionLevel="signature" />
<!-- 声明使用该权限 -->
<uses-permission android:name="com.tony.CUSTOM_PERMISSION" />
</manifest>
限制接收器:
ini
<receiver
android:name=".SecureReceiver"
android:permission="com.tony.CUSTOM_PERMISSION"
android:exported="true">
<intent-filter>
<action android:name="com.tony.SECURE_ACTION" />
</intent-filter>
</receiver>
限制发送者:
less
// 发送时指定权限
sendBroadcast(
Intent("com.tony.SECURE_ACTION"),
"com.tony.CUSTOM_PERMISSION"
)
这样,只有具有相同签名的 App 才能发送/接收此广播。
5.2 避免数据泄露
不要在广播中传递敏感信息(密码、token 等),因为:
- 其他 App 可能监听到(如果 exported=true)
- 广播内容可能被系统日志记录
错误示例:
scss
val intent = Intent("com.tony.LOGIN")
intent.putExtra("password", "123456") // 危险!
sendBroadcast(intent)
正确做法:
scss
// 使用 EncryptedSharedPreferences 或其他加密方案
val sharedPreferences = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// 只通过广播发送通知,敏感数据另外存储
val intent = Intent("com.tony.LOGIN_SUCCESS")
sendBroadcast(intent)
5.3 正确设置 exported 属性
Android 12+ 要求显式声明:
xml
<!-- 仅内部使用 -->
<receiver
android:name=".InternalReceiver"
android:exported="false">
...
</receiver>
<!-- 需要被其他 App 调用 -->
<receiver
android:name=".PublicReceiver"
android:exported="true"
android:permission="com.tony.CUSTOM_PERMISSION">
...
</receiver>
六、性能优化
6.1 onReceive() 中的时间限制
onReceive()
方法必须在 10 秒内完成,否则会触发 ANR(Application Not Responding)。
错误示例:
kotlin
override fun onReceive(context: Context, intent: Intent) {
// 这会导致 ANR!
Thread.sleep(15000)
}
正确做法:启动后台任务
kotlin
override fun onReceive(context: Context, intent: Intent) {
// 方案一:使用 WorkManager(推荐)
val workRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(context).enqueue(workRequest)
// 方案二:使用 goAsync()(适合短时异步任务)
val pendingResult = goAsync()
Thread {
try {
// 执行异步任务(不超过 10 秒)
doSomeWork()
} finally {
pendingResult.finish()
}
}.start()
}
6.2 避免频繁发送广播
问题场景:
scss
// 在循环中频繁发送(性能差)
repeat(1000) {
sendBroadcast(Intent("com.tony.UPDATE"))
}
优化方案:使用 SharedFlow
kotlin
object EventBus {
private val _events = MutableSharedFlow<String>(
replay = 0,
extraBufferCapacity = 100,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val events = _events.asSharedFlow()
fun emit(event: String) {
_events.tryEmit(event)
}
}
// 发送方
repeat(1000) {
EventBus.emit("Update $it")
}
// 接收方
lifecycleScope.launch {
EventBus.events.collect { event ->
// 处理事件
}
}
性能对比:
diff
BroadcastReceiver:
- 每次发送都需要跨进程通信(如果 exported=true)
- 需要序列化/反序列化 Intent
- 系统需要查找所有匹配的接收器
SharedFlow:
- 内存操作,无需跨进程
- 无需序列化
- 直接方法调用
七、常见使用场景
7.1 监听网络状态变化
kotlin
class NetworkReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = cm.activeNetwork
val capabilities = cm.getNetworkCapabilities(network)
val isConnected = capabilities?.hasCapability(
NetworkCapabilities.NET_CAPABILITY_INTERNET
) == true
if (isConnected) {
Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show()
}
}
}
}
// 动态注册
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(networkReceiver, filter)
权限:
ini
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
7.2 监听应用安装/卸载
kotlin
class AppReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val packageName = intent.data?.schemeSpecificPart
when (intent.action) {
Intent.ACTION_PACKAGE_ADDED -> {
Log.d("AppReceiver", "应用已安装:$packageName")
}
Intent.ACTION_PACKAGE_REMOVED -> {
Log.d("AppReceiver", "应用已卸载:$packageName")
}
}
}
}
Manifest 注册:
ini
<receiver
android:name=".AppReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
7.3 监听电池电量
注意:ACTION_BATTERY_CHANGED
是特殊广播,只能动态注册。
kotlin
class BatteryReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val batteryPct = (level / scale.toFloat() * 100).toInt()
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING
Log.d("BatteryReceiver", "电量:$batteryPct%,充电中:$isCharging")
}
}
// 必须动态注册
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
registerReceiver(batteryReceiver, filter)
八、常见问题与避坑指南
问题 1:收不到系统广播
原因:
- Android 8.0+ 限制了静态注册
- 广播已被系统弃用
- 缺少必要权限
解决方案:
kotlin
// 1. 改用动态注册
override fun onStart() {
super.onStart()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(receiver, filter)
}
}
// 2. 检查权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
// 3. 使用替代 API(如 ConnectivityManager.NetworkCallback)
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
Log.d("Network", "网络已连接")
}
override fun onLost(network: Network) {
Log.d("Network", "网络已断开")
}
}
connectivityManager.registerDefaultNetworkCallback(networkCallback)
问题 2:App 未启动收不到广播
原因:
- 使用了动态注册(必须 App 运行才能注册)
- Android 8.0+ 限制了静态注册
解决方案:
xml
<!-- 使用允许静态注册的系统广播 -->
<receiver android:name=".BootReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
kotlin
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// App 启动后可以启动服务、设置闹钟等
val serviceIntent = Intent(context, MyService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
} else {
context.startService(serviceIntent)
}
}
}
问题 3:内存泄漏
原因: 忘记调用 unregisterReceiver()
错误示例:
kotlin
class MainActivity : AppCompatActivity() {
private val receiver = MyReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerReceiver(receiver, IntentFilter("com.tony.TEST"))
// 忘记注销!
}
}
正确做法:
kotlin
class MainActivity : AppCompatActivity() {
private val receiver = MyReceiver()
private var isReceiverRegistered = false
override fun onStart() {
super.onStart()
if (!isReceiverRegistered) {
registerReceiver(receiver, IntentFilter("com.tony.TEST"))
isReceiverRegistered = true
}
}
override fun onStop() {
super.onStop()
if (isReceiverRegistered) {
unregisterReceiver(receiver)
isReceiverRegistered = false
}
}
}
问题 4:onReceive() 不执行
排查步骤:
- 检查 Action 是否匹配:
scss
// 发送
sendBroadcast(Intent("com.tony.TEST"))
// 接收(必须完全一致)
<intent-filter>
<action android:name="com.tony.TEST" />
</intent-filter>
- 检查 exported 设置:
xml
<!-- 如果是自定义广播且不跨进程,设为 false -->
<receiver
android:name=".MyReceiver"
android:exported="false">
...
</receiver>
- 检查权限:
xml
<!-- 如果接收器设置了权限要求 -->
<receiver
android:name=".MyReceiver"
android:permission="com.tony.PERMISSION"
android:exported="true">
...
</receiver>
<!-- 发送方必须声明该权限 -->
<uses-permission android:name="com.tony.PERMISSION" />
- 添加日志调试:
kotlin
override fun onReceive(context: Context, intent: Intent) {
Log.d("MyReceiver", "onReceive called")
Log.d("MyReceiver", "Action: ${intent.action}")
Log.d("MyReceiver", "Extras: ${intent.extras}")
}
问题 5:触发 ANR
原因: onReceive()
执行时间超过 10 秒
解决方案:
kotlin
override fun onReceive(context: Context, intent: Intent) {
// 方案一:使用 goAsync()(适合 10 秒内能完成的任务)
val pendingResult = goAsync()
CoroutineScope(Dispatchers.IO).launch {
try {
// 执行异步任务
val result = doNetworkRequest()
Log.d("Receiver", "Result: $result")
} finally {
pendingResult.finish()
}
}
// 方案二:使用 WorkManager(推荐,适合长时间任务)
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setInputData(workDataOf("key" to "value"))
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
九、总结
BroadcastReceiver 是 Android 世界的"信号员"。 它让应用之间、系统与应用之间得以沟通。
核心要点回顾:
-
注册方式选择:
- 需要 App 未启动时接收 → 静态注册(但受 Android 8.0+ 限制)
- 灵活控制生命周期 → 动态注册
-
安全性:
- 使用自定义权限防止劫持
- 不在广播中传递敏感信息
- 正确设置
exported
属性
-
性能优化:
onReceive()
必须 10 秒内完成- 避免频繁发送广播
- 考虑用 SharedFlow/LiveData 替代本地广播
-
版本适配:
- Android 8.0:静态注册限制
- Android 12:必须声明
exported
- Android 13:通知权限
- Android 14:动态注册 flag
什么时候用 BroadcastReceiver?
- 需要响应系统事件(开机、网络变化等)
- 需要跨进程通信
- 需要一对多的消息分发
什么时候不用?
- 仅在同一 App 内通信 → 用 SharedFlow/LiveData
- 需要高频通信 → 用 EventBus 或响应式框架
- 需要持久化数据 → 用数据库或文件
十、互动与扩展
你在使用 BroadcastReceiver 时踩过哪些坑?
常见问题:
- "收不到广播"
- "onReceive() 不执行"
- "App 安装失败(Android 12+)"
- "触发 ANR"
欢迎在评论区分享你的经验和解决方案。
系列文章:
- 《Android 核心组件深度系列 · 第 1 篇 Activity》
- 《Android 核心组件深度系列 · 第 2 篇 Service》
- 《Android 核心组件深度系列 · 第 3 篇 BroadcastReceiver》(本篇)
- 《Android 核心组件深度系列 · 第 4 篇 ContentProvider》(敬请期待)