WiFi超时后重定向OkHttp3请求至移动网络遇超时问题求助
看起来你已经做了Network绑定的核心工作,但还是遇到了超时问题,我来分享几个排查方向和修复方案:
1. 先确认Network对象的有效性
你获取的TRANSPORT_CELLULAR类型的Network对象,可能只是系统识别到的移动网络,但未必真的连通了互联网。可以先加个校验:
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { NetworkCapabilities capabilities = cm.getNetworkCapabilities(mNetwork); if (capabilities == null || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { Log.e("NetworkCheck", "移动网络无互联网访问权限"); // 这里可以处理网络不可用的情况,比如放弃绑定 return; } } else { // 低版本用NetworkInfo判断 NetworkInfo networkInfo = cm.getNetworkInfo(mNetwork); if (networkInfo == null || !networkInfo.isConnected()) { Log.e("NetworkCheck", "移动网络未连接"); return; } }
如果校验不通过,那超时的根源就是移动网络本身不可用,和OkHttp配置无关。
2. 确保SSLSocketFactory覆盖所有Socket创建方法
你目前只重写了createSocket(Socket socket, String s, int i, boolean flag),但OkHttp在建立连接时可能会调用其他重载的createSocket方法。要确保所有Socket都通过指定Network创建,需要覆盖全部相关重载:
// 补充这些重载方法到你的SSLSocketFactory实现中 @Override public Socket createSocket(String host, int port) throws IOException { if (mNetwork != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return mNetwork.getSocketFactory().createSocket(host, port); } return mFactory.createSocket(host, port); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { if (mNetwork != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return mNetwork.getSocketFactory().createSocket(host, port, localHost, localPort); } return mFactory.createSocket(host, port, localHost, localPort); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { if (mNetwork != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return mNetwork.getSocketFactory().createSocket(host, port); } return mFactory.createSocket(host, port); } @Override public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException { if (mNetwork != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return mNetwork.getSocketFactory().createSocket(host, port, localHost, localPort); } return mFactory.createSocket(host, port, localHost, localPort); }
如果漏掉这些方法,可能部分连接还是会使用默认网络(比如WiFi),导致超时。
3. 检查权限是否齐全
Android 6.0及以上,除了INTERNET,还需要动态申请ACCESS_NETWORK_STATE和CHANGE_NETWORK_STATE权限,否则可能无法正常绑定Network:
- 在
AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
- 在代码中动态申请(针对API 23+):
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { String[] permissions = {Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.CHANGE_NETWORK_STATE}; if (checkSelfPermission(permissions[0]) != PackageManager.PERMISSION_GRANTED || checkSelfPermission(permissions[1]) != PackageManager.PERMISSION_GRANTED) { requestPermissions(permissions, 1001); // 等待权限申请结果后再执行网络绑定逻辑 return; } }
4. 测试移动网络本身的连通性
可以绕过OkHttp,直接用Network对象发起一个简单请求,验证移动网络是否能正常访问互联网:
if (mNetwork != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { new Thread(() -> { try { URL url = new URL("https://www.google.com"); HttpURLConnection conn = (HttpURLConnection) mNetwork.openConnection(url); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); int responseCode = conn.getResponseCode(); Log.d("NetworkTest", "移动网络请求响应码:" + responseCode); } catch (Exception e) { Log.e("NetworkTest", "移动网络请求失败:" + e.getMessage()); } }).start(); }
如果这个请求也超时,那问题出在移动网络本身(比如APN配置错误、运营商限制、后台数据被禁用等),需要先修复网络问题。
5. 排查DNS解析问题
你的DNS实现看起来没问题,但可以临时改用公共DNS测试,排除移动网络DNS故障的可能:
@Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { if (mNetwork != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 用谷歌公共DNS 8.8.8.8测试 InetAddress dnsServer = InetAddress.getByName("8.8.8.8"); List<InetAddress> addresses = Arrays.asList(mNetwork.getAllByName(hostname, new InetAddress[]{dnsServer})); Log.i("NetworkDns", "解析结果:" + addresses); return addresses; } return SYSTEM.lookup(hostname); }
如果改用公共DNS后请求成功,说明你当前的移动网络DNS解析存在问题,需要调整DNS配置。
6. 禁用OkHttp连接复用
OkHttp默认会复用连接池中的连接,可能导致旧的WiFi连接被复用,干扰新的Network绑定。可以临时禁用连接复用测试:
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder() .connectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) .readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS) .sslSocketFactory(mSSLSocketFactory, new MyTrustManager()) .dns(mNetworkDns) .hostnameVerifier(new MyHostnameVerifier()) .connectionPool(new ConnectionPool(0, 1, TimeUnit.NANOSECONDS)); // 禁用连接复用
如果这样能解决问题,后续可以考虑为绑定Network的OkHttpClient单独创建一个连接池,避免和默认连接池冲突。
内容的提问来源于stack exchange,提问作者Mathieu H.

