Android 9 - 启用移动数据时套接字连接超时

Android 9 - Socket Connect TIMEOUT when Mobile data enabled

我有一个已部署的应用程序在 Android9 上失败。它的部分功能是通过接入点网络配置一个模块,以允许该模块连接到用户的家庭网络。

我有代码可以检测并连接到正确的 WIFI 网络,但是当我尝试打开设备的套接字时,它失败了 - 仅在 Android 9 上并且仅在启用移动数据时。如果我手动禁用设备上的移动数据一切正常。

Socket open() {
    Socket sock = new Socket(Proxy.NO_PROXY);
    try {
        sock.bind(new InetSocketAddress(localIpAddress(), 50000));
    } catch (IOException e) {
        activity.logContent("Warning: Failed to bind socket : " + e.toString());
    }
    try {
        sock.connect(new InetSocketAddress("192.168.17.1", 5555), (int)5000);
    } catch (IOException e) {
        // This catch fires when Mobile Data is on.
        activity.logContent("Connected to " + activity.mWifiManager.getConnectionInfo().getSSID());
        activity.logContent("Couldn't open socket : " + e.toString());
    }
    return sock;
}

我已经尝试过使用和不使用 Proxy.NO_PROXY 以及使用和不使用 bind() 调用。如果缺少绑定调用,则错误意味着套接字正在尝试通过单元网络进行连接。 (注意:activity.logContent() 是一个屏幕日志,因此在未连接到调试器时更容易看到发生了什么)。

知道出了什么问题吗?

在Android9中有一个关于网络的安全配置:Android security config

在 network_security_config 中添加您的域可能会解决您的问题。我的 network_security_config.xml:

里有这个
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">119.xxx.xxx.xxx</domain>
    </domain-config>
</network-security-config>

我不确定发生这种情况的确切原因。但是,当您打开移动数据并且仅使用移动数据连接到互联网时(考虑到您的 wifi 已关闭),它会从蜂窝网络获取 IP 地址,该网络不再连接到您的家庭网络。因此,预计这种超时情况是微不足道的,因为它无法到达以 192.168.... 开头的家庭网络的私有 IP 地址。

现在我的困惑是,即使打开了移动数据,并且同时打开了 wifi 和移动数据,设备也应该默认连接到 wifi。

因此我建议您检查以下内容。

  • Android 9 (Pie) 引入了特殊的 Wifi preference,它可以防止自动连接到 public 网络。您可以考虑检查设置。
  • 请检查您设备的IP地址,是否有192.168....开头的IP地址。如果没有,那么肯定是您从蜂窝网络获取 IP 地址,因此它无法访问您家庭网络的私有 IP 地址。

经过几天的诅咒,我相信我已经找到了问题所在,因此找到了解决方案:

问题是由于 android 的版本发生了一些变化(我假设是 9.0,即使在 API 21 上发生了其他变化),特别是在创建套接字时,如果系统检测到有 "better" 网络(访问互联网、高信号等),套接字创建指的是该网络,而不是您想要的 wifi 网络。

我寻找在 wifi 网络(这是我想要的网络)上强制创建套接字的方法,我找到的唯一方法是:

简单地说代替:

Socket sock = new Socket();

做:

ConnectivityManager connectivity = (ConnectivityManager) MyApp.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
                if (connectivity != null)
                {
                    for (Network network : connectivity.getAllNetworks())
                    {
                        NetworkInfo networkInfo = connectivity.getNetworkInfo(network);

                        if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI)
                        {
                            if (networkInfo.isConnected())
                            {
                                Socket sock =  network.getSocketFactory().createSocket();
                            }
                        }
                    }
            }

实际浏览设备中存在的网络,当您找到活动的 wifi 时,除了利用此功能确保获得正确的插座外,您什么都不做:

getSocketFactory().createSocket()

现在你有了工作套接字!

在我的例子中,它现在工作得很好,如果有人找到更好的解决方案,欢迎,但现在这是我发现让一切都像以前版本的 android 一样工作的唯一方法。