3. 网络状态监听实现:ConnectivityManager / 网络回调、离线状态机设计
3.1 为什么需要主动监听网络状态
离线读写核心依赖两个前提:
- 写入层:能在无网络时可靠地把数据写入本地存储;
- 读取层:能在离线场景下优先返回本地缓存,并在在线时同步最新数据。
这两者都需要一个实时、准确、低开销的网络状态信息来源。被动捕获联网异常(如请求超时)存在延迟大、误判多的问题,因此必须通过系统级的 ConnectivityManager 或跨平台的网络回调机制主动监听。
3.2 ConnectivityManager 工作原理
| 组件 | 职责 | Android 版本要求 |
|---|---|---|
ConnectivityManager | 系统级网络连接管理器,提供网络类型、可用性、能力查询 | API 1+ |
NetworkCallback | 回调接口,接收网络变化事件(可用、丢失、能力变化) | API 21+ |
NetworkCapabilities | 描述当前网络的带宽、延迟、计量属性 | API 21+ |
NetworkRequest | 监听时的过滤条件(如仅监听 WiFi 或蜂窝) | API 21+ |
最佳实践: 在 Android 10+ 上同时监听
CONNECTIVITY_CHANGE 广播已废弃,应完全使用 NetworkCallback 注册监听。
3.3 典型注册与回调代码
以下片段展示在 Application 或 Service 启动时注册网络回调:
// 获取系统服务
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
// 构建监听请求(不指定传输类型 = 监听所有网络)
NetworkRequest request = new NetworkRequest.Builder()
.build();
// 实现回调
ConnectivityManager.NetworkCallback callback =
new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// 网络可用 → 触发离线状态机 → “在线态”
NetworkStateManager.getInstance().onNetworkAvailable();
}
@Override
public void onLost(Network network) {
// 网络断开 → 触发离线状态机 → “离线态”
NetworkStateManager.getInstance().onNetworkLost();
}
@Override
public void onCapabilitiesChanged(
Network network,
NetworkCapabilities capabilities) {
// 网络能力变化(如从WiFi切到蜂窝)
boolean isMetered = !capabilities
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
NetworkStateManager.getInstance()
.onMeteredChanged(isMetered);
}
};
// 注册(建议在 onStart 时调用)
cm.registerNetworkCallback(request, callback);
3.4 离线状态机设计
网络状态不是简单的“在线/离线”二元态,因为还存在弱网、计量网络、正在切换等中间态。我们设计一个四状态机:
状态定义:
- ONLINE:网络畅通,可正常发起 API 请求。
- OFFLINE:无可用网络,所有请求落地本地。
- METERED:网络可用但为计量连接(如移动数据),应限制大体积同步。
- PENDING_SYNC:网络刚恢复,正在进行批量数据同步,期间读取优先本地。
3.5 状态机驱动实现
// NetworkStateManager.java 单例
public class NetworkStateManager {
private final StateMachine stateMachine;
private volatile NetworkState currentState = NetworkState.ONLINE;
public enum NetworkState {
ONLINE, OFFLINE, METERED, PENDING_SYNC
}
public synchronized void onNetworkAvailable() {
// 从 OFFLINE → PENDING_SYNC,然后启动同步
if (currentState == NetworkState.OFFLINE) {
currentState = NetworkState.PENDING_SYNC;
startSyncForPending();
} else if (currentState == NetworkState.METERED) {
// 如果之前是计量网络,直切 ONLINE 但标记计量
currentState = NetworkState.ONLINE;
}
}
public synchronized void onNetworkLost() {
currentState = NetworkState.OFFLINE;
// 清空待发送队列、停止同步
}
public synchronized void onMeteredChanged(boolean isMetered) {
if (isMetered && currentState == NetworkState.ONLINE) {
currentState = NetworkState.METERED;
} else if (!isMetered && currentState == NetworkState.METERED) {
currentState = NetworkState.ONLINE;
}
}
public synchronized void onSyncCompleted() {
if (currentState == NetworkState.PENDING_SYNC) {
currentState = NetworkState.ONLINE;
}
}
public boolean isOffline() {
return currentState == NetworkState.OFFLINE;
}
public boolean isMetered() {
return currentState == NetworkState.METERED;
}
}
3.6 性能与稳定性要点
| 考量维度 | 建议 |
|---|---|
| 注册时机 | Application.onCreate 注册,Activity onResume 不重复注册,避免多次回调 |
| 取消注册 | 全局生命周期结束时调用 unregisterNetworkCallback,避免内存泄漏 |
| 回调线程 | 默认在 Binder 线程,应切换到主线程或专用 HandlerThread 处理状态迁移 |
| 弱网判断 | 结合 NetworkCapabilities 的链路带宽和延迟,或使用 Ping 探测 |
| 状态同步锁 | 状态变更使用 synchronized 或 ReentrantLock,防止并发竞态 |
3.7 常见错误与调试
⚠ 易犯错误:#1 未在注销前移除回调导致内存泄漏。 确保在组件 onDestroy 或服务 onDestroy 中调用
⚠ 易犯错误:#2 误将 METERED 直接等同于 OFFLINE。 计量网络下仍可发送低优先级请求,不应完全阻断。
⚠ 易犯错误:#3 状态机未处理 PENDING_SYNC 阶段读取策略。 同步期间读取应优先返回本地缓存,防止读取到半同步的不一致数据。
unregisterNetworkCallback。⚠ 易犯错误:#2 误将 METERED 直接等同于 OFFLINE。 计量网络下仍可发送低优先级请求,不应完全阻断。
⚠ 易犯错误:#3 状态机未处理 PENDING_SYNC 阶段读取策略。 同步期间读取应优先返回本地缓存,防止读取到半同步的不一致数据。