ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

低版本Android无法访问互联网:NetworkMonitor与IPv6修复

低版本Android无法访问互联网:NetworkMonitor与IPv6修复 1. 先搞清楚已连接但无法访问互联网这句话是谁说的绝大多数人看到这个提示的第一反应是网断了然后开始重启路由器、换WiFi、改静态IP一圈折腾下来发现状态栏还是挂着那个感叹号。我在过去几年里帮人处理过不下三十台这类设备其中相当一部分其实网络是通的浏览器能打开网页、微信能收发消息只有系统那个提示一直挂着。所以在动手之前必须先把一件事掰开报错的到底是谁。1.1 这个提示来自系统探测不来自WiFi链路安卓的WiFi状态分两层。第一层是三层的关联状态也就是有没有成功握手、有没有拿到IP这一层由wpa_supplicant和 DHCP 客户端负责。第二层是应用层的连通性判定由系统里的一个叫NetworkMonitor早期叫 CaptivePortalTracker的组件负责。它干的事情非常朴素在拿到IP之后偷偷向一个固定的URL发一个HTTP请求这个URL被设计成只返回一个204空响应。如果它在规定时间内收到了预期结果就认为这个网络能上网如果超时、被重定向、或者返回了别的状态码就认为这个网络被门户拦截或者根本不通然后把WiFi图标打上感叹号并在设置界面显示已连接但无法访问互联网。注意这句话的原始语义是系统探测失败不是你的数据发不出去。这两件事在低版本安卓上经常被混为一谈也是很多人白折腾半天的根源。所以第一步永远是打开浏览器手动访问一个不依赖缓存的网址看看是不是真的不通。如果不通往下走排查链路如果通那问题就变成了如何让系统别再误报处理方式完全不同。1.2 低版本安卓的探测地址为什么特别容易出问题Android 5.0 开始引入这套机制那会儿用的是明文HTTPhttp://connectivitycheck.gstatic.com/generate_204。Android 7.0 之后为了防劫持改成了HTTPS探测同时维护HTTP和HTTPS两个地址。Android 9 又加了captive_portal_mode这个全局开关允许通过adb调整行为。问题就出在这里这个探测地址是一个硬编码在系统里的海外域名。在部分网络环境下这个域名解析不出来、或者能解析但连不上、或者被中间设备劫持后返回了302跳转系统的判定结果就变成了没网。设备本身能正常访问国内站点但系统不认。低版本安卓尤其明显原因有三条一是老系统没有DNS over TLS解析完全依赖路由器下发的DNS二是老系统对HTTPS证书链的校验策略比较死证书链一旦有问题直接判失败三是Android 8之前的NetworkMonitor跑在独立的进程里超时时间给得比较短网络稍微抖一下就报错。1.3 静态IP为什么改了也白改这是被问得最多的疑问。静态IP改的是三层地址的获取方式——从DHCP自动获取变成手动填写。但它动不了三样东西探测用的目标URL还是系统里写死的那两个域名DNS解析走的还是你在静态配置里填的那个DNS服务器时间同步、证书校验这些应用层逻辑跟你用动态还是静态IP没有任何关系。改动项静态IP能否影响说明IP地址、子网掩码能手动指定绕开DHCP异常网关能必须填安卓静态IP配置里网关是必填项DNS服务器能但填错了反而更糟探测目标URL不能硬编码在系统设置里系统时间 / 证书链不能属于应用层路由器侧AP隔离、IPv6不能在路由器上所以静态IP只在一种场景下有效DHCP下发本身有问题比如路由器地址池耗尽、下发了错误的网关、或者下发了不可用的DNS。这种情况下改静态确实能救回来。但如果网络本身三层是通的问题出在探测环节那静态IP就是你折腾一晚上也摸不到门的地方。顺带提一句安卓填静态IP时网关和DNS都是必填的前缀长度也必须填只填IP不填网关在部分机型上会直接保存失败或者保存后不生效。这是跟桌面系统不太一样的地方。2. 把排查顺序倒过来从探测环节往回查常规教程都让你从重启路由器开始这个顺序在低版本安卓上效率极低。我的习惯是反过来先确认系统到底卡在哪一步再决定往哪个方向修。下面这套顺序是我自己用下来最省时间的。2.1 用adb抓日志直接看探测失败的原因有adb环境的话这一步能省掉大量猜测。连上设备后执行adb logcat -c adb logcat -s NetworkMonitor:* ConnectivityService:* CaptivePortalLog:* wpa_supplicant:*然后手动断开WiFi再重连一次。日志里会明确打出探测请求发往哪个地址、返回了什么、判定结果是什么。典型的几类输出Probe failed with HTTP status 302—— 被重定向了说明探测地址被拦截Probe failed with exception java.net.SocketTimeoutException—— 目标地址连不上Probe failed with SSLHandshakeException—— 证书校验失败多半跟时间或证书链有关Validation failed, no DNS—— 压根没解析出来。这几行日志基本就把方向定了。没有adb也没关系用下面的土办法一样能定位。2.2 用ping和浏览器手动复现探测过程先看三层通不通adb shell ping -c 4 223.5.5.5 adb shell ping -c 4 www.baidu.com第一条测的是纯IP连通性第二条测的是DNS解析加连通性。结果组合能说明不少问题ping IPping 域名结论通通三层和DNS都正常问题在应用层探测通不通DNS解析有问题检查路由器下发的DNS不通不通网关、路由或AP隔离问题时通时不通时通时不通信号质量、信道干扰或省电策略这个表格看着简单但实测中能覆盖七八成的情况。特别是第一行——ping IP和域名都通系统还报无法访问互联网那基本可以确定是探测环节的问题别再去动路由器和IP设置了。2.3 DNS这一环最容易被忽略的两个细节第一个细节安卓的DNS缓存比你想的顽固。改完DNS之后老系统不会立刻清缓存需要开飞行模式再关掉或者干脆重启。我遇到过好几次路由器上把DNS换成公共DNS设备上还是走的老缓存以为没生效其实只是没刷新。第二个细节路由器下发的DNS可能是运营商的内网DNS而这个DNS只解析特定域名。部分宽带环境下的内网DNS会对未知域名返回一个广告页或者直接返回NXDOMAIN安卓的探测请求打过去自然拿不到204。这种情况的验证方法很简单在设备上把DNS手动改成223.5.5.5或者119.29.29.29试一次如果感叹号消失了问题就锁定了。提示改DNS的时候记得先记下原来的值。有些企业网络或校园网必须用内网DNS才能访问内部资源随手改掉会导致另一批服务不可用。3. 低版本安卓绕不开的三道坎时间、证书、IPv6排查到这里如果三层、DNS都正常问题大概率落在这三样上。它们有一个共同特点症状看起来跟网络没关系但会让系统的HTTPS探测直接失败。3.1 系统时间偏差会让HTTPS探测必然失败Android 7.0 之后的探测走的是HTTPS。HTTPS握手的第一步就是校验证书有效期而校验依赖设备的系统时间。如果设备时间是2015年而服务器证书的有效期是2023-2024年握手会直接抛CertificateExpiredException系统的判定结果就是这个网络不能上网。低版本安卓设备特别容易中招原因很现实这类设备往往是旧手机、旧平板、电视盒子、车机长期不联网、电池拔掉之后RTC彻底掉电开机时间直接回到出厂默认。设备连上WiFi之后时间同步本身又依赖网络形成一个死循环——时间不对导致探测失败探测失败导致系统认为没网认为没网就不去同步时间。破环的办法是手动改时间adb shell date 081512002025.00 adb shell settings put global auto_time 0或者在设置里关掉自动确定日期和时间手动调到大致的当前时间然后再打开自动同步。实测下来只要时间误差在证书有效期内探测立刻就能过。3.2 证书链问题是老设备的高发区这一条比时间问题更隐蔽。部分老设备出厂时预置的根证书列表比较旧而探测目标使用的证书链可能挂在一个较新的中间证书上。设备无法构建完整信任链握手失败同样报无法访问互联网。判断方法还是在日志里找SSLHandshakeException或者CertPathValidatorException。如果确认是这个原因能做的事情不多一是把探测地址切换到HTTP版本下一节讲怎么改二是用系统更新补证书但老设备基本没有系统更新了所以实际上就是走第一条路。3.3 IPv6 会制造一种看起来正常却上不了网的假象这个坑我踩过。路由器同时下发了IPv4和IPv6地址安卓的地址选择策略会优先走IPv6。如果路由器分了IPv6前缀但上游实际上没有IPv6出口很多宽带环境是这样路由器拿到了前缀但链路不通设备就会一直往IPv6地址上撞超时之后才回退到IPv4。系统的探测超时时间扛不住这个回退耗时直接判定失败。验证起来很直接在设备上看一眼拿到的地址adb shell ip -6 addr show wlan0如果能看到一个全局的IPv6地址不是fe80::开头的链路本地地址而你在其他设备上确认这个网络的IPv6其实不通那问题就找到了。解决办法是在路由器上关掉IPv6的RA通告或者把IPv6模式改成不使用。提示不要只在设备端想办法。安卓本身不提供禁用IPv6的开关这个只能从路由器侧处理。4. 对症下药几套能落地的修复方案前面把原因找出来之后修复反而简单。下面几套方案按侵入性从低到高排列能不动系统就不动系统。4.1 改探测地址需要root或adb的一次性操作这是最直接的方案把系统探测的目标从默认地址换成你能控制的、响应稳定的地址。前提是设备已经开了USB调试或者有root权限。adb shell settings put global captive_portal_http_url http://connectivitycheck.platform.hicloud.com/generate_204 adb shell settings put global captive_portal_https_url https://connectivitycheck.platform.hicloud.com/generate_204 adb shell settings put global captive_portal_fallback_url http://connectivitycheck.platform.hicloud.com/generate_204 adb shell settings put global captive_portal_use_https 0几点说明这三个键在Android 7.0之后的版本才被系统读取Android 5.x/6.x上改了没用只能靠captive_portal_server这个老键而且需要root改数据库captive_portal_use_https设为0会强制走HTTP探测能绕开证书问题但也意味着探测结果更容易被劫持适合内网环境改完之后必须重启一次或者至少断开重连设置不会热生效。如果设备没有adb也没root这条路走不通走4.3。4.2 直接关掉连通性检测如果你已经确认网络实际是通的只是不想看那个感叹号可以干脆关掉检测adb shell settings put global captive_portal_mode 0 adb shell settings put global captive_portal_detection_enabled 0captive_portal_mode的三个取值含义是0关闭检测、1只提示、2自动打开登录页默认值。改成0之后系统不再做探测WiFi图标会显示正常但代价是公共WiFi的登录页也不会再自动弹出需要手动打开浏览器访问任意网址触发跳转。这个方案适合固定环境下的设备比如家里的电视盒子、车机、监控屏。不适合经常连公共WiFi的手机。4.3 路由器侧调整不动设备就能解决的几个开关很多时候根因在路由器改路由器比改设备省事得多。下面这几个开关我按命中频率排序AP隔离客户端隔离开着的话设备之间不能互访部分探测逻辑或者本地服务会失败。家用的直接关掉。IPv6 关闭前面讲过没有IPv6出口的环境直接关省掉一堆回退超时。DNS 手动指定在DHCP设置里把下发的DNS改成223.5.5.5和119.29.29.29绕开运营商内网DNS。快速漫游 802.11r/k/v老设备对这些协议的支持不完整协商过程中出问题的概率不低单AP环境直接关掉。WMM无线多媒体个别老设备跟WMM兼容性差关掉试试。信道和频宽2.4G固定在1、6、11三个非重叠信道频宽设20MHz稳定性优先。这几项不需要全改按顺序一项一项试每改一项就重连一次看效果。一次只改一个变量不然出了问题你都不知道是哪一项引起的。4.4 省电策略导致的连上就断低版本安卓的省电机制比较激进屏幕一关就把WiFi降频或者断开唤醒之后重连需要时间系统的探测正好卡在这个窗口期就报错了。进入设置 - 电池 - 电池优化把关键应用设为不优化同时在WiFi高级设置里找一下有没有保持WiFi在休眠期间开启这类选项有的话打开。开发者选项里也可以把始终开启移动数据和WiFi扫描调节这两个开关调整一下。这个原因造成的报错有个明显特征屏幕亮着的时候正常锁屏一段时间再亮屏感叹号就出现了。如果你观察到这个规律直接往省电方向查。5. 一台Android 9平板的完整修复过程理论讲完讲一个我上个月实际处理的例子。设备是一台Android 9的旧平板用户反馈连上WiFi就显示已连接但无法访问互联网改成静态IP也一样。5.1 现象确认与初步排除第一步先确认是不是真的不通。打开浏览器访问一个国内站点能打开。这说明三层和DNS都是通的问题锁定在应用层探测。这一步花了不到两分钟却直接把排查范围砍掉了一大半。接着抓日志adb logcat -c adb logcat -s NetworkMonitor:* ConnectivityService:*断开重连日志里出现Probe failed with exception java.net.SocketTimeoutException探测请求超时。结合访问国内站点正常这一点基本可以判断是探测目标地址在这个网络环境下不可达。5.2 定位到具体环节为了确认不是DNS的问题我做了两组对照adb shell ping -c 4 connectivitycheck.gstatic.com adb shell ping -c 4 223.5.5.5第一个域名解析出来了但ping不通第二个IP通。所以DNS本身工作正常是目标地址的可达性问题。再看一眼IPv6adb shell ip -6 addr show wlan0发现设备确实拿到了一个全局IPv6地址而路由器那边IPv6是没有出口的。这就解释清楚了——探测请求优先走IPv6一直超时超时时间扛不住直接判失败。而浏览器访问国内站点时因为目标站点的IPv6解析要么没有要么很快失败回退到IPv4反而很快。5.3 修复与回归验证处理方式分两步。第一步在路由器上关掉IPv6的RA通告让设备不再获取全局IPv6地址。第二步把探测地址换成国内可访问的adb shell settings put global captive_portal_http_url http://connectivitycheck.platform.hicloud.com/generate_204 adb shell settings put global captive_portal_https_url https://connectivitycheck.platform.hicloud.com/generate_204 adb shell settings put global captive_portal_use_https 0重启平板。开机后连WiFi感叹号消失设置界面显示已连接。为了确认稳定我锁屏放了一个小时再唤醒状态正常切换飞行模式来回三次状态正常另外换了两个不同的热点测试也正常。再回头看静态IP为什么没用这台设备的问题在IPv6地址选择和探测目标可达性上静态IP只改了IPv4地址的获取方式IPv6地址是路由器RA通告下发的跟IPv4静态配置完全是两套机制所以改了一点效果都没有。这个案例很典型地说明了改静态IP这个动作的适用边界有多窄。6. 关于这类问题的几点个人经验写到这里前面该讲的原理和步骤基本都覆盖了。最后补几条零散的、文档里不太会写的东西。第一先分清楚真不通和假不通。这一条我重复了三遍因为它能省掉你至少一半的时间。判断方法就是浏览器手动访问一个不依赖缓存的页面两秒钟的事。第二改设置之前先记录原值。特别是captive_portal_http_url这类系统全局设置改之前先把原值读出来存一份adb shell settings get global captive_portal_http_url adb shell settings get global captive_portal_https_url adb shell settings get global captive_portal_mode万一改完出现了新问题还能改回去。我见过有人把captive_portal_mode改了之后忘了设了几最后只能恢复出厂。第三老设备的WiFi模块本身可能老化。处理过一台设备所有软件层面的排查都做了最后发现是WiFi天线接触不良信号强度在-80dBm左右徘徊偶尔能连上但极不稳定。这种情况系统报什么错都有可能。如果你的设备用了五年以上硬件因素值得考虑进去。第四判断是否值得修。一台Android 7的旧设备如果只是当电子相框或者看视频用直接关掉连通性检测最省事别折腾探测地址。如果是主力机还要用公共WiFi那还是老实解决根因。投入产出比这件事自己心里得有杆秤。第五做一次完整的变更记录。如果你在路由器上动了好几项设置把改了什么、改成什么记下来。下次出问题的时候这份记录比任何排查思路都值钱。我自己是用一个简单的文本文件按日期记几年下来翻回去查非常方便。
返回列表