第十八章 网络管理(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是怎么知道的?答案是:NetworkMonitor 和 NetworkAgent 的配合。
每个网络连接(Wi-Fi、蜂窝、以太网)都会创建一个NetworkAgent对象。这个Agent会定期向CS上报状态:
- CONNECTED:物理层已连接
- CAPTIVE_PORTAL:需要认证(比如酒店Wi-Fi)
- VALIDATED:能正常访问互联网
- LOST:连接断开
这里有个关键点:CS不会直接相信“已连接”状态。它会启动一个NetworkMonitor,去访问一个特定的验证服务器(比如 connectivitycheck.gstatic.com)。如果验证失败,即使Wi-Fi信号满格,CS也会认为这个网络“不可用”。
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) {
// 网络断开
}
});
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分流完全失效,所有流量都走了直连。
18.6 实战:网络切换的完整链路
假设用户从Wi-Fi切换到移动数据,整个过程是这样的:
- Wi-Fi断开:WifiService检测到连接丢失,通知NetworkAgent
- CS收到事件:NetworkAgent向CS上报NETWORK_LOST
- 重新评分:NetworkRanker重新计算所有可用网络,发现移动数据可用
- 策略检查:NetworkPolicyManager确认移动数据没有被限制
- 切换路由:CS通知Netd修改路由表,默认网关指向移动数据接口
- 通知应用:CS通过NetworkCallback通知所有注册的应用
这个过程中,任何一个环节出问题都会导致切换失败。我遇到过最奇葩的一个案例:Netd的Socket缓冲区满了,导致CS下发路由命令超时,结果手机在长达30秒内处于“无网络”状态。解决方案是增大Netd的接收缓冲区大小。
adb shell dumpsys connectivity 可以查看当前所有网络的状态、评分、策略。如果怀疑是CS的问题,这是第一手资料。
好了,网络管理这块内容不少,但核心逻辑其实不复杂。记住一句话:CS做决策,Netd做执行。搞清楚了这两层的分工,大部分网络问题都能迎刃而解。
公众号:蓝海资料掘金营,微信deep3321