App 运行环境复杂,用户投诉中很大一部分集中在”页面加载慢”、“登录失败”、“连接不稳定”等网络相关问题上,这类问题在测试环境中往往难以复现。传统的服务端监控或 CDN 日志,无法完整还原终端侧的真实网络状态。
针对这类问题,设计的端内网络监控方案核心目标是:
- 主动感知用户当前的网络实际情况
- 精准识别网络故障的真实成因
- 为网络问题的定位、修复和优化提供结构化的数据支撑
具体实现为应用网速检查、DNS 劫持检测两部分。
应用网速检查
原理
该模块基于 Android 原生的 TrafficStats API,以应用 UID 为统计粒度,在固定的短时窗口(5 秒)内读取接收流量增量,通过增量字节数除以时间差计算客户端真实下载速度。该方式无需额外权限或发起网络请求,运行轻量化,且能反映用户真实的网络使用体验。
检查时机
- 应用启动时:首次启动立即执行检查
- 网络切换时:例如从 WiFi 切换至移动网络
- 应用回前台时:避免后台期间网络状态发生变化未被感知
- 请求失败时:辅助诊断网络层面的问题诱因
- 每 20 秒定期执行:满足长时间驻留场景下的持续采样需求
由于检测本身需要 5 秒的采样窗口,实际上天然具备了去重能力,无需额外设置冷却时间。同时,为避免多线程并发触发检查导致的统计异常,通过原子变量标记当前检查状态,确保同一时间仅执行一次检查。
数据上报
| 内容 | 示例值 |
|---|---|
| 平均下载速度 | 32.00 KB/s |
| 当前网络类型 | WIFI / 4G / 5G |
| 运营商名称 | 中国联通 |
| 移动网络信号强度 | -88 |
| 是否处于漫游状态 | false |
| WiFi 信号等级 | 3 |
注:部分字段需要特定权限支持(获取信号强度/WiFi 等级需 ACCESS_FINE_LOCATION 权限,获取漫游状态需 READ_PHONE_STATE 权限),方案仅做权限判断不主动申请,若权限缺失则不上报对应字段。速度值会根据量级自动格式化为 B/s、KB/s 或 MB/s。
DNS 劫持检测
原理
DNS 劫持是客户端常见的网络安全问题,在公共 WiFi 或部分三线运营商环境下更容易出现。我们通过对比系统 DNS 与可信 DoH 服务的解析结果,来识别是否存在劫持行为。
- 系统 DNS:调用
InetAddress.getAllByName()获取解析结果 - DoH 服务:如 Google DNS、AliDNS、腾讯 DNSPod,通过加密连接获取的权威解析结果
Google DNS:https://dns.google/resolve?name=%s&type=A
AliDNS:https://dns.alidns.com/resolve?name=%s&type=A
DNSPod:https://doh.pub/dns-query?name=%s&type=A
可信 IP 池与缓存机制
可信 IP 池由两部分组成:
- 当前 DoH 解析结果:本次从三家 DoH 服务获取到的 IP 地址
- 历史 DoH 缓存:过去通过 DoH 成功解析过的 IP 地址,本地持久化存储,有效期 90 天
缓存采用结构化存储设计,核心包含”域名-IP-时间戳”的关联结构:以域名为维度归类 IP,每个 IP 条目附带最后更新时间戳,用于后续过期判断。对应的核心策略如下:
- 缓存更新策略:每次 DoH 解析成功后,将有效 IP 添加到对应域名的缓存中;若 IP 已存在,则更新其时间戳,确保活跃 IP 的有效性
- 过期清理策略:定期过滤超出 90 天有效期的 IP 条目,避免缓存冗余占用终端存储,同时保证缓存 IP 的时效性
判定逻辑执行前会先通过网络连接状态检查(确认设备是否真的接入网络),避免将网络未连接的情况误判为 DNS 问题。在此基础上,具体判断逻辑为:
- 若系统 DNS 返回的 IP 全部包含在可信 IP 池中,视为解析正常
- 若存在系统 DNS 返回的 IP 不在可信 IP 池中,判定为疑似劫持
这种设计主要是考虑到 CDN 和负载均衡场景下,同一域名的解析结果会随时间、地域变化。单纯依赖实时 DoH 结果对比,容易将正常的 IP 轮转误判为劫持。通过维护历史 DoH 缓存,可以有效缓解这一问题。
检查时机
为节省终端资源,DNS 劫持检查仅在网络请求失败且命中特定异常类型集合时触发,同时设置 5 秒冷却时间,即两次检查之间至少间隔 5 秒。该冷却机制的核心作用是避免同一网络问题(如持续的连接失败)导致频繁触发检测:一方面可减少重复的 DoH 网络请求,降低用户流量消耗;另一方面能避免终端线程频繁占用,提升应用运行流畅度。
相较于启动、切网等高频触发方式,这种按需触发+精准异常命中的策略更贴合实际业务场景,也能减少不必要的资源消耗。
异常类型集
以下是网络异常中与 DNS 劫持高度相关的典型类型:
| 异常类型 | 原因分类 | 说明 |
|---|---|---|
CertPathValidatorException | 证书不可信 | TLS 证书签名链不受信任,常见于中间人攻击场景 |
SSLHandshakeException / SSLPeerUnverifiedException | TLS 握手失败 | 证书链异常或握手中断,可能由劫持导致 |
UnknownHostException | DNS 解析失败 | 无法将域名解析为 IP,多出现于 DNS 劫持或污染场景 |
ConnectException | 连接被拒绝 | 网络可达但目标端口无响应,可能是劫持后的伪地址 |
SocketTimeoutException | TCP 超时 | 网络拥堵或服务不可达,需结合 DNS 解析结果排查 |
检测结果分类
根据系统 DNS 和 DoH 的解析情况,我们将检测结果细分为以下几类:
| 结果状态 | 判定条件 | 说明 |
|---|---|---|
| 劫持 | 系统 DNS 和 DoH 都成功,但系统 IP 不在可信 IP 池中 | 疑似 DNS 劫持 |
| 网络问题 | 检测到网络未连接 | 真实的网络连接中断 |
| 系统 DNS 失败 | 系统 DNS 失败但 DoH 成功 | 本地 DNS 配置问题 |
| DoH 失败 | 系统 DNS 成功但 DoH 失败 | DoH 服务不可用 |
| 正常 | 系统 DNS 的 IP 全部在可信 IP 池中 | 解析结果一致 |
| 未知 | 网络连接正常但 DNS 服务均失败 | DNS 服务器问题或端口被封锁 |
这种细粒度的分类有助于快速定位问题根因,避免将所有异常都归结为”劫持”。
数据上报
| 内容 | 示例值 |
|---|---|
| 检查目标域名 | api.example.com |
| 系统 DNS 返回的 IP 列表 | [1.2.3.4, 5.6.7.8] |
| DoH 返回的 IP 汇总 | GoogleDNS=[1.2.3.4]; AliDNS=[1.2.3.4, 9.9.9.9] |
| 检测结果状态 | IS_HIJACKED / PASS / NETWORK_FAIL 等 |
| 详细原因描述 | 包含异常 IP、可信 IP 池等详细信息 |
| 触发检查的异常类型 | TLS_CERT_UNTRUSTED_SIGNATURE / DNS_RESOLUTION_FAILED 等 |
注:为避免无效数据干扰分析,埋点仅上报判定为劫持的情况。