1、ConnectivityService 架构总览
1.1 核心职责与设计理念
【ConnectivityService 的五大核心职责】
═════════════════════════════════════════════════════════════
1️⃣ 网络监控与发现(Network Monitoring & Discovery)
────────────────────────────────────────────────────────────
核心功能:
├─ 监听所有网络接口的状态变化
├─ 检测新网络的出现(WiFi、LTE、Ethernet 等)
├─ 维护 NetworkAgentInfo 列表(所有已知网络)
├─ 跟踪网络的连接/断开事件
实现方式:
├─ 通过 INetworkAgent AIDL 接口接收来自网络提供者的更新
├─ NetworkAgent 在网络状态变化时调用:
│ ├─ markConnected() - 标记为连接
│ ├─ unregister() - 标记为断开
│ └─ sendNetworkCapabilities/LinkProperties - 发送能力更新
└─ ConnectivityService 维护网络列表并及时响应
数据结构:
```java
// 所有已注册的网络代理
private List<NetworkAgentInfo> mNetworkAgents = new ArrayList<>();
// 网络评分(用于选择最佳网络)
private Map<Network, NetworkScore> mNetworkScores = new HashMap<>();
// 网络状态跟踪
private Map<Network, NetworkInfo> mNetworkInfos = new HashMap<>();
2️⃣ 网络请求匹配与路由(Network Request Matching & Routing) ─────────────────────────────────────────────────────────────
核心功能: ├─ 管理应用的网络请求(NetworkRequest) ├─ 匹配网络与请求的关系 ├─ 为应用分配最合适的网络 ├─ 处理网络切换逻辑
请求类型: ├─ LISTEN 请求:监听特定网络(不使用) ├─ REQUEST 请求:要求特定网络(使用) ├─ TRACK_DEFAULT 请求:跟踪默认网络变化 └─ LISTEN_FOR_BEST 请求:监听最好的网络
匹配算法:
private void rematchAllNetworksAndRequests() {
// 步骤 1:获取所有网络,按评分排序(从高到低)
List<NetworkAgentInfo> sorted = sortNetworksByScore(mNetworkAgents);
// 步骤 2:对于每个网络,找出满足条件的所有请求
for (NetworkAgentInfo nai : sorted) {
for (NetworkRequestInfo nri : mNetworkRequests) {
if (networkSatisfiesRequest(nai, nri.request)) {
// 步骤 3:为请求分配网络
allocateNetworkForRequest(nai, nri);
}
}
}
// 步骤 4:通知应用有新网络可用
notifyNetworkCallbacks();
}
网络匹配的核心逻辑(SimplifyNetworkCapabilities):
private boolean networkSatisfiesRequest(
NetworkAgentInfo nai, NetworkRequest request) {
NetworkCapabilities caps = nai.networkCapabilities;
// 检查 REQUIRED 能力(必须满足)
for (int reqCap : request.getCapabilities()) {
if (!caps.hasCapability(reqCap)) {
return false; // ❌ 网络不满足要求
}
}
// 检查 UNWANTED 能力(不能有)
for (int unwantedCap : request.getUnwantedCapabilities()) {
if (caps.hasCapability(unwantedCap)) {
return false; // ❌ 网络有不需要的能力
}
}
// 如果指定了特定传输方式
if (request.hasTransportType()) {
if (!caps.hasTransport(request.getTransport())) {
return false; // ❌ 传输方式不匹配
}
}
return true; // ✓ 网络满足所有要求
}
3️⃣ 网络状态上报与广播(Network State Reporting & Broadcasting) ──────────────────────────────────────────────────────────────
核心功能: ├─ 收集网络状态信息(连接、断开、能力变化等) ├─ 通知应用网络变化(通过 NetworkCallback) ├─ 发送 CONNECTIVITY_ACTION 广播给系统 ├─ 更新默认网络
网络状态变化的上报流程:
NetworkAgent 状态变化
↓
ConnectivityService.handleNetworkInfoChange()
↓
notifyNetworkCallbacks(nai, callbackType)
↓
通知所有监听该网络的 NetworkCallback
↓
App 收到回调
├─ onAvailable(Network)
├─ onCapabilitiesChanged(Network, caps)
├─ onLinkPropertiesChanged(Network, lp)
└─ onLost(Network)
所有支持的回调类型(CallbackType):
// Framework 能力相关
CALLBACK_PRECHECK // 网络预检查(验证前)
CALLBACK_AVAILABLE // 网络可用
CALLBACK_LOSING // 网络即将丧失(Linger 状态)
CALLBACK_LOST // 网络已丧失
CALLBACK_UNAVAILABLE // 网络不可用
// 能力与属性变化
CALLBACK_CAPABILITIES_CHANGED // 能力变化
CALLBACK_LINK_PROPERTIES_CHANGED // 链路属性变化
CALLBACK_IP_CHANGED // IP 地址变化
// 验证相关
CALLBACK_NETWORK_STATE_CHANGED // 网络验证状态变化
CALLBACK_PROVISIONING_CHANGED // 配置状态变化
// 高级回调
CALLBACK_BLK_CHANGED // 阻塞状态变化
CALLBACK_SUSPENDED // 网络暂停
CALLBACK_RESUMED // 网络恢复
4️⃣ 网络验证与评估(Network Validation & Assessment) ───────────────────────────────────────────────────
核心功能: ├─ 确定网络是否有真正的互联网连接 ├─ 检测是否存在强制门户(Captive Portal) ├─ 评估网络的连通性质量 ├─ 根据验证结果更新网络能力
验证过程(3 个阶段):
阶段 1: 网络连接
├─ NetworkAgent 调用 markConnected()
├─ 网络标记为已连接(LOCAL_NETWORK 除外)
└─ 如果网络满足 INTERNET 要求,进入验证
阶段 2: NetworkMonitor 探测
├─ 发送 HTTP 请求到 captive portal 检测服务器
├─ 检查 HTTP 重定向(强制门户)
├─ 发送 DNS 查询(DNS 可达性)
├─ 验证 HTTPS(私有 DNS)
└─ 根据结果返回验证状态
阶段 3: 更新 NET_CAPABILITY_VALIDATED
├─ 如果所有探测通过 → 添加 NET_CAPABILITY_VALIDATED
├─ 如果存在强制门户 → 添加 NET_CAPABILITY_CAPTIVE_PORTAL
└─ 如果只有部
分连通 → 添加 NET_CAPABILITY_PARTIAL_CONNECTIVITY
验证时间线:
T=0: 网络连接
└─ NetworkAgent.markConnected() 被调用
T=1: ConnectivityService 创建网络
├─ 通知 NetworkMonitor 开始验证
└─ 如果网络满足 INTERNET 要求
T=2-5s: NetworkMonitor 探测
├─ HTTP GET 到 Google 的探测服务器
├─ DNS 查询 Google 域名
└─ HTTPS 连接测试
T=5s: 验证完成
├─ 更新 NET_CAPABILITY_VALIDATED
├─ 将网络标记为"就绪"
└─ 应用可以使用该网络
验证结果(6 种):
1. NETWORK_VALIDATION_RESULT_VALID
└─ 网络可以访问互联网,没有强制门户
└─ 添加 NET_CAPABILITY_VALIDATED
2. NETWORK_VALIDATION_RESULT_PARTIAL
└─ 网络有部分连通(例如只有 IPv4 或 IPv6)
└─ 添加 NET_CAPABILITY_PARTIAL_CONNECTIVITY
3. NETWORK_VALIDATION_RESULT_SKIPPED
└─ 验证被跳过(例如 LOCAL_NETWORK)
└─ 不添加任何验证能力
4. NETWORK_VALIDATION_RESULT_PORTAL_DETECTED
└─ 检测到强制门户
└─ 添加 NET_CAPABILITY_CAPTIVE_PORTAL
5. NETWORK_VALIDATION_RESULT_OFFLINE_DETECTED
└─ 网络完全离线(不能访问任何外部资源)
└─ 不添加任何验证能力
6. NETWORK_VALIDATION_RESULT_INVALID
└─ 验证失败(未知原因)
└─ 不添加任何验证能力
5️⃣ 网络策略执行与流量控制(Network Policy & Traffic Control) ──────────────────────────────────────────────────────────────
核心功能: ├─ 实施后台应用限制 ├─ 控制流量计量(Metered) ├─ 数据使用限制与警告 ├─ 省流量模式(Data Saver) ├─ App 权限与网络访问控制
流量控制层级:
Layer 1: 全局策略
├─ Background Restriction(后台限制)
├─ Data Saver Mode(省流量)
├─ Doze Mode(打瞌睡模式)
└─ Power Save Mode(省电)
↓
Layer 2: UID 级限制
├─ 应用是否在后台运行
├─ 应用是否被冻结
├─ 应用是否被锁定
└─ 应用的权限(INTERNET, ACCESS_NETWORK_STATE 等)
↓
Layer 3: 网络级限制
├─ 网络是否被计量(METERED)
├─ 网络是否被漫游(ROAMING)
├─ 网络是否受限(RESTRICTED)
└─ 网络的 VPN 状态
↓
Layer 4: BPF 防火墙执行
├─ eBPF 在内核网络栈中拦截数据包
├─ 根据 UID + Network + Policy 决定是否转发
└─ 实时生效,无延迟
数据流与网络访问决策:
private void updateNetworkBlockStatus(int uid, Network network) {
// 步骤 1: 检查应用是否有 INTERNET 权限
if (!hasPermission(uid, Manifest.permission.INTERNET)) {
block(uid, network);
return;
}
// 步骤 2: 检查应用是否在后台
if (isUidInBackground(uid)) {
if (mRestrictBackgroundEnabled) {
block(uid, network);
return;
}
}
// 步骤 3: 检查网络是否计量
if (isNetworkMetered(network)) {
if (isDataSaverEnabled() && !isWhitelisted(uid)) {
block(uid, network);
return;
}
}
// 步骤 4: 检查 VPN 和特殊角色
if (isNetworkVPN(network)) {
// VPN 流量通常有特殊处理
}
// 步骤 5: 通过 BPF 防火墙执行
applyFirewallRule(uid, network, ALLOW);
}
#### **1.2 与其他系统服务的关系**
【ConnectivityService 的依赖关系】 ═════════════════════════════════════════════════════════════
┌─────────────────────────────────┐
│ SystemServer │
│ (启动所有系统服务的入口) │
└────────────────┬────────────────┘
│
┌───────────────┴────────────────┐
│ │
▼ ▼
┌─────────────────────────┐ ┌──────────────────────┐ │ ConnectivityService │◄───┤ NetworkManagementSvc │ │ │ │ (Netd 接口) │ │ 核心网络管理服务 │ │ │ │ - 网络监控 │ └──────────────────────┘ │ - 请求匹配 │ △ │ - 路由决策 │ │ │ - 策略执行 │ INetd 接口 └────┬────────────────────┘ │ │ (Linux netlink socket) │ │ ├─────────────┬────────────────────┘ │ │ ▼ ▼ ┌──────────────────────────────────┐ ┌─────────────────┐ │ NetworkMonitor │ │ DnsResolver │ │ (网络验证) │ │ (DNS 解析) │ │ - 检测强制门户 │ │ │ │ - 验证互联网连通性 │ │ 与 NetworkMonitor │ - 检测网络质量 │ │ 协作验证网络 │ └──────────────────────────────────┘ └─────────────────┘
▼
┌──────────────────────────────┐ ┌──────────────────────┐ │ WifiManager / WiFiService │ │ TelephonyManager │ │ (WiFi 网络代理) │ │ (移动网络代理) │ │ - 注册 WifiNetworkAgent │ │ - 注册 Cellular │ │ - 报告 WiFi 状态 │ │ NetworkAgent │ │ - WiFi 扫描与连接 │ │ - 报告蜂窝状态 │ │ │ │ - 信号强度更新 │ └──────────────────────────────┘ └──────────────────────┘
│ │
└──────────────┬───────────────────────┘
│
NetworkAgent 接口 (AIDL)
│
┌─────────────┴──────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐ │ VpnManager │ │ EthernetManager │ │ (VPN 代理) │ │ (以太网代理) │ │ │ │ │ │ - VPN 连接 │ │ - 有线网络 │ │ - 应用 VPN 路由 │ │ - 企业网络 │ └──────────────────┘ └──────────────────┘
关键交互流:
【网络请求的完整处理流程】 ════════════════════════════════════════════════════════
应用层 ├─ App 调用 ConnectivityManager.requestNetwork() └─ 发送 NetworkRequest 到 ConnectivityService
ConnectivityService (主线程 Handler) ├─ 接收 NetworkRequest(通过 Binder) ├─ 创建 NetworkRequestInfo ├─ 调用 rematchAllNetworksAndRequests() └─ 将请求发送给 NetworkFactory
NetworkFactory(通常在 WifiService 或 TelephonyManager) ├─ 接收请求 ├─ 检查是否可以满足(信号强度、资源可用性) ├─ 如果可以:启动网络连接流程 ├─ 如果不能:等待条件改善或拒绝 └─ 创建 NetworkAgent
NetworkAgent(新网络) ├─ 调用 register() 向 ConnectivityService 注册 ├─ 初始状态:CONNECTING ├─ 待网络就绪:调用 markConnected() ├─ 状态更新:调用 sendNetworkCapabilities(), sendNetworkScore() 等 └─ 网络断开:调用 unregister()
ConnectivityService 对 NetworkAgent 的响应 ├─ onNetworkInfoChanged():网络状态变化 ├─ onNetworkCapabilitiesChanged():能力变化 ├─ onNetworkScoreChanged():评分变化 ├─ 触发重新匹配:rematchAllNetworksAndRequests() ├─ 发送验证请求:发送给 NetworkMonitor └─ 广播网络变化:notifyNetworkCallbacks()
NetworkMonitor(验证网络) ├─ 接收网络信息 ├─ 发起 HTTP/HTTPS 探测 ├─ 查询 DNS ├─ 返回验证结果:VALID, PORTAL, OFFLINE 等 └─ ConnectivityService 更新网络能力
最终回调给应用 ├─ 通过 NetworkCallback 接口 ├─ onAvailable(Network) ├─ onCapabilitiesChanged() ├─ 应用可以开始使用网络 └─ 应用 getActiveNetwork() 返回该网络
#### **1.3 Android 15 的架构演进**
【Android 14 → Android 15 的主要变化】 ═════════════════════════════════════════════════════════════
Feature 1: 更精细的网络状态跟踪 ──────────────────────────────────
Android 14: ├─ 网络状态:CONNECTED / DISCONNECTED ├─ 验证状态:VALIDATED / UNVALIDATED └─ 基本的评分机制
Android 15: ├─ 网络状态:CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED ├─ Linger 状态:网络断开前可保活一段时间 ├─ 验证状态细分: │ ├─ VALIDATED(有效的互联网连接) │ ├─ PARTIAL(部分连通,例如仅 IPv4) │ ├─ CAPTIVE_PORTAL(强制门户) │ ├─ PROVISIONING(配置中) │ └─ OFFLINE(离线) ├─ 更详细的网络能力: │ ├─ OEM_PAID(OEM 付费网络) │ ├─ OEM_PRIVATE(OEM 私有网络) │ └─ LOCAL_NETWORK(本地网络,不需要互联网) └─ NetworkScore 更复杂的评分算法
Feature 2: LocalNetwork 支持 ──────────────────────────
问题:应用需要连接局域网设备(打印机、NAS 等),但不需要互联网
Android 14: ├─ LocalNetwork 概念有限 ├─ 本地网络无法与互联网网络共存 └─ 应用需要特殊处理
Android 15: ├─ 完整的 LocalNetwork 框架 ├─ LocalNetworkConfig 定义本地网络属性 ├─ 应用可以: │ ├─ 使用互联网网络(默认) │ ├─ 同时使用本地网络 │ ├─ 在两者之间切换 │ └─ 列举本地网络设备 ├─ DNS 解析支持: │ ├─ 局域网 mDNS(.local 域名) │ ├─ 企业 DNS(Active Directory) │ └─ 云端 DNS(AWS, Azure 等) └─ 应用无需 INTERNET 权限也能访问本地资源
Feature 3: 改进的网络验证机制 ────────────────────────────
Android 14: ├─ 简单的 HTTP 重定向检测 ├─ 缺少某些场景下的验证支持 └─ 验证失败处理不够细致
Android 15: ├─ 更智能的门户检测: │ ├─ 支持多种门户类型(Wifi 热点、航班 WiFi) │ ├─ 更好的假阳性(false positive)避免 │ └─ 支持跳过已知的良好网络验证 ├─ 隐私 DNS 验证: │ ├─ 支持 DNS-over-HTTPS (DoH) │ ├─ 支持 DNS-over-TLS (DoT) │ └─ 支持私有 DNS 转发 ├─ 部分连通性处理: │ ├─ IPv4-only 网络(某些运营商) │ ├─ IPv6-only 网络(新兴运营商) │ └─ 混合 Dual-stack 检测 └─ 验证缓存: └─ 相同 SSID/BSSID 的 WiFi 在一小时内无需重复验证
Feature 4: NetworkScore 演进 ─────────────────────────────
Android 14: ├─ 基本评分(-100 到 +100) ├─ 考虑因素: │ ├─ 网络类型(WiFi > LTE > 2G) │ ├─ 信号强度 │ ├─ 是否计量 │ └─ 是否验证 └─ 简单的排序算法
Android 15: ├─ 新的评分范围:0 到任意值(无上限) ├─ 考虑因素增加: │ ├─ 带宽(MBps) │ ├─ 延迟(毫秒) │ ├─ 丢包率(百分比) │ ├─ 网络稳定性(连接持续时间) │ ├─ 地理位置(优先本地网络) │ └─ 运营商优先级设置(OEM 配置) ├─ 更细致的决策: │ ├─ WiFi 5GHz vs 2.4GHz(不同评分) │ ├─ WiFi 加密强度(不同评分) │ ├─ LTE vs 5G(动态评分) │ └─ 不同 SIM 卡的评分(多卡设备) └─ 机器学习预测: └─ 基于历史数据预测网络质量(测试中)
Feature 5: 多网络并行使用 ──────────────────────────
Android 14: ├─ 多个应用使用不同网络(通过 NetworkRequest) ├─ 但默认网络通常是单一的(WiFi 或 LTE) └─ 应用很难同时使用多个网络
Android 15: ├─ 完整的多网络栈支持: │ ├─ 应用可以同时使用互联网网络 + 本地网络 │ ├─ 应用可以同时使用 WiFi + 移动网络(部分应用) │ └─ VPN 可以与其他网络共存 ├─ 改进的 DNS 处理: │ ├─ 支持多个 DNS 搜索列表 │ ├─ 根据域名选择 DNS 服务器 │ └─ 优化 DNS 重试(减少延迟) ├─ 路由策略(策略路由): │ ├─ 基于目标地址选择网络 │ ├─ 基于应用 UID 选择网络 │ ├─ 基于流量优先级选择网络 │ └─ VPN 流量优先级处理 └─ 应用 API: └─ 新的 NetworkCapabilities 和 NetworkRequest 选项
Feature 6: 改进的后台限制 ─────────────────────────
Android 14: ├─ 全局后台限制 ├─ 应用白名单机制 └─ 限制粒度较粗
Android 15: ├─ 更细致的限制: │ ├─ 按功能限制(定位、后台 Sync 等) │ ├─ 不同应用角色的不同限制 │ └─ 与设备模式关联(省电模式时更严格) ├─ 更聪明的检测: │ ├─ 检测应用是否在前台(基于 Visible Window) │ ├─ 检测是否有用户交互 │ └─ 检测优先级标签(PRIORITY, NORMAL, LOW) ├─ 改进的白名单: │ ├─ 动态白名单(基于条件) │ ├─ 时间表白名单(特定时间段) │ └─ 隐私感知白名单(权限相关) └─ 用户透明度: └─ Settings 中可见的网络限制原因
Feature 7: 安全与隐私增强 ─────────────────────────
Android 14: ├─ 基本的权限检查 ├─ MAC 地址隐藏(WiFi) └─ 有限的加密支持
Android 15: ├─ 增强的权限模型: │ ├─ 细粒度权限(CHANGE_NETWORK_STATE 分离) │ ├─ 权限委托(应用A 可代表应用B 请求网络) │ └─ 权限检查审计日志 ├─ 更好的隐私: │ ├─ 隐机制 MAC 地址(随机化间隔) │ ├─ MAC 地址池管理 │ ├─ MAC 地址与 SSID 绑定 │ └─ 防止 MAC 地址追踪 ├─ 加密网络管理: │ ├─ WPA3 完整支持 │ ├─ OWE(Open With Encryption) │ ├─ 企业 EAP 改进 │ └─ IoT 设备网络隔离 └─ 恶意网络防护: ├─ 检测已知的恶意 SSID ├─ 中间人攻击防护(HSTS) └─ DNS 劫持检测
---
### **2、NetworkAgent 机制深度解析**
#### **2.1 NetworkAgent 生命周期**
【完整的 NetworkAgent 生命周期】 ═════════════════════════════════════════════════════════════
状态图: ┌─────────────────────────────────────────────────────────┐ │ NetworkAgent 生命周期 │ └─────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 1. UNREGISTERED (初始化) ┃
┃ - 创建 NetworkAgent ┃
┃ - 配置初始能力和属性 ┃
┃ - 尚未向系统注册 ┃
┗━━━┯━━━━━━━━━━━━━━━━━━━━┛
│
│ register()
│
┏━━━▼━━━━━━━━━━━━━━━━━━━━┓
┃ 2. REGISTERED (已注册) ┃
┃ - 向 ConnectivityService ┃
┃ 注册 ┃
┃ - 收到 onRegistered() ┃
┃ - 初始状态:DISCONNECTED ┃
┗━━━┯━━━━━━━━━━━━━━━━━━━━┛
│
│ (网络连接过程)
│ - 发起连接
│ - 发送能力/属性更新
│
┏━━━▼━━━━━━━━━━━━━━━━━━━━┓
┃ 3. CONNECTING (连接中) ┃
┃ - 网络处于连接过程 ┃
┃ - 向 ConnectivityService ┃
┃ 发送实时更新 ┃
┃ - 信号强度、IP 地址等可能 ┃
┃ 不完整 ┃
┗━━━┯━━━━━━━━━━━━━━━━━━━━┛
│
│ markConnected()
│ - 标记网络已连接
│ - 完整 IP 配置
│ - 可以接收流量
│
┏━━━▼━━━━━━━━━━━━━━━━━━━━┓
┃ 4. CONNECTED (已连接) ┃
┃ - L3 连通性已建立 ┃
┃ - 应用可以使用 ┃
┃ - 可以验证互联网连通性 ┃
┃ - NetworkMonitor 可开始 ┃
┃ 验证工作 ┃
┗━━━┯━━━┯━━━━━━━━━━━━━━┛
│ │
│ │ (网络更新)
│ ├─ sendNetworkCapabilities()
│ ├─ sendLinkProperties()
│ ├─ sendNetworkScore()
│ └─ (重复循环)
│
│ (网络断开或被替换)
│
┌───┴──────────────────────┐
│ │
┏━━━▼━━━━┓ ┏━━━━━━━━━▼━━┓
┃ 5a. 正常断开 ┃ 5b. 被替换 ┃
┃ unregister() ┃ unregisterAfter
┃ ┃ Replacement()
┗━━━┯━━━━┛ ┗━━━┯━━━━━┛
│ │
│ (网络清理) │ (网络保活)
│ - 释放资源 │ - 仍满足旧请求
│ - 断开网络 │ - 但新的同类网络优先
│ - 停止验证 │ - 在超时或替换完成时断开
│ │
┏━━━▼━━━━━━━━━━━━━━━▼━━┓
┃ 6. DISCONNECTED (已断开) ┃
┃ - 网络不再可用 ┃
┃ - 资源已释放 ┃
┃ - 应用失去该网络 ┃
┃ - onDisconnected() 被调用 ┃
┃ - Agent 不可再使用 ┃
┗━━━━━━━━━━━━━━━━━━━━━━┛
详细阶段说明:
```java
【Stage 1: 初始化 (Initialization)】
─────────────────────────────────
// WiFi 网络代理示例
WifiNetworkAgent agent = new WifiNetworkAgent(
context,
looper,
"WifiAgent-2.4GHz", // 日志标签
// 初始网络能力
new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_WIFI)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_METERED)
.addCapability(NET_CAPABILITY_NOT_ROAMING)
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
.build(),
// 初始链路属性
new LinkProperties.Builder()
.setInterfaceName("wlan0")
.addLinkAddress("192.168.1.100/24")
.addRoute(new RouteInfo(null, "192.168.1.1"))
.addDnsServer(InetAddress.getByName("8.8.8.8"))
.build(),
// 初始评分
new NetworkScore.Builder()
.setTransportPrimary(true)
.setSignalStrength(85) // WiFi 信号强度 (0-100)
.build(),
// 配置
config,
provider
);
// 在这个阶段,Agent 已创建但尚未注册
// 网络还不被系统知道
【Stage 2: 注册 (Registration)】
─────────────────────────────────
// 调用 register() 向 ConnectivityService 注册
agent.register();
→ ConnectivityService 处理流程:
├─ 创建 NetworkAgentInfo 包装 Agent
├─ 添加到 mNetworkAgents 列表
├─ 分配唯一的 netId
├─ 发送 onRegistered() 回调给 Agent
└─ 触发网络重匹配(rematchAllNetworksAndRequests)
// 在 register() 中,Agent 初始状态是 DISCONNECTED
// 这意味着网络还在连接过程中
【Stage 3: 连接 (Connecting)】
─────────────────────────────
// 网络提供者开始建立连接
// 例如 WiFi 模块执行以下操作:
step 1: 扫描和选择 AP
├─ WifiManager.startScan()
├─ 列出可用 SSID
└─ 选择最好的 BSSID
step 2: 发起连接
├─ 调用 WifiManager.connect()
├─ 发送 Association Request 到 AP
├─ AP 验证证书(如需要)
└─ 建立 802.11 连接
step 3: 获取 IP 地址
├─ 发送 DHCP Discovery
├─ 接收 DHCP Offer
├─ 发送 DHCP Request
└─ 获得 IP 地址,TTL 等信息
step 4: 向 ConnectivityService 报告进展
├─ 调用 sendNetworkCapabilities()
│ └─ 更新能力(例如加入 IPv6 能力)
├─ 调用 sendLinkProperties()
│ └─ 更新 IP 地址、DNS、Gateway 等
├─ 调用 sendNetworkScore()
│ └─ 更新信号强度得分
└─ 重复这些调用,直到完全就绪
// 在这个阶段,网络是 CONNECTING
// ConnectivityService 知道这个网络正在建立
// 但应用尚不能使用
【Stage 4: 已连接 (Connected)】
─────────────────────────────
// 当网络完全就绪时,调用 markConnected()
agent.markConnected();
→ ConnectivityService 处理流程:
├─ 更新网络状态为 CONNECTED
├─ 检查网络是否需要验证(是否满足 INTERNET 要求)
├─ 如需要,向 NetworkMonitor 发送验证请求
├─ 更新 mNetworkAgents 中的状态
├─ 触发网络重匹配
└─ 应用可能开始接收这个网络的回调
// 此时应用可以:
├─ 通过 NetworkRequest 请求该网络
├─ 使用 Network.getSocketFactory() 获取套接字
├─ 将流量定向到该网络
└─ 接收该网络的 CALLBACK_AVAILABLE
// 网络仍在验证中(如果需要)
// 网络可能没有 NET_CAPABILITY_VALIDATED 能力
好的,我继续深入讲解 Android 15 网络子系统。
现在让我继续详细讲解:
好的,继续讲解。现在我进入第 2-4 部分的详细讲解:
2.2 网络状态上报流程与回调机制
【NetworkAgent 上报网络状态的完整流程】
═════════════════════════════════════════════════════════════
核心概念:
├─ NetworkAgent 作为"信使"向 ConnectivityService 上报网络状态
├─ 通过 INetworkAgentRegistry AIDL 接口发送更新
├─ ConnectivityService 在专用线程处理这些更新
└─ 最终通过 NetworkCallback 通知应用
时序与调用链:
T=0: 网络事件发生 (例如获得 IP 地址)
└─ WiFi Driver / Telephony 检测到事件
T=1: NetworkAgent 中的事件处理
├─ WiFiNetworkAgent.onNetworkStateChange()
│ └─ 调用 sendNetworkCapabilities()
│
├─ sendNetworkCapabilities() 实现:
│ ├─ 创建新的 NetworkCapabilities 对象
│ ├─ 通过 queueOrSendMessage() 添加到消息队列
│ └─ AsyncResult.forMessage(mMessage)
│
└─ queueOrSendMessage() 内部:
├─ 检查是否可以立即发送(通常在同一线程)
├─ 或将消息排队到 mRegistry Messenger
└─ 最后通过 Binder IPC 发送
T=2: IPC 传输 (Binder)
└─ AIDL 参数序列化
├─ NetworkCapabilities 被 Parcel 化
└─ 通过 Binder 内核驱动传输
T=3: ConnectivityService 接收 (Handler Thread)
├─ Messenger 中的 Handler 收到消息
├─ 调用 NetworkAgentMessageHandler.handleMessage()
├─ 根据消息类型分派:
│ ├─ ASYNCCHANNEL_CMD_SUBSCRIBE_REPLY
│ ├─ REQUEST_NETWORK
│ ├─ RELEASE_NETWORK
│ ├─ NETWORK_INFO_CHANGED
│ ├─ NETWORK_CAPABILITIES_CHANGED
│ ├─ LINK_PROPERTIES_CHANGED
│ └─ ...
└─ 调用对应的处理方法
T=4: ConnectivityService 处理状态变化
├─ 更新 NetworkAgentInfo 中的状态
├─ 可能触发网络重匹配
├─ 验证网络(如需要)
└─ 广播通知所有监听者
T=5: 应用回调 (通过 NetworkCallback)
├─ 同步 Handler 线程中
├─ 或异步通过 Binder 回调
└─ 应用收到通知:
├─ onAvailable()
├─ onCapabilitiesChanged()
├─ onLinkPropertiesChanged()
└─ ...
【4 种关键的状态上报方法】
═════════════════════════════════════════════════════════════
1️⃣ sendNetworkCapabilities() - 能力变化
────────────────────────────────────
用途:
├─ 上报网络的功能能力
├─ 例如:支持 IPv6、计量、验证状态等
├─ 在网络连接的任何阶段可调用
示例代码:
```java
public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
// 参数验证
if (networkCapabilities == null) {
throw new NullPointerException("networkCapabilities is null");
}
// 构建新的能力对象
NetworkCapabilities newCaps = new NetworkCapabilities.Builder(networkCapabilities)
// 可选:添加或移除能力
.build();
// 发送到 ConnectivityService
// 内部调用:queueOrSendMessage(mNetworkCapabilitiesCallback, newCaps);
queueOrSendMessage(reg -> reg.sendNetworkCapabilities(newCaps));
}
常见的能力变化: ├─ 获得 IPv6:addCapability(NET_CAPABILITY_IPV6) ├─ 网络验证:addCapability(NET_CAPABILITY_VALIDATED) ├─ 检测门户:addCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ├─ 信号变化:setSignalStrength(85) └─ 连通性部分:addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)
ConnectivityService 的处理:
private void handleNetworkCapabilitiesChanged(
NetworkAgentInfo nai, NetworkCapabilities caps) {
// 检查是否真的发生了变化
NetworkCapabilities oldCaps = nai.networkCapabilities;
if (oldCaps.equals(caps)) {
return; // 无变化,无需处理
}
// 记录日志(用于调试)
logNetworkEvent(nai, "Capabilities changed", caps);
// 更新网络的能力
nai.setNetworkCapabilities(caps);
// 重新评估网络的适用性(网络可能失去某些要求的能力)
rematchAllNetworksAndRequests();
// 通知所有监听者
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAPABILITIES_CHANGED);
// 验证状态变化需要特殊处理
if (oldCaps.hasCapability(NET_CAPABILITY_VALIDATED)
!= caps.hasCapability(NET_CAPABILITY_VALIDATED)) {
// 验证状态改变,更新 NetworkMonitor
updateNetworkMonitorForValidation(nai);
}
}
2️⃣ sendLinkProperties() - 链路属性变化 ────────────────────────────────────
用途: ├─ 上报网络的 L3 属性(链路层以上) ├─ IP 地址、子网掩码、网关、DNS 等 ├─ 路由信息、接口名称
示例代码:
public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
if (linkProperties == null) {
throw new NullPointerException("linkProperties is null");
}
// 典型场景:获得 IP 地址后更新
LinkProperties newProps = new LinkProperties.Builder(linkProperties)
.setInterfaceName("wlan0") // 接口名
.addLinkAddress("192.168.1.100/24") // IP 和子网掩码
.addRoute(new RouteInfo(null, "192.168.1.1")) // 默认网关
.addDnsServer(InetAddress.getByName("8.8.8.8")) // DNS 服务器
.build();
queueOrSendMessage(reg -> reg.sendLinkProperties(newProps));
}
ConnectivityService 的处理:
private void handleLinkPropertiesChanged(
NetworkAgentInfo nai, LinkProperties lp) {
LinkProperties oldLp = nai.linkProperties;
if (oldLp.equals(lp)) {
return; // 无变化
}
// 更新 DNS 信息(系统 DNS 解析器)
updateDnsServers(nai, lp);
// 更新路由表(netd)
updateRouting(nai, lp);
// 更新 IP 地址(VPN、6to4 转换等)
updateIpv6Configuration(nai, lp);
// 通知应用
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LINK_PROPERTIES_CHANGED);
// 如果接口名改变,更新 BPF 防火墙规则
if (!oldLp.getInterfaceName().equals(lp.getInterfaceName())) {
updateBpfRules(nai);
}
}
IP 地址变化的影响: ├─ 应用收到 CALLBACK_IP_CHANGED ├─ Socket 连接可能需要重建 ├─ DNS 查询可能使用新的 DNS 服务器 └─ 路由表改变影响流量转向
3️⃣ sendNetworkScore() - 网络评分 ────────────────────────────────
用途: ├─ 告知系统网络质量(好坏) ├─ 影响网络选择(决定使用哪个网络) ├─ 动态更新(网络质量实时变化)
示例代码:
public void sendNetworkScore(@NonNull NetworkScore score) {
if (score == null) {
throw new NullPointerException("score is null");
}
// 评分通常基于信号强度
// WiFi: 0-100 (RSSI 强度)
// LTE: 0-100 (RSRP 强度)
NetworkScore newScore = new NetworkScore.Builder()
.setTransportPrimary(true) // 是否是主要传输方式
.setSignalStrength(rssiValue) // 信号强度
.setExiting(false) // 是否正在退出(被替换)
.build();
queueOrSendMessage(reg -> reg.sendNetworkScore(newScore));
}
评分如何影响网络选择(SimpleNetworkScorer):
private int scoreDiff(NetworkScore score1, NetworkScore score2) {
// 比较两个网络的评分
// 返回正数:score1 更好
// 返回负数:score2 更好
// 返回 0:相同质量
// WiFi 通常评分高于 LTE
// 5GHz WiFi 高于 2.4GHz WiFi
// 信号强的网络高于信号弱的网络
// 如果 score1 是 WiFi,score2 是 LTE:
// → score1 通常赢(假设两者都连接了)
// 如果都是 WiFi,但信号不同:
// → 信号强的赢 (80dbm > 70dbm)
}
// NetworkRanker 使用这个评分决定默认网络
private Network selectBestNetwork(List<NetworkAgentInfo> networks) {
// 按评分从高到低排序
Collections.sort(networks, (a, b) -> {
return b.getScore() - a.getScore();
});
// 选择评分最高的
if (!networks.isEmpty()) {
return networks.get(0).network;
}
return null;
}
典型的评分变化场景:
WiFi 信号从 -60dbm 变为 -75dbm(变弱)
→ sendNetworkScore(new NetworkScore(..., 75)) (假设 75 分)
→ ConnectivityService 重新评估
→ 如果评分低于 LTE,可能切换到 LTE
用户靠近 WiFi 路由器,信号变强 -50dbm
→ sendNetworkScore(new NetworkScore(..., 95)) (95 分)
→ 切换回 WiFi(评分更高)
4️⃣ markConnected() - 标记网络已连接 ───────────────────────────────
用途: ├─ 表示 L3 连通性已建立 ├─ IP 地址、网关、DNS 都已配置 ├─ 网络可以接受流量
方法签名:
public void markConnected() {
// 这是一个标志性方法,表示网络已准备就绪
// 之前的 CONNECTING 状态 → 现在的 CONNECTED 状态
queueOrSendMessage(reg -> reg.markConnected());
}
ConnectivityService 的处理:
private void handleMarkConnected(NetworkAgentInfo nai) {
if (nai.getNetworkState() == NetworkInfo.State.CONNECTED) {
return; // 已经连接,无需再次处理
}
// 更新网络信息
NetworkInfo info = nai.networkInfo;
info.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
// 创建网络(如果还未创建)
// 这会通知 netd 创建 Linux 网络命名空间
createNetwork(nai);
// 启动验证(如果网络要求 INTERNET 能力)
if (nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) {
startNetworkMonitor(nai); // 开始验证网络连通性
}
// 应用现在可以开始使用这个网络
// 触发网络重匹配,应用可能会 onAvailable() 回调
rematchAllNetworksAndRequests();
}
markConnected() 之前的典型状态:
step 1: register() - 网络注册,初始状态 DISCONNECTED
step 2: sendNetworkCapabilities() - 上报初始能力
step 3: sendLinkProperties() - 上报初始链路属性
step 4: sendNetworkScore() - 上报初始评分
step 5: ... (多次更新能力/属性/评分)
step 6: markConnected() - ✓ 现在完全就绪
标记为已连接后会发生什么: ├─ 应用可以通过 NetworkRequest 请求该网络 ├─ 应用收到 onAvailable() 回调 ├─ 网络进入验证流程(如需要) ├─ 网络成为候选的默认网络 └─ 可以处理应用流量
【应用回调的类型与顺序】 ═════════════════════════════════════════════════════════════
假设应用注册了 NetworkCallback:
ConnectivityManager.NetworkCallback callback =
new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// ← 当网络可用时调用
// 此时网络已连接
}
@Override
public void onCapabilitiesChanged(Network network,
NetworkCapabilities caps) {
// ← 当网络能力改变时调用
// 例如:获得验证、失去验证等
}
@Override
public void onLinkPropertiesChanged(Network network,
LinkProperties lp) {
// ← 当链路属性改变时调用
// 例如:获得 IPv6 地址
}
@Override
public void onLosing(Network network, int maxMsToLive) {
// ← 网络即将丧失
// Linger 状态,应用应准备切换
}
@Override
public void onLost(Network network) {
// ← 网络已完全断开
// 不再可用
}
};
// 注册回调
ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
cm.registerNetworkCallback(request, callback);
典型的回调序列(WiFi 连接过程):
T=0: 用户选择 WiFi SSID 并输入密码
└─ WiFiManager 开始连接
T=1s: WiFi 连接建立,正在获取 IP
└─ NetworkAgent: sendLinkProperties()
└─ App: onLinkPropertiesChanged() [仅 IP 地址]
T=2s: IP 获取完成,标记为连接
└─ NetworkAgent: markConnected()
└─ ConnectivityService 创建网络
└─ App: onAvailable(network) ✓ 网络可用!
T=3s: IPv6 自动配置完成
└─ NetworkAgent: sendNetworkCapabilities() [添加 IPv6]
└─ App: onCapabilitiesChanged() [新增 IPv6]
T=4s: NetworkMonitor 验证互联网连接
└─ 发送 HTTP 探测...
T=5s: 验证成功
└─ NetworkAgent: sendNetworkCapabilities() [添加 VALIDATED]
└─ App: onCapabilitiesChanged() [获得 VALIDATED 能力]
└─ 网络现在是"高质量"的
T=100s: 用户走出 WiFi 范围,信号变弱
└─ NetworkAgent: sendNetworkScore() [评分下降]
└─ ConnectivityService 重新评估
└─ 如果 LTE 评分更高 → 触发网络切换
T=101s: LTE 成为新的默认网络
└─ App: onAvailable(lte_network)
└─ App: onLosing(wifi_network, 120000) [Linger 120 秒]
└─ WiFi 暂时保活(可能马上重连)
T=120s: Linger 期满,WiFi 断开
└─ App: onLost(wifi_network)
现在进入第 3 部分:
### **3、NetworkFactory 工作原理**
【NetworkFactory 的三大核心职责】 ═════════════════════════════════════════════════════════════
NetworkFactory 是一个工厂类,由网络提供者(如 WifiManager、TelephonyManager) 创建和持有,用于管理该提供者能否满足特定的网络请求。
┌────────────────────────────────────────┐ │ NetworkFactory 职责 │ ├────────────────────────────────────────┤ │ 1. 评估请求 │ │ ├─ 该工厂能否满足此请求? │ │ ├─ 评分是多少? │ │ └─ 依据:能力、信号强度等 │ │ │ │ 2. 管理网络生命周期 │ │ ├─ 接收请求时启动网络 │ │ ├─ 所有请求移除时关闭网络 │ │ └─ 创建 NetworkAgent │ │ │ │ 3. 动态调整评分 │ │ ├─ 信号强度变化 → 评分变化 │ │ ├─ 重新评估所有请求 │ │ └─ 影响网络选择 │ └────────────────────────────────────────┘
【TelephonyNetworkFactory 的实现】 ═════════════════════════════════════════════════════════════
蜂窝网络工厂的核心逻辑:
public class TelephonyNetworkFactory extends NetworkFactory {
// 工厂维持的所有网络请求
private Map<NetworkRequest, Integer> mNetworkRequests = new HashMap<>();
// 当前 Phone 对象(可能有多个,代表多张 SIM 卡)
private Phone mPhone;
// 多卡管理器
private PhoneSwitcher mPhoneSwitcher;
/**
* Called by ConnectivityService when a new NetworkRequest is received
* that matches this factory's filter.
*/
@Override
protected void needNetworkFor(NetworkRequest request) {
// 步骤 1: 检查是否已有相同的请求
if (mNetworkRequests.containsKey(request)) {
return; // 已有相同请求,无需重复
}
// 步骤 2: 评估是否可以满足
// 例如:检查信号强度、是否在 Airplane Mode 等
if (!canSatisfyRequest(request)) {
return; // 无法满足,拒绝
}
// 步骤 3: 如果这是第一个请求,启动数据连接
boolean isFirstRequest = mNetworkRequests.isEmpty();
// 步骤 4: 记录请求
mNetworkRequests.put(request, TRANSPORT_CELLULAR);
if (isFirstRequest) {
// 这是第一个请求,启动数据连接
// 例如建立 PDP Context
startDataConnection(request);
} else {
// 已经有活跃的数据连接
// 新请求可以立即使用
}
}
/**
* Called by ConnectivityService when a NetworkRequest is withdrawn
*/
@Override
protected void releaseNetworkFor(NetworkRequest request) {
// 步骤 1: 移除请求
mNetworkRequests.remove(request);
// 步骤 2: 如果没有更多请求,关闭数据连接
if (mNetworkRequests.isEmpty()) {
stopDataConnection(); // 断开 PDP Context
}
}
/**
* 开始数据连接
*/
private void startDataConnection(NetworkRequest request) {
// 与 DataNetworkController 通信
mDataNetworkController.requestDataConnection(
request.getApnType(), // 例如:INTERNET, IMS, MMS
new DataConnectionCallback() {
@Override
public void onConnected(DataNetwork network, LinkProperties lp) {
// 网络已连接
// 创建 TelephonyNetworkAgent 向系统注册
TelephonyNetworkAgent agent = new TelephonyNetworkAgent(
mPhone,
mLooper,
network, // DataNetwork 对象
score, // 初始评分(基于信号强度)
config,
this // provider (TelephonyNetworkFactory)
);
// agent.register() 在构造函数中调用
}
@Override
public void onDisconnected() {
// 网络已断开
// agent 将调用 unregister()
}
}
);
}
private void stopDataConnection() {
// 通知 DataNetworkController 关闭所有 PDP Context
mDataNetworkController.releaseDataConnection();
}
}
关键特点: ├─ 一个 Factory 可能同时满足多个 NetworkRequest ├─ 但底层可能只有一个数据连接(PDP Context) ├─ 所有请求共享同一个 NetworkAgent ├─ 当最后一个请求移除时才断开连接
【WiFi NetworkFactory 的实现】 ═════════════════════════════════════════════════════════════
WiFi 工厂的特点:一个 WiFi 连接一个 NetworkAgent
public class WifiNetworkFactory extends NetworkFactory {
// WiFi 提供的网络 Agent(通常只有一个)
private WifiNetworkAgent mWifiAgent = null;
// WiFi 管理器
private WifiManager mWifiManager;
@Override
protected void needNetworkFor(NetworkRequest request) {
// WiFi 工厂的逻辑相对简单
// step 1: 如果已经有 WiFi 连接
if (mWifiAgent != null && mWifiAgent.isConnected()) {
// WiFi 网络已连接,可以满足请求
// (无需额外操作,该网络已注册)
return;
}
// step 2: 如果没有 WiFi,可能启动扫描
// 或者如果之前已连接但暂时断开,自动重连
if (mWifiManager != null) {
// WiFi 在之前已配置,自动重连
mWifiManager.reconnect();
}
}
@Override
protected void releaseNetworkFor(NetworkRequest request) {
// WiFi 不会因为一个请求的移除就断开
// 用户可能还在浏览网页,即使应用没有特定的网络请求
// WiFi 应该保活
// 实际上,WiFi 通常不实现这个方法
// 或者直接 return(无操作)
}
/**
* 当 WiFi 连接成功时,由 WifiMonitor 或 WifiManager 调用
*/
public void onWifiConnected(String ssid, int rssi) {
// 创建新的 WifiNetworkAgent
mWifiAgent = new WifiNetworkAgent(
mContext,
mLooper,
ssid,
new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_WIFI)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_METERED) // 通常不计量
.setSignalStrength(rssiToSignalLevel(rssi))
.build(),
linkProperties,
networkScore,
config,
provider
);
mWifiAgent.register(); // 注册到系统
}
/**
* 当 WiFi 断开连接时
*/
public void onWifiDisconnected() {
if (mWifiAgent != null) {
mWifiAgent.unregister();
mWifiAgent = null;
}
}
}
WiFi 和移动网络的重要区别: ├─ WiFi: 一个连接一个 Agent(多个应用共享) ├─ 移动网络: 可能有多个 PDP Context(不同 APN) └─ 关键:评分决定使用哪个网络
【网络请求的匹配算法】 ═════════════════════════════════════════════════════════════
ConnectivityService 如何决定哪个工厂来满足请求:
private void rematchAllNetworksAndRequests() {
// 步骤 1: 获取所有网络,按评分排序
List<NetworkAgentInfo> networks = sortByScore(mNetworkAgents);
// 步骤 2: 对于每个网络,检查所有未分配的请求
for (NetworkAgentInfo network : networks) {
for (NetworkRequestInfo request : mNetworkRequests) {
if (request.isAlreadyAssigned()) {
continue; // 已分配给其他网络
}
// 检查网络是否满足请求
if (network.networkCapabilities.canBeSatisfiedBy(
request.networkRequest.getCapabilities())) {
// 分配该网络给该请求
assignNetworkToRequest(network, request);
}
}
}
}
具体的匹配逻辑:
private boolean canNetworkSatisfyRequest(
NetworkAgentInfo network, NetworkRequest request) {
// 检查 1: 能力匹配
// 请求的所有 REQUIRED 能力必须存在
for (int capability : request.getCapabilities()) {
if (!network.networkCapabilities.hasCapability(capability)) {
return false; // 网络缺少必要的能力
}
}
// 检查 2: UNWANTED 能力
// 请求要求的任何 UNWANTED 能力都不能存在
for (int unwanted : request.getUnwantedCapabilities()) {
if (network.networkCapabilities.hasCapability(unwanted)) {
return false; // 网络有不需要的能力
}
}
// 检查 3: 传输类型
// 如果请求指定了传输类型,必须匹配
if (request.hasTransport()) {
if (!network.networkCapabilities.hasTransport(request.getTransport())) {
return false; // 传输类型不匹配
}
}
// 检查 4: 特殊的企业需求(如果有)
// 企业 VPN, 特定运营商等
return true; // 网络可以满足请求
}
请求分配的优先级:
优先级 1: 已验证的网络 (NET_CAPABILITY_VALIDATED)
├─ 分配给 INTERNET 请求
├─ 评分越高越优先
└─ 通常是 WiFi 或强信号的 LTE
优先级 2: 未验证但连接的网络
├─ 可以暂时满足请求
├─ 但应用可能检测到无互联网
└─ 用于某些特殊应用(本地网络)
优先级 3: VPN 网络
├─ 评分高于底层网络
├─ 流量被 VPN 加密
└─ 所有互联网请求被转向 VPN
优先级 4: 其他特殊网络
├─ 蓝牙网络
├─ 以太网
└─ 本地网络(不需要互联网)
让我继续讲解第 4 部分:
### **4、网络状态管理与 Linger 机制**
【9 种关键的网络状态】 ═════════════════════════════════════════════════════════════
Android 网络栈中,一个网络可以处于以下几种状态之一:
1️⃣ IDLE / UNREGISTERED(空闲/未注册) ─────────────────────────────────────
定义: ├─ 网络还不存在或尚未被系统感知 ├─ NetworkAgent 尚未创建或已销毁 └─ 典型情况:WiFi 未连接、移动网络无信号
持续时间: ├─ 最长(直到用户连接或设备移出信号范围) └─ 无确定的终止时间
转换到: └─ → REGISTERING / CONNECTING
2️⃣ REGISTERING(注册中) ──────────────────────
定义: ├─ NetworkAgent 已创建但尚未向 ConnectivityService 注册 ├─ 或刚注册但初始状态还是 DISCONNECTED └─ 网络还不可用
示例流程:
// WiFi 或移动网络刚发现,创建 Agent
WifiNetworkAgent agent = new WifiNetworkAgent(...);
// 此时网络是 REGISTERING
// Agent 向系统注册
agent.register();
// 现在在 ConnectivityService 中记录,但状态仍是 DISCONNECTED
持续时间: ├─ 非常短(通常 < 100ms) └─ 直到 Agent 构造完成并调用 register()
转换到: └─ → CONNECTING 或 DISCONNECTED
3️⃣ CONNECTING(连接中) ────────────────────
定义: ├─ NetworkAgent 已注册,正在建立连接 ├─ 底层驱动(WiFi Driver、RIL)正在工作 ├─ 可能在身份验证、获取 IP 等阶段 └─ 网络还不能接收流量
典型的 CONNECTING 子阶段:
CONNECTING
├─ SCANNING(扫描可用网络)
├─ AUTHENTICATING(验证身份/密码)
├─ OBTAINING_IPADDR(通过 DHCP 获取 IP)
├─ VERIFYING_POOR_LINK(检查连接质量)
└─ CAPTIVE_PORTAL_CHECK(检查强制门户)
示例:
用户选择 WiFi SSID 并输入密码
↓
WifiManager.connect()
↓
WifiNetworkAgent: sendNetworkInfo(CONNECTING)
↓
ConnectivityService: 网络状态变为 CONNECTING
↓
应用看到网络在 CONNECTING(onAvailable 还未调用)
↓
经过数秒...
↓
WiFi Driver 获得 IP 地址
↓
WifiNetworkAgent: markConnected()
↓
状态转为 CONNECTED
持续时间: ├─ WiFi: 1-5 秒(通常) ├─ 移动网络: 3-10 秒(取决于信号) └─ 恶劣条件:可能超过 30 秒
转换到: ├─ → CONNECTED(连接成功) └─ → DISCONNECTED(连接失败)
4️⃣ CONNECTED(已连接) ──────────────────────
定义: ├─ 网络完全连接,L3 连通性已建立 ├─ IP 地址、网关、DNS 配置完成 ├─ 应用可以通过 NetworkRequest 使用该网络 ├─ 网络可以接收和发送数据包
这是最重要的一个状态,分为两个子阶段:
4a. CONNECTED (UNVALIDATED) ├─ 网络已连接但验证状态未知 ├─ 可能无法访问互联网(例如,需要登录门户) ├─ NetworkMonitor 可能正在验证 └─ 应用应该预期验证失败
4b. CONNECTED (VALIDATED) ├─ 网络已连接且验证通过 ├─ 有真实的互联网连接 ├─ 应用可以安全使用该网络 └─ 网络成为主要网络的候选
示例时间线:
T=0s: 网络 CONNECTED(未验证)
└─ NetworkMonitor 启动验证
T=2-5s: 验证在进行中
└─ 发送 HTTP 探测、DNS 查询等
T=5s: 验证完成
├─ 情况 A: 验证成功 → VALIDATED
├─ 情况 B: 检测到强制门户 → CAPTIVE_PORTAL
└─ 情况 C: 网络离线 → UNVALIDATED
持续时间: ├─ 可以持续数秒到数小时 ├─ 直到网络断开或被替换 └─ 或者从 VALIDATED 降级到 UNVALIDATED(连接变差)
转换到: ├─ → LOSING(被更好的网络替换) ├─ → SUSPENDED(临时暂停,例如飞行模式) └─ → DISCONNECTED(网络故障或用户断开)
5️⃣ LOSING(即将丧失)⭐ Linger 状态 ───────────────────────────────────
定义: ├─ 网络即将被替换为更好的网络 ├─ 但仍保活一段时间(Linger 时间) ├─ 应用有机会平滑切换 ├─ 典型场景:WiFi 变弱,切换到 LTE,但 WiFi 还活着
何时进入 LOSING:
ConnectivityService 发现:
├─ 新网络比当前网络评分高
├─ 新网络也能满足相同的请求
├─ 设置 Linger 计时器(通常 120 秒)
└─ 告知应用:onLosing(network, maxMsToLive)
示例:
T=0s: 用户走出 WiFi 范围,WiFi 信号变弱
└─ WiFiNetworkAgent 降低评分
T=1s: LTE 信号强于 WiFi
└─ ConnectivityService 重新评估
└─ 决定:LTE 是新的默认网络
T=2s: WiFi 进入 LOSING 状态
└─ 调用所有监听者的 onLosing()
└─ 设置 120 秒 Linger 计时器
T=2-120s: WiFi 仍可用
├─ 已连接的 Socket 可继续使用
├─ 新的 Socket 默认使用 LTE
├─ 应用如果愿意可以继续用 WiFi
└─ onLosing() 告诉应用:还有 120 秒!
T=122s: Linger 时间到期
└─ WiFi 进入 DISCONNECTED
└─ 调用所有监听者的 onLost()
Linger 的设置:
// 默认 Linger 时间(秒)
private static final int DEFAULT_LINGER_DELAY_MS = 120 * 1000; // 120 秒
// 不同网络类型的 Linger 时间
private static final int WIFI_LINGER_TIME_MS = 120 * 1000; // WiFi: 120 秒
private static final int CELLULAR_LINGER_TIME_MS = 60 * 1000; // LTE: 60 秒
private static final int VPN_LINGER_TIME_MS = 30 * 1000; // VPN: 30 秒
Linger 的用途: ├─ 给应用时间进行平滑过渡 ├─ 避免频繁的网络切换 ├─ 保护旧网络上已建立的连接 ├─ 减少用户感知的中断
Linger 的缺点: ├─ 延迟了网络释放 ├─ 消耗电池(网络芯片保持活跃) ├─ 浪费流量(同时使用两个网络) └─ 某些 OEM 减短或关闭 Linger
6️⃣ SUSPENDED(暂停) ──────────────────
定义: ├─ 网络仍然连接,但临时不可用 ├─ 例如:飞行模式打开、设备暂停 ├─ 应用看不到该网络
触发条件:
飞行模式启用
→ PhoneStateListener.onServiceStateChanged()
→ Telephony 网络 SUSPENDED
屏幕关闭 + 某些 App 要求
→ DeviceIdleManager 发送 IDLE 信号
→ 非必要网络 SUSPENDED
VPN 连接
→ 底层网络 SUSPENDED
→ VPN 网络成为唯一的 INTERNET 网络
示例:
T=0s: WiFi 正常连接并验证
└─ 网络: CONNECTED, VALIDATED
T=5s: 用户启用飞行模式
└─ WifiManager.setAirplaneModeEnabled(true)
└─ 所有网络状态: SUSPENDED
└─ onLost() 回调(应用看不到网络)
T=10s: 用户关闭飞行模式
└─ WiFi 恢复连接
└─ 网络状态回到 CONNECTED
└─ onAvailable() 回调
持续时间: ├─ 可以是秒级(临时暂停) ├─ 也可以是长时间(用户不改变设置) └─ 无自动清除,需要用户或系统事件触发恢复
转换到: ├─ → CONNECTED(恢复可用) └─ → DISCONNECTED(永久断开)
7️⃣ DISCONNECTING(断开中) ────────────────────────
定义: ├─ 网络正在优雅断开连接 ├─ 底层驱动正在进行清理(例如注销会话) ├─ 应用应该停止使用该网络 ├─ 这个状态通常很短
示例:
用户按"断开连接"按钮
↓
WifiManager.disconnect()
↓
WiFi Driver 发送断开帧到 AP
↓
NetworkAgent: sendNetworkInfo(DISCONNECTING)
↓
应用收到 onLosing() 和后续的 onLost()
↓
(数百毫秒后)
↓
Driver 确认断开完成
↓
NetworkAgent: unregister()
↓
状态变为 DISCONNECTED
持续时间: ├─ 通常 < 1 秒 ├─ WiFi: 100-500ms ├─ 移动网络: 500ms - 2 秒 └─ 若驱动有问题可能更长
转换到: └─ → DISCONNECTED(断开完成)
8️⃣ DISCONNECTED(已断开) ──────────────────────
定义: ├─ 网络已完全断开 ├─ NetworkAgent 已 unregister() ├─ 应用无法使用该网络 ├─ 这是网络生命周期的终点
可能的原因: ├─ 用户手动断开 ├─ 网络不可用(信号丧失、交换机掉线等) ├─ 设备进入飞行模式 ├─ 系统关闭 ├─ NetworkFactory 决定释放网络
示例:
WiFi 网络从 CONNECTED 到 DISCONNECTED 的完整生命周期:
T=0s: 用户连接到 WiFi
├─ Agent 创建: REGISTERING
├─ Agent 注册: CONNECTING
├─ 获取 IP: CONNECTING
└─ 标记连接: CONNECTED
T=5s: 用户走出范围,信号丧失
└─ WiFi Driver 检测到
└─ NetworkAgent: unregister()
└─ 状态: DISCONNECTED
T=5.1s: onLost() 回调给应用
└─ 应用停止使用该网络
持续时间: ├─ DISCONNECTED 没有"持续时间" ├─ 它是终点,不会自动转变 └─ 需要新的网络连接事件才能回到活跃状态
从 DISCONNECTED 到: └─ → REGISTERING(新网络连接)
9️⃣ UNREGISTERED (最初始状态) ───────────────────────────
定义: ├─ 网络从未被系统知道 ├─ NetworkAgent 从未创建 ├─ 等同于 IDLE,但从系统的角度 └─ 这是网络的预初始化状态
转换到: └─ → REGISTERING(网络被发现和创建)
【状态转换的完整图表】 ═════════════════════════════════════════════════════════════
┌──────────────┐
│ UNREGISTERED │
│ (初始) │
└───────┬──────┘
│ 网络被发现
▼
┌──────────────┐
│ REGISTERING │
│ (注册中) │
└───────┬──────┘
│ register()
▼
┌──────────────┐
│ CONNECTING │ ← WiFi 扫描、身份验证、DHCP
│ (连接中) │ 通常持续 1-10 秒
└──────┬───────┘
│
┌──────┴───────┐
│ │ 连接失败或超时
│ markConnected()
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ CONNECTED │ │ DISCONNECTED │
│ (已连接) │ │ (已断开) │
└──────┬───────┘ └──────────────┘
│
┌───────┴───────┐
│ │
┌──────▼────────┐ │ 被替换
│ VALIDATED │ │ (Linger)
│ (已验证) │ │
└──────┬────────┘ ▼
│ ┌──────────────┐
│ │ LOSING │
│ │ (即将丧失) │ <- Linger 120 秒
│ └────┬─────────┘
│ │ Linger 期满
│ ▼
│ ┌──────────────┐
└────────►│ DISCONNECTED │
│ (已断开) │
└──────────────┘
其他可能:
任何状态 ──飞行模式──► SUSPENDED
SUSPENDED ────────► 恢复网络所有状态
【Linger 机制的详细工作流程】 ═════════════════════════════════════════════════════════════
Linger 是 Android 网络栈中最重要的平滑过渡机制。
Step 1: 触发条件 - 更好的网络出现
─────────────────────────────────
当前状态:
├─ 默认网络: WiFi (评分: 75, 已验证)
├─ 待机网络: LTE (评分: 50, 已验证)
└─ 活跃应用: 浏览器,通过 WiFi 下载文件
网络评分变化:
├─ WiFi 信号变弱: -75dbm → -85dbm
├─ WiFi 评分下降: 75 → 45
├─ LTE 评分: 50 (保持不变)
└─ 新的评分: LTE (50) > WiFi (45)
ConnectivityService 的决策:
├─ 调用 rematchAllNetworksAndRequests()
├─ 发现 LTE 现在应该是默认网络
├─ 但 WiFi 仍然满足所有活跃请求
└─ 决定:进入 LOSING (Linger)
Step 2: 进入 LOSING 状态
─────────────────────
ConnectivityService 的操作:
```java
private void handleNetworkLosing(NetworkAgentInfo nai) {
if (nai.isLosing()) {
return; // 已经在 LOSING 状态
}
// 标记网络为 LOSING
nai.setLosing();
// 设置 Linger 计时器
int lingerMs = getLingerTimeoutMs(nai.networkType);
// lingerMs = 120000ms (默认 120 秒)
mHandler.sendMessageDelayed(
mHandler.obtainMessage(EVENT_LINGER_TIMEOUT, nai),
lingerMs
);
// 通知所有监听者:网络即将丧失
notifyNetworkCallbacks(nai,
ConnectivityManager.CALLBACK_LOSING,
lingerMs); // 告诉应用还有多少时间
}
应用收到的回调:
callback.onLosing(wifiNetwork, 120000); // 还有 120 秒!
应用的典型反应:
@Override
public void onLosing(Network network, int maxMsToLive) {
// 应用知道 WiFi 即将不可用
// 有两种选择:
// 选项 1: 立即停止使用 WiFi
// 主动切换到 LTE
// 优点:不会中断
// 缺点:可能不会有 WiFi 的重新连接机会
// 选项 2: 继续使用 WiFi
// 希望 Linger 时间内 WiFi 信号恢复
// 优点:如果信号恢复就能继续用 WiFi
// 缺点:如果不恢复会被强制切换,导致中断
}
Step 3: Linger 期间 - 并行网络 ─────────────────────────
Linger 时间内发生的事情:
时间: 0 - 120 秒
应用的网络使用:
├─ 已建立的 Socket/连接: 继续用 WiFi(不中断)
├─ 新的网络请求: 使用 LTE(默认网络)
└─ 显式使用 WiFi 的应用: 仍然可用
系统的操作:
├─ WiFi 评分继续变化
│ ├─ 如果信号改善: 回到 CONNECTED(取消 Linger)
│ └─ 如果信号继续变差: 保持 LOSING
├─ LTE 成为默认网络
├─ 新应用使用 LTE(自动)
└─ 旧应用仍在 WiFi 上(如果显式请求)
Step 4: WiFi 信号恢复 - Linger 取消
────────────────────────────────
在 Linger 期间,如果 WiFi 信号改善:
WiFiNetworkAgent:
├─ 检测到信号改善
├─ sendNetworkScore(newScore)
│ └─ 评分提高: 40 → 70
└─ ConnectivityService 收到更新
ConnectivityService:
```java
private void handleNetworkScoreChanged(NetworkAgentInfo nai) {
int newScore = nai.getNetworkScore();
if (nai.isLosing()) {
// 检查是否还应该 LOSING
if (newScore > currentDefaultNetworkScore) {
// 评分改善,取消 Linger
nai.clearLosing();
mHandler.removeMessages(EVENT_LINGER_TIMEOUT, nai);
// WiFi 回到 CONNECTED
// 并可能成为默认网络(如果评分足够高)
rematchAllNetworksAndRequests();
// 通知应用:不用担心了
notifyNetworkCallbacks(nai,
CALLBACK_AVAILABLE);
}
}
}
应用收到回调:
// WiFi 恢复,不再 LOSING
callback.onAvailable(wifiNetwork); // 或其他能力更新回调
Step 5: Linger 期满 - 网络断开 ───────────────────────────
如果 120 秒内 WiFi 评分没有恢复到足够高:
private void handleLingerTimeout(NetworkAgentInfo nai) {
if (!nai.isLosing()) {
return; // 已经恢复了
}
// Linger 期满,断开网络
nai.clearLosing();
// 标记为 DISCONNECTED
handleNetworkDisconnected(nai);
// 最终通知应用
notifyNetworkCallbacks(nai, CALLBACK_LOST);
}
应用最终收到:
callback.onLost(wifiNetwork); // WiFi 彻底断开
完整的时间线:
T=0s: WiFi 信号开始变弱
应用: 收到 onCapabilitiesChanged (评分变化,但仍 CONNECTED)
T=5s: WiFi 评分 < LTE 评分
ConnectivityService: 决定切换
应用: 收到 onLosing(wifiNetwork, 120000)
T=5-60s: 用户可能靠近 WiFi 路由器
WiFi: sendNetworkScore(改善)
应用: 继续使用旧连接的 WiFi
T=60s: WiFi 信号继续变差或未改善
ConnectivityService: 评分仍 < LTE
应用: 仍在 Linger, 仍可用
T=125s: Linger 期满
ConnectivityService: handleLingerTimeout()
WiFi: 强制 DISCONNECTED
应用: 收到 onLost(wifiNetwork)
已建立的 WiFi 连接: 中断 ❌
【Linger 的配置与优化】 ═════════════════════════════════════════════════════════════
系统属性控制 Linger 时间:
ro.config.ip_linger_ms=120000 # 120 秒(默认)
OEM 可以自定义:
ro.config.ip_linger_ms=60000 # 激进:60 秒(早点切换)
ro.config.ip_linger_ms=300000 # 保守:5 分钟(等待网络恢复)
ro.config.ip_linger_ms=0 # 禁用 Linger(立即切换)
Linger 的优缺点:
✓ 优点: ├─ 平滑过渡:应用无需感知网络变化 ├─ 自动恢复:如果信号改善,自动回到原网络 ├─ 灵活性:应用可选择何时切换 └─ 用户体验:减少中断和延迟
✗ 缺点: ├─ 电池消耗:多个网络同时活跃 ├─ 流量浪费:可能使用两个网络 ├─ 延迟切换:不是立即切换到最好的网络 └─ 复杂性:增加网络栈的复杂度
不同 OEM 的选择: ├─ Google Pixel: 120 秒(标准) ├─ Samsung: 60-120 秒(中等) ├─ 某些国运营商: 30 秒(激进) └─ 企业设备: 0 秒(禁用,强制最好网络)
继续讲解第 5-6 部分。
好的,现在开始讲解第 5-6 部分:
---
### **5、WiFi 与移动网络切换深度解析**
【NetworkRanker 的 3 层决策模型】 ═════════════════════════════════════════════════════════════
NetworkRanker 是决定"哪个网络最好"的核心引擎。它通过一系列过滤规则, 逐步排除不合格的网络,最终选出最佳网络。
Layer 1: 硬性条件(必须满足) ────────────────────────────
这一层过滤掉完全不符合条件的网络:
┌─────────────────────────────────────────────────────┐ │ 1. 请求完全满足 (Satisfy Request) │ │ └─ 网络必须有请求要求的所有能力 │ │ 例如:请求要求 INTERNET │ │ 网络必须有 NET_CAPABILITY_INTERNET │ │ │ │ 2. 不能是即将销毁的网络 │ │ └─ POLICY_IS_DESTROYED 的网络被排除 │ │ 这样新网络可以直接替换 │ └─────────────────────────────────────────────────────┘
代码:
// 步骤 1: 过滤出能满足请求的网络
List<NetworkAgentInfo> candidates =
networks.stream()
.filter(nai -> nai.satisfies(request))
.collect(toList());
if (candidates.isEmpty()) {
return null; // 没有网络满足要求
}
Layer 2: 优先级过滤(Prioritization Filters) ──────────────────────────────────────────
这一层使用一系列规则逐步缩小候选范围:
规则 1: 不败的网络(Invincible Networks)
if (any network has POLICY_IS_INVINCIBLE) {
return that network; // 直接返回,其他网络无论多好都不行
}
用途: ├─ OEM 特殊配置的网络 ├─ 企业 VPN(如配置为不败) └─ 某些特殊场景下的专网
规则 2: VPN 网络(VPN Policy)
if (any network is VPN (POLICY_IS_VPN)) {
return the VPN network; // VPN 通常优先于底层网络
}
原因: ├─ VPN 是用户明确选择的加密通道 ├─ 所有互联网流量都应该通过 VPN └─ VPN 的评分通常高于底层网络
规则 3: 用户选择的网络(User Selection Policy)
if (any network has both:
- POLICY_EVER_USER_SELECTED (用户主动连接过)
- POLICY_ACCEPT_UNVALIDATED (用户愿意接受未验证)
) {
prefer that network;
}
用途: ├─ 用户喜欢用某个特定的 WiFi ├─ 即使有验证失败,也应该优先 └─ 例如:家里的 WiFi,user 明确选择
规则 4: 验证状态(Validation Policy)
if (any network is VALIDATED or POLICY_ACCEPT_UNVALIDATED) {
exclude non-validated networks;
}
逻辑: ├─ 有效的网络(已验证互联网连接)总是优于无效网络 ├─ 或者如果有网络的用户接受未验证,就排除所有未验证网络 └─ 关键的决策点
规则 5: WiFi 与蜂窝的选择(Yield to Bad WiFi Policy)
if (some networks have POLICY_YIELD_TO_BAD_WIFI) {
// 这个规则用来处理"坏 WiFi vs 好蜂窝"的困境
if (POLICY_YIELD_TO_BAD_WIFI && there are bad WiFis) {
prefer the bad WiFi; // 偏好坏 WiFi
} else {
prefer the LTE; // 否则用 LTE
}
}
这是最复杂的规则!详见下文。
规则 6: 非退出网络(Non-Exiting Policy)
if (any network doesn't have POLICY_EXITING) {
exclude networks with POLICY_EXITING; // Linger 阶段的网络
}
用途: ├─ 优先选择不在 Linger 的网络(即将消失) ├─ 避免选择一个即将断开的网络
规则 7: 主要传输方式(Primary Transport Policy)
for each SIM slot {
if (there's a network for primary subscription) {
prefer it over networks for secondary subscriptions;
}
}
用途: ├─ 多卡设备,通常有一张主卡(Primary SIM) ├─ 蜂窝网络优先使用主卡的信号 └─ 次卡的网络作为备选
规则 8: 传输优先级(Transport Priority)
// 优先级顺序:以太网 > WiFi > 蓝牙 > 蜂窝
for (transport in [ETHERNET, WIFI, BLUETOOTH, CELLULAR]) {
if (any network has this transport) {
prefer this transport over lower ones;
}
}
例子:
候选网络:
├─ LTE (评分 80, 已验证)
├─ WiFi (评分 60, 已验证)
└─ 以太网 (评分 30, 已验证)
按规则 8 评估:
├─ 有以太网?→ 选择以太网(即使评分最低!)
规则 9: 稳定性倾向(Stickiness)
if (currentSatisfier is still in candidates after all rules) {
keep it; // "粘性":倾向于保持当前网络
}
用途: ├─ 避免频繁切换(抖动) ├─ 减少用户感知到的网络切换 └─ 如果当前网络足够好,就别换
Layer 3: 评分比较(Score Comparison) ──────────────────────────────────────
如果经过所有过滤规则后仍有多个候选,则按评分排序:
int scoreDiff = networkA.getScore() - networkB.getScore();
if (scoreDiff > 0) return networkA; // A 评分更高
else return networkB;
评分的计算(FullScore):
class FullScore {
// WiFi 评分(0-100,基于 RSSI)
// RSSI -40dbm: 95 分(最强)
// RSSI -70dbm: 60 分(中等)
// RSSI -85dbm: 25 分(弱)
// RSSI < -90dbm: 0 分(断线)
// LTE 评分(0-100,基于信号强度)
// RSRP -75dbm: 90 分(最强)
// RSRP -100dbm: 60 分(中等)
// RSRP -120dbm: 20 分(弱)
// 验证奖励:+20 分
// Linger 惩罚:-30 分
// VPN 奖励:+50 分
}
【Yield to Bad WiFi 规则详解】⭐ 最关键的 WiFi vs LTE 决策 ═════════════════════════════════════════════════════════════
这个规则解决的问题:
用户有两个网络可用:
├─ WiFi: -85dbm,未验证或强制门户("坏" WiFi)
└─ LTE: -95dbm,已验证("好" LTE)
问题:
├─ 评分上,LTE 可能更高
├─ 但用户已连接到 WiFi(可能是预期的)
├─ 系统应该保留 WiFi(即使坏)还是立即切换到 LTE?
POLICY_YIELD_TO_BAD_WIFI 是一个标志,表示:
"如果有坏但已连接的 WiFi,优先使用它,而不是切换到更好的蜂窝网络"
何时设置:
1. WiFi 已连接但验证失败(可能是强制门户)
├─ 用户可能想通过门户认证
├─ 此时不应立即切换到 LTE
└─ 设置 YIELD_TO_BAD_WIFI
2. WiFi 信号变弱但仍连接
├─ 用户可能走近路由器时 WiFi 会恢复
├─ 不应立即切换到 LTE
└─ 设置 YIELD_TO_BAD_WIFI
3. WiFi 虽然未验证但能上网(例如某些特殊场景)
├─ 可能是代理、特殊配置等
└─ 优先保留 WiFi
算法(isPreferredBadWiFi):
private boolean isPreferredBadWiFi(NetworkScore score, NetworkCapabilities caps) {
// 条件 1: 必须是 WiFi
if (!caps.hasTransport(TRANSPORT_WIFI)) return false;
// 条件 2: 不能是已验证的网络(已验证就不是"坏" WiFi)
if (score.hasPolicy(POLICY_IS_VALIDATED)) return false;
// 条件 3: 不能是用户主动避免的(在 UI 中取消勾选)
if (score.hasPolicy(POLICY_AVOIDED_WHEN_UNVALIDATED)) return false;
// 条件 4: 必须曾经评估过(在连接评估阶段)
if (!score.hasPolicy(POLICY_EVER_EVALUATED)) return false;
// 条件 5: 不能是活跃强制门户
// (但如果曾经验证过,即使现在是门户也可以)
if (caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
// 如果曾经验证过,说明用户之前登录过
// 现在门户可能由于某些原因重新出现
// 仍然应该优先使用 WiFi
if (!score.hasPolicy(POLICY_EVER_VALIDATED)) {
return false; // 从未验证过的门户,不优先
}
}
return true; // 这是一个"优先的坏 WiFi"
}
// 在网络选择中使用
applyYieldToBadWifiPolicy(validated, unvalidated) {
if (any network has POLICY_YIELD_TO_BAD_WIFI) {
List<BadWiFi> badWiFis = unvalidated.stream()
.filter(nai -> isPreferredBadWiFi(nai.getScore()))
.collect(toList());
if (!badWiFis.isEmpty()) {
// 有坏但优先的 WiFi
// 移除所有有 YIELD_TO_BAD_WIFI 的非 WiFi 网络
validated.removeAll(networks_with_YIELD_TO_BAD_WIFI);
// 保留坏 WiFi
}
}
}
典型场景的决策流程:
场景 A: 用户在开放 WiFi(如机场)
WiFi 状态:连接,存在强制门户,未验证
LTE 状态:连接,已验证
isPreferredBadWiFi(WiFi)?
├─ 是 WiFi? ✓ YES
├─ 已验证? ✗ NO (未验证)
├─ 被用户避免? ✗ NO
├─ 曾评估过? ✓ YES
├─ 是强制门户? ✓ YES,但曾验证? ✗ NO
└─ → return FALSE (不优先)
决策:选择 LTE(已验证)❌
问题:用户可能想登录 WiFi!
场景 B: 用户在家里 WiFi(已登录过的门户)
WiFi 状态:连接,强制门户,未验证,但曾验证
LTE 状态:连接,已验证
isPreferredBadWiFi(WiFi)?
├─ 是 WiFi? ✓ YES
├─ 已验证? ✗ NO (现在未验证)
├─ 被用户避免? ✗ NO
├─ 曾评估过? ✓ YES
├─ 是强制门户? ✓ YES,但曾验证? ✓ YES
└─ → return TRUE (优先!)
决策:选择 WiFi(坏但优先)✓
原因:用户之前登录过,现在只是门户重新出现,应该保留 WiFi
场景 C: WiFi 信号弱
WiFi 状态:RSSI -88dbm,评分 25,已验证
LTE 状态:RSSI -95dbm,评分 70,已验证
isPreferredBadWiFi(WiFi)?
├─ 是 WiFi? ✓ YES
├─ 已验证? ✓ YES
└─ → return FALSE (已验证不算"坏" WiFi)
评分比较:
└─ LTE (70) > WiFi (25)
决策:选择 LTE ✓
原因:WiFi 虽然验证过,但评分太低,LTE 更好
【网络切换的完整时间线】 ═════════════════════════════════════════════════════════════
实际网络切换的发生过程:
T=0: 用户在 WiFi 覆盖范围内
├─ WiFi 连接,已验证 (RSSI -40dbm, 评分 95)
├─ LTE 连接,已验证 (RSRP -90dbm, 评分 65)
└─ 默认网络:WiFi (95 > 65)
T=10s: 用户走出 WiFi 覆盖范围
└─ WiFi 信号开始衰减 -40 → -50 → -60 ...
T=15s: WiFi 信号继续衰减到 -85dbm
├─ WiFiNetworkAgent.onSignalStrengthChanged(-85)
├─ 新评分:25
├─ ConnectivityService.onNetworkScoreChanged()
└─ 触发 rematchAllNetworksAndRequests()
T=16s: NetworkRanker.getBestNetwork()
├─ 候选:WiFi (评分 25), LTE (评分 65)
├─ 规则 1-7:都通过 (都已验证,不在 Linger 等)
├─ 传输优先级:都不是以太网/蓝牙,继续
├─ 评分比较:LTE (65) > WiFi (25)
└─ 结论:LTE 是更好的网络
T=16.1s: 做出切换决定
└─ ConnectivityService: "WiFi 评分太低,应该切换到 LTE"
T=16.2s: 进入 Linger(如果 WiFi 有活跃请求)
├─ WiFi 状态变为 LOSING
├─ 设置 120 秒 Linger 计时器
├─ 调用 onLosing(wifiNetwork, 120000)
└─ LTE 成为新的默认网络
T=16.3s: 应用行为改变
├─ 新的网络请求 → LTE
├─ 现有 Socket → WiFi (仍在 Linger)
├─ ConnectivityManager.getActiveNetwork() → LTE
└─ 应用可能不感知(已在 Linger)
T=60s: WiFi 信号完全丧失
├─ WiFi 断开
├─ WiFi 状态变为 DISCONNECTED
└─ onLost() 回调
T=136s: 如果 WiFi 信号一直没有恢复,Linger 期满
└─ WiFi 被强制断开(如果仍在 Linger)
【切换的平滑度优化】 ═════════════════════════════════════════════════════════════
为了让用户无感知地切换网络,Android 采用了多种优化:
优化 1: Linger 机制 ───────────────── ├─ 给旧网络保活时间(120 秒) ├─ 已建立的连接继续用旧网络 ├─ 新连接自动用新网络 └─ 应用无需改变代码
优化 2: 连接池复用 ───────────────── HTTP: ├─ TCP 连接池绑定到网络 ├─ 新网络,新连接池 └─ 旧连接在旧网络上继续用
Socket: ├─ 使用 Network.getSocketFactory() 创建 ├─ 绑定到特定网络 └─ 网络断开时才会中断
优化 3: DNS 缓存 ────────────── ├─ 域名解析结果被缓存 ├─ 避免频繁重新解析 └─ 跨网络使用缓存结果
优化 4: 预连接 ───────────── ├─ 识别即将需要的资源 ├─ 在新网络上预先建立连接 └─ 用户切换时立即可用
优化 5: 信号预测 ─────────────── 基于历史数据: ├─ WiFi 通常在这个位置会断开 ├─ 这个时间段 LTE 信号较弱 ├─ 根据预测主动调整网络选择 └─ 减少网络卡顿
现在进入第 6 部分:
### **6、实战:网络问题诊断与优化**
【WiFi 连接失败的排查流程】 ═════════════════════════════════════════════════════════════
问题:用户无法连接到特定的 WiFi SSID
诊断步骤:
Step 1: 检查底层 WiFi 驱动 ─────────────────────────
命令:
adb logcat | grep -i "wifi"
关键日志信息:
WifiManager: [Network event callback]
WifiManager: State change: SCANNING
WifiManager: State change: CONNECTING
WifiManager: State change: AUTHENTICATING
WifiManager: State change: OBTAINING_IPADDR
WifiManager: State change: CONNECTED
WifiManager: State change: DISCONNECTED (reason=X)
常见的断开原因代码:
reason=1 : WIFI_REASON_UNSPECIFIED (未指定)
reason=2 : WIFI_REASON_WRONG_PASSWORD (密码错误)
reason=3 : WIFI_REASON_AUTH_FAILED (认证失败)
reason=4 : WIFI_REASON_NETWORK_UNAVAILABLE (网络不可用)
reason=5 : WIFI_REASON_DEAUTH_RECEIVED (收到 Deauth 帧)
reason=6 : WIFI_REASON_DISASSOC_RECEIVED (收到 Disassoc 帧)
reason=7 : WIFI_REASON_ASSOC_FAILED (关联失败)
reason=8 : WIFI_REASON_HAND_SHAKE_TIMEOUT (握手超时)
诊断:
如果日志显示 AUTHENTICATING 停留很久后跳到 DISCONNECTED
├─ 可能原因:密码错误、路由器不支持该加密方式
└─ 解决:检查密码、检查路由器配置
如果快速显示 DISCONNECTED,reason=5
├─ AP 拒绝连接(可能 MAC 过滤、安全问题)
└─ 联系 WiFi 管理员
Step 2: 检查 IP 配置 ──────────────────
命令:
adb shell ifconfig wlan0
输出示例:
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>
inet 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::xxxx:yyyy:zzzz:wwww prefixlen 64 scopeid 0x20<link>
...
诊断:
如果没有 inet 地址:
├─ DHCP 没有给 IP
├─ 可能路由器 DHCP 故障
└─ 或者 WiFi 驱动问题
如果有 inet 地址,可以继续
Step 3: 检查网络连接性 ────────────────────
命令:
adb shell ping -c 4 8.8.8.8
结果:
成功:
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=57 time=25.3 ms
64 bytes from 8.8.8.8: seq=1 ttl=57 time=24.8 ms
...
失败:
PING 8.8.8.8 (8.8.8.8): 56 data bytes
(no response)
诊断:
如果 ping 成功:
├─ IP 配置正常
├─ DNS 可能有问题(见下一步)
└─ 或者是应用问题
如果 ping 失败:
├─ 网络连接确实有问题
├─ 可能原因:
│ ├─ 路由器网络故障
│ ├─ ISP 断网
│ ├─ 防火墙阻止
│ └─ 路由器设置不当
Step 4: 检查 DNS 解析 ───────────────────
命令:
adb shell nslookup google.com
或
adb shell getprop net.dns1
adb shell getprop net.dns2
结果:
成功:
Server: 8.8.8.8
Address: 8.8.8.8#53
Name: google.com
Address: 142.251.41.14
失败:
nslookup: can't resolve google.com
诊断:
如果 DNS 失败:
├─ 路由器 DNS 故障
├─ 改用公共 DNS (8.8.8.8, 1.1.1.1)
└─ 检查路由器 DHCP 配置
如果 DNS 成功但浏览器不工作:
├─ 可能是代理问题
├─ 或者防火墙问题
└─ 或应用权限不足
Step 5: 检查 ConnectivityService 日志 ───────────────────────────────────
命令:
adb logcat -b system | grep -i "connectivity"
关键日志:
ConnectivityService: updateNetworkInfo()
ConnectivityService: handleNetworkInfoChange()
ConnectivityService: maybeNotifyNetworkCallbacks()
// 网络状态变化
NetworkInfo: State=CONNECTING -> State=CONNECTED
NetworkInfo: DetailedState=AUTHENTICATING -> DetailedState=CONNECTED
// 能力变化
NetworkCapabilities: INTERNET, NOT_METERED, NOT_ROAMING, VALIDATED
// 评分
Network score: 75
诊断:
如果看到 CONNECTED 但没有 VALIDATED:
├─ NetworkMonitor 验证失败
├─ 可能是强制门户(需要登录)
├─ 或网络确实无互联网
└─ 查看下一步的 NetworkMonitor 日志
如果没有看到网络创建(netId):
├─ 网络没有被正确注册
├─ 可能 NetworkAgent 有问题
└─ 检查 WiFi 驱动或 WifiService
Step 6: 检查 NetworkMonitor 验证状态 ─────────────────────────────────
命令:
adb logcat | grep -i "NetworkMonitor"
关键日志:
NetworkMonitor: Probing network...
NetworkMonitor: GET http://www.google.com/generate_204
NetworkMonitor: Response code: 204
NetworkMonitor: Network validation successful
或
NetworkMonitor: GET http://...
NetworkMonitor: Captive portal detected
NetworkMonitor: Response code: 302 (redirect)
或
NetworkMonitor: Probe failed
NetworkMonitor: DNS timeout
诊断:
如果看到 "validation successful":
├─ 网络已验证,应该可以用
├─ 如果应用还是不能联网,可能是应用权限问题
如果看到 "Captive portal detected":
├─ 需要登录 WiFi
├─ 系统会弹出 portal 登录窗口
└─ 用户需要在浏览器中认证
如果看到 "Probe failed":
├─ 验证失败,网络不可用
├─ 原因:没有 IPv4, IPv6, 或 DNS 问题
└─ 检查网络配置
Step 7: 检查应用权限 ──────────────────
命令:
adb shell cmd appops get com.your.app INTERNET
结果:
Allow -> 有权限
Deny -> 无权限
Default -> 跟随系统设置
如果无权限:
adb shell cmd appops set com.your.app INTERNET allow
其他需要的权限:
ACCESS_NETWORK_STATE -> 查询网络状态
ACCESS_WIFI_STATE -> 查询 WiFi 状态
CHANGE_NETWORK_STATE -> 切换网络
诊断:
如果应用有 INTERNET 权限但仍不能连:
├─ 可能是网络本身的问题(见前面步骤)
├─ 或应用有 bug(发送不正确的请求)
└─ 用 tcpdump 抓包检查
Step 8: 抓包分析流量 ──────────────────
命令:
adb shell tcpdump -i wlan0 -w /sdcard/traffic.pcap
// 运行应用和重现问题
adb pull /sdcard/traffic.pcap
// 用 Wireshark 分析
关键检查点:
DHCP 阶段:
├─ 设备发送 DHCP Discovery
├─ 路由器响应 DHCP Offer
├─ 设备发送 DHCP Request
└─ 路由器响应 DHCP ACK
DNS 阶段:
├─ 设备发送 DNS Query (A 或 AAAA)
├─ DNS 服务器响应结果
└─ 检查是否有超时或错误
HTTP/HTTPS 阶段:
├─ TCP 3-way handshake
├─ HTTP GET/POST 请求
├─ 检查响应状态码
└─ 检查是否有超时
【网络切换异常的定位】 ═════════════════════════════════════════════════════════════
问题:设备频繁切换 WiFi 和 LTE(抖动)
诊断:
Step 1: 确认是否真的在切换 ─────────────────────────
命令:
adb logcat -b system | grep -E "rematch|default|network.available"
日志示例(表示在切换):
T+0.0s: [WiFi] Network score: 75
T+1.5s: [LTE] Network score: 65
T+2.0s: rematchAllNetworksAndRequests() -> 选择 WiFi
T+5.0s: [WiFi] Network score: 45 (信号变弱)
T+5.1s: rematchAllNetworksAndRequests() -> 选择 LTE
T+5.2s: WiFi entering LOSING state
T+8.0s: [WiFi] Network score: 75 (信号恢复!)
T+8.1s: rematchAllNetworksAndRequests() -> 选择 WiFi (取消 Linger)
T+10.0s: (重复上面的循环)
诊断:
如果频繁看到这种日志:
├─ WiFi 信号不稳定(信号在边界,上下波动)
├─ NetworkRanker 每次评分变化都触发重新匹配
└─ 这导致频繁切换
Step 2: 检查信号强度变化 ──────────────────────
命令:
adb logcat | grep -E "SignalStrength|RSSI|RSRP"
日志示例:
WifiManager: RSSI changed: -60dbm -> -70dbm -> -60dbm -> -70dbm ...
TelephonyManager: Signal strength: 2 bars -> 3 bars -> 2 bars -> 3 bars ...
诊断:
如果看到信号在边界频繁波动:
├─ 物理位置在信号边界
├─ 用户走近/离开 WiFi 路由器
└─ 解决:建议用户移动位置或加强 WiFi 覆盖
Step 3: 检查评分计算 ──────────────────
命令:
adb logcat | grep "NetworkScore"
日志示例:
WiFi score: 60 (RSSI: -75dbm, validated: true, exiting: false)
LTE score: 65 (RSRP: -90dbm, validated: true, exiting: false)
Decision: LTE > WiFi, switch to LTE
诊断:
如果评分都正确,但频繁切换:
├─ 可能是网络排序算法的问题
├─ 或者 NetworkRanker 的 "stickiness" 没有生效
└─ 检查当前默认网络是否在 candidates 中
Step 4: 调整参数以减少切换 ─────────────────────────
系统属性:
# 增加 Linger 时间(给网络恢复更多时间)
ro.config.ip_linger_ms=300000 (默认 120000)
# 改变评分偏好
persist.sys.wifi.prefer_bad_wifi=true (倾向于保留 WiFi)
# 禁用自动切换(不推荐)
persist.sys.disable_network_switching=true
代码层面的解决:
在 NetworkRanker 中增加"粘性"评分奖励:
├─ 如果当前网络还在 candidates 中
├─ 给当前网络 +10 分(粘性奖励)
├─ 减少不必要的切换
└─ 只在评分差异足够大时才切换
【网络性能优化】 ═════════════════════════════════════════════════════════════
优化 1: 加速网络就绪时间 ──────────────────────
问题:从无网络到有网络要 10 秒
优化方法:
a) 并行初始化
// 不推荐:串行
step1_getIPAddress(); // 3 秒
step2_startDNS(); // 2 秒
step3_startValidation(); // 5 秒
// 总耗时:10 秒
// 推荐:并行
parallel {
task1: getIPAddress(); // 3 秒
task2: startDNS(); // 2 秒
task3: startValidation(); // 5 秒 (并行)
}
// 总耗时:5 秒 (最长的任务)
b) 验证缓存
如果相同 SSID 的 WiFi 在 1 小时内验证过:
├─ 跳过验证,直接标记为 VALIDATED
├─ 或发送探测但不等结果
└─ 节省 3-5 秒
c) 提前启动网络服务
在 CONNECTING 状态,就可以:
├─ 启动 DNS 解析
├─ 启动 HTTP 连接池
├─ 预加载常用资源
└─ 等 CONNECTED 时立即可用
优化 2: 降低网络切换的延迟 ──────────────────────────
问题:WiFi 到 LTE 的切换需要 2-3 秒
优化方法:
a) 预连接
识别即将需要的服务器:
├─ 建立 TCP 连接(SYN 发送)
├─ 建立 TLS 握手(部分完成)
└─ 当 Linger 时用户交互时,立即可用
b) 连接复用
使用 HTTP/2 和 HTTP/3:
├─ 多路复用(避免多个 TCP 连接)
├─ 连接迁移(跨网络保活连接)
└─ 头部压缩(减少重建连接的开销)
c) 缓存和离线支持
缓存关键资源:
├─ 避免网络切换时重新获取
├─ Service Worker 提供离线支持
└─ 用户无感知切换
优化 3: 减少电池消耗 ────────────────────
问题:WiFi 和 LTE 同时活跃消耗电池
优化方法:
a) 及时关闭 Linger 中的网络
当网络质量差时:
├─ 不等 120 秒 Linger 完成
├─ 立即切换并断开旧网络
└─ 省电
b) 合并 DNS 查询
不同应用的 DNS 查询:
├─ 系统缓存,避免重复查询
├─ 减少网络芯片唤醒次数
└─ 更省电
c) 智能预测
基于应用使用模式:
├─ 预测用户何时需要网络
├─ 提前建立网络,避免频繁开关
└─ 比反复开关更省电
优化 4: 提高网络稳定性
────────────────────
a) 增强型握手
在不稳定网络中: ├─ 重试机制(TCP fast open) ├─ 断线重连(快速恢复) ├─ 请求重试(自动恢复) └─ 用户感受到的中断更短
b) 多网络绑定
关键连接使用两个网络: ├─ WiFi 和 LTE 同时用 ├─ 任一网络故障自动切换 ├─ 无中断(超级用户体验)
c) 编码优化
在差网络中: ├─ 更强的纠错码 ├─ 更频繁的校验 ├─ 自动码率调整 └─ 减少重传