第十八章 网络管理(ConnectivityService):网络状态监听、Wi-Fi与移动网络切换、VPN与网络策略、Netd与网络过滤

网络管理这块,说实话,是Android Framework里最容易出“玄学”问题的地方。用户经常抱怨“Wi-Fi明明连着,但就是上不了网”,或者“切换网络时应用闪退”。这些问题的根子,大多出在ConnectivityService和Netd的交互上。今天我就带你把这块硬骨头啃下来。

18.1 ConnectivityService 的核心职责

ConnectivityService,简称CS,是整个网络管理的“大脑”。它不负责具体的数据收发,而是做决策——用哪个网络、怎么切换、要不要通知应用。

我个人习惯把CS比作一个“网络路由器”:它监听所有网络接口的状态,然后根据策略选出一条最优路径。你想想看,手机同时开着Wi-Fi和移动数据,到底走哪条路?这就是CS说了算。

核心类关系:
ConnectivityService → NetworkAgent(代表一个网络) → Netd(内核通信) → 底层驱动

18.2 网络状态监听机制

网络状态变化,CS是怎么知道的?答案是:NetworkMonitorNetworkAgent 的配合。

每个网络连接(Wi-Fi、蜂窝、以太网)都会创建一个NetworkAgent对象。这个Agent会定期向CS上报状态:

  • CONNECTED:物理层已连接
  • CAPTIVE_PORTAL:需要认证(比如酒店Wi-Fi)
  • VALIDATED:能正常访问互联网
  • LOST:连接断开

这里有个关键点:CS不会直接相信“已连接”状态。它会启动一个NetworkMonitor,去访问一个特定的验证服务器(比如 connectivitycheck.gstatic.com)。如果验证失败,即使Wi-Fi信号满格,CS也会认为这个网络“不可用”。

避坑指南: 我曾经遇到一个项目,客户反馈手机连上公司Wi-Fi后无法上网。排查了半天,发现是公司内网屏蔽了Google的验证服务器。解决方案:要么改验证URL,要么在CS里配置跳过验证。嗯,这种问题在定制ROM里特别常见。

18.3 Wi-Fi与移动网络的切换策略

切换逻辑,说白了就是“谁更好用谁”。但“更好”的定义很复杂。CS内部维护了一个 NetworkRanker,给每个网络打分:

因素 权重 说明
是否已验证 最高 未验证的网络直接降权
信号强度 RSSI值越高越好
网络类型 Wi-Fi > 以太网 > 移动数据
用户偏好 用户手动选择的网络优先
电量消耗 移动数据更耗电,但权重不高

切换发生时,CS会发送 CONNECTIVITY_ACTION 广播。但注意——这个广播在Android 7.0之后变成了隐式广播,普通应用收不到。如果你需要监听网络变化,建议用 NetworkCallback 注册:

ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NET_CAPABILITY_INTERNET)
    .build();
cm.registerNetworkCallback(request, new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        // 网络可用
    }
    @Override
    public void onLost(Network network) {
        // 网络断开
    }
});
注意: 不要在主线程里注册NetworkCallback!回调可能很频繁,我见过有人直接在UI线程里做网络请求,结果ANR了。建议用HandlerThread处理。

18.4 VPN与网络策略

VPN接入后,CS会创建一个 虚拟网络接口。这个接口的优先级通常高于物理网络。也就是说,一旦VPN建立,所有流量默认走VPN。

但这里有个坑:分应用VPN。Android支持只让部分应用走VPN,其他应用走直连。这个逻辑在 VpnManager 里实现,底层通过 iptables 规则做流量分流。

网络策略(NetworkPolicy)则是另一个维度。它控制的是“某个应用能不能用网络”。比如:

  • 后台限制:应用在后台时禁止联网
  • 数据节省:限制非活跃应用的网络访问
  • 按需网络:只有前台应用才能使用高带宽网络

这些策略最终会下发到Netd,由Netd在底层执行。我记得有一次调试一个视频App,发现它在后台时网络请求全部超时。查了半天,原来是NetworkPolicyManager把它的UID标记成了“受限”。

18.5 Netd与网络过滤

Netd(Network Daemon)是CS和内核之间的桥梁。它运行在Native层,通过Socket与Java层的CS通信。Netd的主要工作包括:

  • 创建/销毁网络接口(比如VPN的tun0)
  • 配置路由表(决定数据包走哪个接口)
  • 设置防火墙规则(iptables/nftables)
  • DNS解析(通过netd的dnsproxyd)

网络过滤,说白了就是“谁可以上网,谁不可以”。Android用 iptables 实现了一套完整的防火墙:

# 禁止某个UID联网
iptables -A OUTPUT -m owner --uid-owner 10042 -j DROP

# 只允许VPN流量
iptables -A OUTPUT -o tun0 -j ACCEPT
iptables -A OUTPUT -j DROP

这些规则由 NetworkManagementService 下发到Netd。Netd再调用 libnetd_client 库执行。嗯,这里要注意:iptables规则是有顺序的,顺序错了会导致策略失效。我曾经见过一个Bug,因为规则插入顺序不对,导致VPN分流完全失效,所有流量都走了直连。

SVG 流程图:ConnectivityService 网络管理核心流程
应用层 (App) ConnectivityService 网络选择 / 策略管理 / 状态通知 NetworkMonitor NetworkRanker NetworkPolicyManager Netd (Network Daemon) Linux 内核 (iptables / 路由表) 用户空间 内核空间

18.6 实战:网络切换的完整链路

假设用户从Wi-Fi切换到移动数据,整个过程是这样的:

  1. Wi-Fi断开:WifiService检测到连接丢失,通知NetworkAgent
  2. CS收到事件:NetworkAgent向CS上报NETWORK_LOST
  3. 重新评分:NetworkRanker重新计算所有可用网络,发现移动数据可用
  4. 策略检查:NetworkPolicyManager确认移动数据没有被限制
  5. 切换路由:CS通知Netd修改路由表,默认网关指向移动数据接口
  6. 通知应用:CS通过NetworkCallback通知所有注册的应用

这个过程中,任何一个环节出问题都会导致切换失败。我遇到过最奇葩的一个案例:Netd的Socket缓冲区满了,导致CS下发路由命令超时,结果手机在长达30秒内处于“无网络”状态。解决方案是增大Netd的接收缓冲区大小。

调试技巧:adb shell dumpsys connectivity 可以查看当前所有网络的状态、评分、策略。如果怀疑是CS的问题,这是第一手资料。

好了,网络管理这块内容不少,但核心逻辑其实不复杂。记住一句话:CS做决策,Netd做执行。搞清楚了这两层的分工,大部分网络问题都能迎刃而解。


公众号:蓝海资料掘金营,微信deep3321