【问题标题】:Android, communicate with mobile data while connected to wifi without internet accessAndroid,在没有互联网访问的情况下连接到 wifi 时与移动数据通信
【发布时间】:2020-08-29 13:51:24
【问题描述】:

我有一个需要与 wifi 和移动数据网络通信的汽车伴侣应用。

我的车辆控制单元提供了一个没有互联网访问权限的 wifi 网络,它公开了一个我们可以从应用程序调用的 API 服务。 除此之外,我们还需要使用手机移动数据 (3G/4G) 与另一个可通过互联网访问的后端进行通信。

我立即注意到,当连接到没有互联网的 wifi 网络时,使用 android 设置菜单会显示一个系统对话框,通知用户当前网络无法访问互联网。在这里用户有两个选择:保持这个 wifi 网络或断开连接并切换到另一个。

这里有一些例子:

Samsung J7 - Android 7.0

Motorola moto G7 power - Android 9.0

Xiaomi mi 9T - Android 10

Huawei p9 lite - Android 6.0

经过简短的分析,我了解到如果用户单击“否”选项,系统会断开与当前 wifi 网络的连接,如果另一个网络可用,请连接到此。

如果用户点击“是”选项,我们可以有两种不同的行为:

  1. 手机一直连接到 wifi 网络而无法访问互联网,并且所有应用程序都无法再与互联网通信,因为 android 尝试使用 wifi 接口。
  2. 手机在没有互联网访问的情况下保持连接到 wifi 网络,但 android 系统重新绑定了所有现有的套接字以及将来在移动数据网络上打开的套接字(如果 sim 可用)。

然后我尝试了同样的方法,但 使用了编程连接。 我的不同android版本的示例代码:

fun connectToWifi(ssid: String, key: String) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        connectPost10(ssid, key)
    } else {
        connectPre10(ssid, key)
    }
}

@RequiresApi(Build.VERSION_CODES.O)
private fun connectPost10(ssid: String, wpa2Passphrase: String) {

    val specifier = WifiNetworkSpecifier.Builder()
            .setSsid(ssid)
            .setWpa2Passphrase(wpa2Passphrase)
            .build()

    val request = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .setNetworkSpecifier(specifier)
            .build()

    val networkCallback = object: ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            val networkSSID = wifiManager.connectionInfo.ssid
                .trim()
                .removeSurrounding("\"")

            if (networkSSID == "MY_NETWORK_WITHOUT_INTERNET_SSID") {
                // i'm connected here
            }
        }
    }

    connectivityManager.requestNetwork(request, networkCallback)
}

private fun connectPre10(ssid: String, key: String) {

    // setup wifi configuration
    val config = WifiConfiguration().apply {
        SSID = "\"$ssid\""
        preSharedKey = "\"$key\""
    }

    val networkId = wifiManager.addNetwork(config)

    wifiManager.disconnect() // disconnect from current (if connected)
    wifiManager.enableNetwork(networkId, true) // enable next attempt
    wifiManager.reconnect()
}

请注意,为了读取网络 ssid,android 需要 ACCESS_FINE_LOCATION 权限并且 GPS 必须处于活动状态。

我立即注意到,使用编程连接时,原生弹出窗口不再显示,但在许多设备上,连接后,移动数据连接被 android“禁用”。

我想这种行为是系统想要的,并且是由 android 更喜欢 wifi 而不是计量连接这一事实决定的。 我对此没意见,但是在wifi网络无法访问互联网的情况下会发生什么?其他需要连接的应用程序停止工作,因为它们无法连接到互联网。

我需要一个解决方案,允许我的应用程序通过 wifi 和 4g 进行通信,而不会阻止其他应用程序正常工作。 我的 min sdk api 级别是 23 (Marshmallow),目标是 29 (Android 10)。

我设法通过保存来自连接管理器上注册的回调的网络来解决问题。

val connectivityManager by lazy {
    MyApplication.context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}

private val wifiNetworkCallback = object : ConnectivityManager.NetworkCallback() {
    // Called when the framework connects and has declared a new network ready for use.
    override fun onAvailable(network: Network) {
        super.onAvailable(network)
        listener?.onWifiConnected(network)
    }
    // Called when a network disconnects or otherwise no longer satisfies this request or callback.
    override fun onLost(network: Network) {
        super.onLost(network)
        listener?.onWifiDisconnected()
    }
}

private val mobileNetworkCallback = object : ConnectivityManager.NetworkCallback() {
    // Called when the framework connects and has declared a new network ready for use.
    override fun onAvailable(network: Network) {
        super.onAvailable(network)
        connectivityManager.bindProcessToNetwork(network)
        listener?.onMobileConnected(network)
    }

    // Called when a network disconnects or otherwise no longer satisfies this request or callback.
    override fun onLost(network: Network) {
        super.onLost(network)
        connectivityManager.bindProcessToNetwork(null)
        listener?.onMobileDisconnected()
    }
}

private fun setUpWifiNetworkCallback() {

    val request = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        .build()

    try {
        connectivityManager.unregisterNetworkCallback(wifiNetworkCallback)
    } catch (e: Exception) {
        Log.d(TAG, "WiFi Network Callback was not registered or already unregistered")
    }

    connectivityManager.requestNetwork(request, wifiNetworkCallback)
}

private fun setUpMobileNetworkCallback() {

    val request = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        .build()

    try {
        connectivityManager.unregisterNetworkCallback(mobileNetworkCallback)
    } catch (e: Exception) {
        Log.d(TAG, "Mobile Data Network Callback was not registered or already unregistered")
    }

    connectivityManager.requestNetwork(request, mobileNetworkCallback)
}

标记这个“connectivityManager.bindProcessToNetwork()”,我们稍后再讨论。

随后我创建了两种不同的改造服务:

  1. Private Retrofit:绑定在我从 wifi 回调收到的网络对象上,公开 api 以在本地网络中与我的车辆通信。
  2. Public Retrofit:绑定在我从移动数据回调中收到的网络对象上,公开 api 以与我的后端和其他需要互联网的东西进行通信。

此时,在我的应用程序中,我能够重定向通过改造的流量, 但是我在项目中包含的库如何理解他们应该使用哪个网络? 答:他们不明白,实际上他们尝试使用wifi网络时出现超时错误。

当我将谷歌地图添加到我的应用程序时,我注意到了这种行为,画布只显示了一个空网格。 由于无法通过我之前创建的公共改造服务重定向谷歌地图流量,我不得不寻找另一种解决方案来解决这个问题。

这里是您之前见过的 ConnectivityManager.bindProcessToNetwork()! https://developer.android.com/reference/android/net/ConnectivityManager#bindProcessToNetwork(android.net.Network).

通过这种方法,我可以告诉我的应用程序的进程,将来创建的所有套接字都必须使用来自移动数据回调的网络。

使用这个技巧,谷歌地图和我无法控制连接的所有其他图书馆,它们将使用数据连接与互联网通信,因此它们将正常工作。

在某些手机中,尤其是旧版本的 android,仍然存在其他应用程序无法连接的问题,因为它们尝试使用 wifi 而不是使用移动数据。 作为一个用户,如果使用一个应用程序所有其他应用程序都不再工作,我会感到非常沮丧。

所以我想了解是否有一种方法可以满足我的所有要求,使我的应用程序和其他应用程序都可以正常工作,而不管 Android 版本和手机供应商如何。

总之,我的问题是:

  1. 如果以编程方式建立连接,为什么不会出现“没有 Internet 访问的网络”弹出窗口?
  2. 如果 Android 知道我的 WiFi 无法访问互联网,为什么其他应用程序不自动使用移动数据网络作为后备?
  3. 是否可以告诉 android 其他应用程序必须使用某个网络才能打开未来的套接字?
  4. 每个供应商都有自定义 WiFi 设置以提供增强的互联网体验(Huawei WiFi+Samsung Adaptive WiFiOppo WiFi Assistant,...)。我注意到在某些手机中激活它可以解决其他应用程序的问题,这些功能似乎有权在特定的网络接口上重新绑定整个应用程序生态系统。这些功能如何帮助/阻碍我?是否可以编写一些代码来完成这些功能的功能?

【问题讨论】:

  • 由于某种原因,android 变得越来越受限。限制随着物联网和这些天出现的所有好东西而出现的可能性。我们在一个类似的问题上遇到了很多困难,但是对于移动热点......你猜怎么着......经过几个月的讨论和乒乓球,实际上没有任何改进

标签: android networking mobile wifi iot


【解决方案1】:

首先,关于您的问题:

  1. 此行为保留给系统应用程序。
  2. Android 知道与 WiFi 网络的连接正常。它不会进一步检查以验证与外部世界没有连接。顺便说一句,这实际上并不总是理想的行为。
  3. 是的,见下文
  4. 在某些方面是的,见下文

在我看来,您正在寻找的是更改 Android 的默认路由机制。 也就是说,您希望 WiFi 网络内到服务器的所有流量都路由到 WiFi 网络,而所有其他流量都通过移动数据接口路由。有几种方法可以实现这一点:

  1. 如果您的应用程序是车辆信息娱乐系统的一部分,并且可以拥有系统权限,或者在有根 Android 手机上,您可以使用 ip route 命令直接更改路由表。

  2. 您所描述的实际上是虚拟专用网络 (VPN) 功能的一部分。您可以基于 OpenVPN 等开源解决方案在服务器端和客户端自己实现 VPN 服务,其中 VPN 服务器将位于 wifi 网络内。 Android 具有用于实现 VPN 客户端的预构建基础架构:https://developer.android.com/guide/topics/connectivity/vpn

  3. 您可以使用商业 VPN 解决方案。其中一些允许您正在寻找的配置,我相信会满足您描述的需求。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-24
    • 1970-01-01
    • 2016-12-30
    • 1970-01-01
    • 2019-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多