ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Unity安卓网络检测:避开Application.internetReachability的坑

Unity安卓网络检测:避开Application.internetReachability的坑 不知道你有没有在安卓上被Application.internetReachability坑过我去年做一款休闲游戏的时候测试同学提了一个诡异 Bug手机连着办公室 Wi-Fi游戏里却一直提示“网络异常请重试”后来我换用移动数据反而正常了。查了半天最后发现罪魁祸首就是 Unity 自带的这个网络检测接口。我先说结论Application.internetReachability在安卓上只能告诉你“当前系统认为有没有网络连接”并不能告诉你“当前设备能不能真正访问互联网”。这两个概念之间的差距就是你项目里各种“假断网”“假在线”问题的根源。下面把原理、坑位和替代方案一次说清楚。1. 先搞清楚 Application.internetReachability 到底是干嘛的1.1 一个 API 和一个枚举Application.internetReachability是 Unity 引擎提供的一个只读属性返回值是一个NetworkReachability枚举总共三个状态NotReachable当前没有任何网络连接或者连接不可用。ReachableViaCarrierDataNetwork当前使用蜂窝移动网络4G/5G并且系统认为可以访问网络。ReachableViaLocalAreaNetwork当前使用 Wi-Fi / 有线局域网并且系统认为可以访问网络。平时项目里最常见的写法就是这样的switch (Application.internetReachability) { case NetworkReachability.NotReachable: // 断网处理 break; case NetworkReachability.ReachableViaCarrierDataNetwork: // 移动网络处理 break; case NetworkReachability.ReachableViaLocalAreaNetwork: // Wi-Fi 处理 break; }看起来很直观对吧问题就出在这个“系统认为”上。它判断的是“当前活动网络是否存在”至于这个网络能不能真的打通外网它根本不关心。1.2 项目里的典型误判案例我遇到的那次问题就是典型的“连上 Wi-Fi 但外网不通”。办公室路由器没有自动拨号手机虽然拿到了内网 IP所有应用都访问不了外网。但Application.internetReachability依然返回ReachableViaLocalAreaNetwork游戏客户端以为网络没问题一直去发请求最后一堆超时、断线重连体验非常糟糕。类似的误判场景其实不少商场、酒店、机场的公共 Wi-Fi连上后弹出网页认证没认证就不能上网但系统已经认为网络“连上了”。路由器拨号失败但 DHCP 正常分配 IP设备照样能连路由器只是出不了外网。飞行模式刚关闭系统网络状态正在恢复中Application.internetReachability可能会短暂返回错误值。部分国产安卓定制 ROM 对网络状态的上报逻辑不一致同一个接口在不同手机上结果完全不同。所以我后来在做网络检测时第一件事就是告诉团队Application.internetReachability只能当“参考信息”不能当“最终结论”。2. 为什么它在安卓上会“说假话”2.1 它只回答“有没有插网线”不回答“能不能上网”拿网线比喻最合适。Application.internetReachability相当于看你电脑右下角的网络图标图标显示“已连接”但网线另一头可能根本没接路由器外网口或者宽带欠费了。系统层面只是确认了“链路层通了”并没有做真正的互联网可达性探测。在安卓上Unity 底层早期版本通常会读取ConnectivityManager.getActiveNetworkInfo()再去判断isConnectedOrConnecting()。这个 API 只能告诉你当前有没有一个激活状态的网络比如 Wi-Fi 关联成功、蜂窝数据打开它不会去 Ping 百度也不会去访问任何服务器来验证。关键点在于Wi-Fi 关联成功和能访问互联网是两码事。路由器可以正常分配 IP但上游断网系统依旧会把这个网络标记为“已连接”。Application.internetReachability拿到这个结果后自然就会给你一个虚假的“可达”。2.2 安卓 5.0 之后的网络校验机制是存在的Unity 却没有直接暴露其实安卓本身不是完全没做“互联网探测”。从 Android 5.0API 21开始系统引入了NetworkCapabilities里面有两个非常关键的标志NET_CAPABILITY_INTERNET这个网络具备访问互联网的能力。NET_CAPABILITY_VALIDATED系统已经通过实际探测确认这个网络可以访问外网。第二个标志尤其重要。系统会在网络连接后尝试访问一个系统配置的探测服务器如果成功才会把这个网络标记为VALIDATED。如果路由器虽然连着宽带的猫但宽带本身已经断了系统探测失败这个标志位就不会被标上。然而 Unity 的Application.internetReachability并不读取这个标志位。它只关心“有没有网络连接”所以安卓系统已经判断出“网络不可上网”的情况Unity 依然可能认为“可达”。这也是很多项目在安卓 9、安卓 10 上频繁出问题的原因之一。2.3 权限和上下文环境也会影响结果Application.internetReachability在安卓上依赖ACCESS_NETWORK_STATE权限。如果 AndroidManifest 里没有声明这个权限Unity 拿不到网络状态可能会直接返回NotReachable。很多新手刚接入 SDK 时就碰到“明明有网却提示没网”大概率是权限问题。另外这个 API 是同步读取系统缓存的。如果网络刚发生切换比如从 Wi-Fi 切到 4G系统缓存还没来得及刷新你立刻读取拿到的很可能还是旧状态。所以它天然不适合在网络切换瞬间做判断。遇到这种问题建议先做一件事在游戏里写一段调试日志把Application.internetReachability的结果、当前活动网络的名称、以及一个简单 HTTP 请求的结果一起打印出来对比着看。你会发现大部分“误判”都是因为把“网络连接状态”和“互联网连通状态”混为一谈了。3. 快速自救用 HTTP 探测网络连通性3.1 为什么主动探测有效既然系统状态不可靠那就自己动手。原理很简单向一个固定地址发起一个 HTTP 请求能拿到正确响应就说明当前网络真的能访问互联网拿不到就是断网或者网络不通。这是最直观、最不依赖具体 API 的检测方式。这种方案的优点是实现简单、跨平台一致、不受 Unity 底层封装影响。缺点是必须依赖一个外部服务器如果探测服务器本身出问题即使客户端网络正常也会被误判为断网。所以需要选一个稳定的探测地址。3.2 完整的 C# 协程实现我项目里最常用的是一段基于UnityWebRequest的协程代码只发 HEAD 请求数据量小响应快using System.Collections; using UnityEngine; using UnityEngine.Networking; public class NetChecker : MonoBehaviour { public static bool IsInternetAvailable { get; private set; } public static IEnumerator CheckInternet(System.Actionbool callback) { const string testUrl https://www.baidu.com; // 换成你自己可控的后端更稳 using (UnityWebRequest request UnityWebRequest.Head(testUrl)) { request.timeout 3; // 秒 yield return request.SendWebRequest(); bool ok request.result UnityWebRequest.Result.Success; Debug.Log($[NetChecker] HTTP 探测结果: {ok}, 返回码: {request.responseCode}); IsInternetAvailable ok; callback?.Invoke(ok); } } }调用方式也很简单private IEnumerator Start() { yield return NetChecker.CheckInternet((online) { if (online) { Debug.Log(当前可以上网); } else { Debug.Log(当前无法访问互联网); } }); }这里有几个细节要注意选用Head而不是Get因为 HEAD 请求不会下载页面正文开销更小。timeout一定要设置。不设置的话在微弱信号下请求可能卡十几秒用户早就骂街了。我通常设置 3 秒最长不超过 5 秒。探测地址建议优先选自己的服务器接口或一个长期稳定的健康检查接口。如果非要用第三方也尽量选你目标用户群体所在地区都容易访问的地址。不要在Update里每帧检测极度浪费资源。一般只在启动、网络切换、请求连续失败时检测。3.3 工程里怎么落这个方案我把这套检测做成了网络模块的一部分大致逻辑是游戏启动时调用一次 HTTP 探测拿到初始状态。监听Application.internetReachability变化事件一旦有变化延迟 1 到 2 秒再做一次 HTTP 确认。所有业务网络请求失败时先判断是否因为网络不可达如果是弹出断网提示并引导用户检查网络。状态缓存 30 秒避免频繁检测。这样既避免了Application.internetReachability误判又不会因为主动探测请求太频繁造成额外流量。4. 更彻底接入安卓原生网络校验能力4.1 思路让系统告诉我们“已经验证过有网”HTTP 探测虽然能用但毕竟是在 Unity 层模拟了一次网络访问。如果想拿到安卓系统真实的“已验证互联网”状态更彻底的做法是直接调用安卓原生 API读取NetworkCapabilities里的NET_CAPABILITY_VALIDATED标志。这个思路相当于把系统自己做的探测结果拿过来用。安卓系统本身已经在后台完成了联网验证我们不需要再额外请求一个页面既不消耗用户流量也不需要维护探测服务器而且系统探测会随网络状态变化实时更新比 Unity 的静态属性靠谱得多。打通方案是写一个安卓原生 Java 插件通过ConnectivityManager监听网络状态再把结果回调给 Unity C#。4.2 Java 插件代码下面是一个可用的 Java 插件事例基于registerDefaultNetworkCallback监听当前默认网络的验证状态package com.company.netcheck; import android.content.Context; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.os.Handler; import android.os.Looper; import com.unity3d.player.UnityPlayer; public class NativeNetChecker { private ConnectivityManager cm; private boolean isMonitoring false; private String gameObjectName; public NativeNetChecker() { Context context UnityPlayer.currentActivity.getApplicationContext(); cm (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); } public void startMonitoring(String gameObjectName) { if (cm null || isMonitoring) return; this.gameObjectName gameObjectName; isMonitoring true; NetworkRequest request new NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(); cm.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback() { Override public void onAvailable(Network network) { postStatus(isNetValidated()); } Override public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { boolean validated networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_VALIDATED); if (network.equals(cm.getActiveNetwork())) { postStatus(validated); } } Override public void onLost(Network network) { postStatus(false); } }); } private boolean isNetValidated() { Network active cm.getActiveNetwork(); if (active null) return false; NetworkCapabilities caps cm.getNetworkCapabilities(active); return caps ! null caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); } private void postStatus(final boolean online) { new Handler(Looper.getMainLooper()).post(new Runnable() { Override public void run() { if (UnityPlayer.currentActivity ! null) { UnityPlayer.UnitySendMessage(gameObjectName, OnNativeNetStatus, online ? 1 : 0); } } }); } public void stopMonitoring() { isMonitoring false; } }这段代码的核心是onCapabilitiesChanged回调。系统每次完成网络验证或验证失败后都会触发这个方法我们在里面读取NET_CAPABILITY_VALIDATED。因为只关心默认网络所以回调里还要判断一下当前触发回调的Network是不是活跃网络避免出现“双卡切换时回调错乱”的情况。需要特别注意的是registerDefaultNetworkCallback需要 Android 6.0API 23及以上。如果你的最低版本低于 6.0建议在调用前加版本判断低版本退回 HTTP 探测方案。4.3 在 Unity C# 中调用原生插件Java 代码写好之后主要工作是导入 Unity。我建议用一个 AAR 包或者直接把 Java 源文件放到Assets/Plugins/Android下让 Unity 在构建时一起编译。如果工程本身用自定义 Gradle也确实懒得分包可以直接用源码目录方式。C# 侧需要这样封装using UnityEngine; public class NativeNetMonitor : MonoBehaviour { private AndroidJavaObject javaObject; void Start() { #if UNITY_ANDROID !UNITY_EDITOR try { javaObject new AndroidJavaObject(com.company.netcheck.NativeNetChecker); javaObject.Call(startMonitoring, gameObject.name); } catch (System.Exception e) { Debug.LogWarning($[NativeNetMonitor] 初始化失败: {e.Message}); } #endif } void OnNativeNetStatus(string status) { bool online status 1; Debug.Log($[NativeNetMonitor] 系统网络验证状态: {online}); NetChecker.IsInternetAvailable online; // 这里可以刷新你的 UI 状态、缓存、重试队列等 } void OnDestroy() { #if UNITY_ANDROID !UNITY_EDITOR javaObject?.Call(stopMonitoring); #endif } }UnitySendMessage要求gameObject.name对应的物体上有一个接收方法所以挂这个NativeNetMonitor组件的 GameObject 名字要保持一致。如果场景里物体名字改过记得同步修改。4.4 让“快检”和“系统校验”组合起来原生监听方案不是万能的偶尔也会有回调丢失或者时序问题。我目前的策略是组合使用启动时先做一次 HTTP 探测快速拿到结果。同时启动原生监听持续接收系统验证状态。如果原生回调连续多次没有触发就用 HTTP 探测兜底。UI 上的网络指示只信任“HTTP 探测 原生 VALIDATED”都通过的结果。这套组合下来基本能覆盖绝大多数误判场景。尤其是那些“Wi-Fi 显示已连接但无法上网”的经典问题原生方案能在系统验证失败时第一时间把状态推给 Unity不至于让客户端继续傻等超时。5. 常见问题与排查技巧实录5.1 问题速查表我把实际工程中常踩的坑整理成了一个速查表方便你对照排查现象可能原因解决方案明明有网却一直返回 NotReachable缺少ACCESS_NETWORK_STATE权限检查 AndroidManifest补上权限声明Wi-Fi 已连接应用认为可上网实际打不开网页Unity 只判断网络存在未判断外网连通使用 HTTP 探测或读取NET_CAPABILITY_VALIDATED4G 下偶发断网误报信号弱或者网络切换瞬间状态未刷新延迟 1 到 2 秒再检测使用原生回调从 Wi-Fi 切到 4G 后状态长时间不更新Application.internetReachability缓存未刷新监听网络切换事件主动触发重新检测某些国产 ROM 上结果不稳定系统定制层修改了网络状态上报逻辑不依赖单一 API用业务请求结果兜底手游内频繁弹“网络异常”业务接口超时被误判为断网区分超时和真实断网超时重试两次再报错原生插件回调收不到GameObject 名称不匹配或方法名写错检查UnitySendMessage三参数是否一致5.2 一套远程日志排查思路如果你还是定位不到问题我建议做一套“远程日志标记”。每次网络状态变化时把时间戳、Application.internetReachability值、NetworkCapabilities里是否包含VALIDATED、当前 Wi-Fi 名称、蜂窝数据是否开启以及最近一次 HTTP 探测的结果一起上传到后台。这样一旦用户报告“网络有问题”你可以直接对比同一时间点的各个状态看是 Unity 层误判、系统层未验证、还是业务服务器本身响应慢。很多排查问题的时间都能省下来。5.3 最后再分享一个小技巧不管用哪种方案都不要在网络状态刚变化时立刻弹窗。安卓从网络切换完成到系统验证通过一般要经历几百毫秒到几秒不等。你要是拿了一个瞬间状态就去提示用户很容易把自己玩成“狼来了”。我的做法是状态变化后先延迟 1.5 秒再执行具体业务逻辑并且连续两次确认失败才提示用户断网。在一线开发里网络检测看似是个小功能但真要做好必须把 Unity 层、安卓系统层、业务接口三层状态统一起来。现在我自己项目里的网络模块已经彻底不把Application.internetReachability当作判断依据了顶多拿来做个参考真正的线上状态全靠主动探测和原生回调。稳定跑了快一年再也没有出现“假断网”的投诉。
返回列表