23. LiveData与BroadcastReceiver:网络状态监听、电量变化监听、实战:网络变化提示
各位同学,今天我们来聊一个非常接地气的话题——LiveData 与 BroadcastReceiver 的联姻。
说实话,BroadcastReceiver 在 Android 里算是个老面孔了。从最早的 Android 1.0 开始,它就在那里。但问题是,很多同学用 BroadcastReceiver 的方式还停留在「注册-接收-注销」的原始阶段,代码散落在 Activity 或 Service 里,维护起来那叫一个酸爽。
我个人习惯是:能用 LiveData 包装的,绝不裸写 Receiver。为什么?因为 LiveData 自带生命周期感知,不会泄漏,不会空指针,而且 UI 层订阅起来极其优雅。
今天我们就拿两个最典型的场景开刀:网络状态监听和电量变化监听。最后再做一个实战——网络变化提示弹窗。
23.1 为什么要把 BroadcastReceiver 包装成 LiveData?
你想想看,传统的 BroadcastReceiver 写法是什么样的?
// 传统写法:在 Activity 里注册和注销
class MainActivity : AppCompatActivity() {
private lateinit var networkReceiver: BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
networkReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// 处理网络变化
}
}
registerReceiver(networkReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(networkReceiver) // 容易忘!
}
}
这段代码有什么问题?我列几个:
- 生命周期耦合:你必须在 onDestroy 里手动注销,忘了就泄漏。
- 无法感知生命周期状态:如果 Activity 在后台,Receiver 还在跑,白白浪费资源。
- 测试困难:Receiver 逻辑和 Activity 绑在一起,单元测试无从下手。
而用 LiveData 包装后,这些问题全部消失。LiveData 会自动在 onStart 时开始观察,onStop 时停止观察,onDestroy 时自动清理。说白了,LiveData 就是 BroadcastReceiver 的「智能管家」。
23.2 网络状态监听:LiveData 封装实战
我们先写一个网络状态监听器。核心思路是:创建一个自定义 LiveData,在它 active 时注册 Receiver,inactive 时注销 Receiver。
class NetworkStateLiveData(context: Context) : LiveData<Boolean>() {
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val activeNetwork = connectivityManager.activeNetworkInfo
val isConnected = activeNetwork?.isConnectedOrConnecting == true
postValue(isConnected) // 注意:这里用 postValue,因为是在子线程回调
}
}
private var appContext: Context = context.applicationContext
override fun onActive() {
super.onActive()
// LiveData 开始被观察时注册 Receiver
appContext.registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
// 立即获取一次当前状态
val activeNetwork = connectivityManager.activeNetworkInfo
value = activeNetwork?.isConnectedOrConnecting == true
}
override fun onInactive() {
super.onInactive()
// LiveData 不再被观察时注销 Receiver
appContext.unregisterReceiver(receiver)
}
}
关键点解析:
onActive()和onInactive()是 LiveData 的生命周期回调,由系统自动管理。- 使用
applicationContext避免 Activity 泄漏。 postValue()用于跨线程更新数据,setValue()只能在主线程调用。
我在项目中遇到过一个问题:Android 7.0 之后,CONNECTIVITY_ACTION 这个广播不再支持动态注册。嗯,这里要注意,从 Android 7.0(API 24)开始,系统限制了这个隐式广播。那怎么办?
解决方案是改用 registerNetworkCallback()。我们升级一下:
class NetworkStateLiveData(context: Context) : LiveData<Boolean>() {
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
postValue(true)
}
override fun onLost(network: Network) {
postValue(false)
}
}
private var appContext: Context = context.applicationContext
override fun onActive() {
super.onActive()
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, networkCallback)
// 获取当前状态
val activeNetwork = connectivityManager.activeNetworkInfo
value = activeNetwork?.isConnectedOrConnecting == true
}
override fun onInactive() {
super.onInactive()
connectivityManager.unregisterNetworkCallback(networkCallback)
}
}
避坑指南:我曾经在旧项目里混用了两种方式,结果在 Android 10 上出现了重复回调。建议统一使用 registerNetworkCallback(),兼容性更好。
23.3 电量变化监听:同样的套路
网络状态能监听,电量当然也可以。原理一模一样:
class BatteryStateLiveData(context: Context) : LiveData<Int>() {
private val appContext = context.applicationContext
private val receiver = object : 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 percentage = (level.toFloat() / scale.toFloat() * 100).toInt()
postValue(percentage)
}
}
override fun onActive() {
super.onActive()
appContext.registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
// 注意:ACTION_BATTERY_CHANGED 是粘性广播,注册后会立即收到一次
}
override fun onInactive() {
super.onInactive()
appContext.unregisterReceiver(receiver)
}
}
这里有个小细节:ACTION_BATTERY_CHANGED 是粘性广播(Sticky Broadcast)。注册后系统会立即发送最后一次的状态,所以我们不需要手动获取初始值。
但粘性广播在 Android 8.0 之后被标记为废弃。我个人建议:不要依赖粘性特性,还是在 onActive 里手动获取一次初始值更稳妥。
23.4 实战:网络变化提示弹窗
好,理论讲完了,我们来做一个完整的实战。需求很简单:
- 当网络从有到无时,弹出一个 Snackbar 提示「网络已断开」。
- 当网络从无到有时,弹出一个 Snackbar 提示「网络已恢复」。
- 只在 Activity 可见时弹出,不可见时不弹。
我们用 ViewModel + LiveData 来实现:
class NetworkViewModel : ViewModel() {
private val networkState = NetworkStateLiveData(MyApp.instance)
// 对外暴露不可变的 LiveData
val isConnected: LiveData<Boolean> = networkState
// 用于触发提示的 SingleLiveEvent(避免重复弹出)
private val _networkToastEvent = SingleLiveEvent<Boolean>()
val networkToastEvent: LiveData<Boolean> = _networkToastEvent
init {
// 监听网络变化,当状态改变时发送事件
networkState.observeForever { connected ->
_networkToastEvent.value = connected
}
}
override fun onCleared() {
super.onCleared()
networkState.removeObserver { } // 清理
}
}
这里用到了 SingleLiveEvent,它是一个只会发送一次事件的 LiveData,非常适合「弹一次 Toast」这种场景。代码我就不贴了,网上有很多实现,核心就是重写 observe() 方法,每次只分发一次。
然后在 Activity 里:
class MainActivity : AppCompatActivity() {
private val viewModel: NetworkViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 观察网络状态(用于 UI 更新)
viewModel.isConnected.observe(this) { connected ->
// 比如更新一个图标
updateNetworkIcon(connected)
}
// 观察网络变化事件(用于弹提示)
viewModel.networkToastEvent.observe(this) { connected ->
val message = if (connected) "网络已恢复" else "网络已断开"
Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG).show()
}
}
}
注意:千万不要在 isConnected 的观察回调里直接弹 Snackbar。因为每次 Activity 重建(比如旋转屏幕)时,LiveData 会重新发送最后一次的值,导致重复弹窗。用 SingleLiveEvent 就是为了避免这个问题。
23.5 知识体系总览
下面这张图总结了本章的核心逻辑:
这张图其实就说了三件事:
- 数据源:系统广播(网络、电量等)通过 BroadcastReceiver 接收。
- 中间层:自定义 LiveData 负责注册/注销 Receiver,并把数据转为 LiveData 流。
- 消费端:Activity/Fragment 通过 observe() 订阅,自动感知生命周期。
说白了,这就是一个观察者模式的经典应用。BroadcastReceiver 是事件源,LiveData 是事件总线,UI 是观察者。
23.6 总结与最佳实践
最后,我给大家几个建议:
| 场景 | 推荐做法 | 不推荐做法 |
|---|---|---|
| 网络状态监听 | 使用 registerNetworkCallback() + LiveData |
直接注册 CONNECTIVITY_ACTION 广播 |
| 电量变化监听 | 使用 ACTION_BATTERY_CHANGED + LiveData |
在 Activity 里直接注册不注销 |
| 弹窗提示 | 使用 SingleLiveEvent 避免重复 |
直接在 LiveData 回调里弹 Toast |
| 生命周期管理 | 依赖 LiveData 的 onActive/onInactive | 手动在 onResume/onPause 里注册注销 |
嗯,今天就到这里。记住一句话:能用 LiveData 包装的广播,就不要裸写。你的代码会变得更干净、更安全、更好测试。
公众号:蓝海资料掘金营,微信deep3321