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:网络刚恢复,正在进行批量数据同步,期间读取优先本地。
离线状态机(四状态) ONLINE 正常请求 OFFLINE 本地读写 METERED 仅小包同步 PENDING_SYNC 批量同步中 箭头上的事件来自 NetworkCallback 回调,状态迁移由 NetworkStateManager 驱动

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 中调用 unregisterNetworkCallback。
⚠ 易犯错误:#2 误将 METERED 直接等同于 OFFLINE。 计量网络下仍可发送低优先级请求,不应完全阻断。
⚠ 易犯错误:#3 状态机未处理 PENDING_SYNC 阶段读取策略。 同步期间读取应优先返回本地缓存,防止读取到半同步的不一致数据。