我有一个需要与 wifi 和移动数据网络通信的汽车配套应用程序。
我的车辆控制单元提供了一个没有互联网访问权限的 wifi 网络,它公开了一个我们可以从应用程序调用的 API 服务。
除此之外,我们需要使用电话移动数据(3G/4G)与另一个可从互联网访问的后端进行通信。
我立即注意到一些 android 手机,当连接到没有互联网的 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 网络的连接,如果另一个网络可用,则连接到该网络。
如果用户点击"is"选项,我们可以有两种不同的行为:
然后我尝试了同样的事情,但是 使用编程连接 .
我的不同 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 必须处于 Activity 状态。
我立即注意到使用编程连接时,本地弹出窗口不再出现,但在许多设备上,连接后,移动数据连接被 android“禁用”。
我想这种行为是系统想要的,并且是由android更喜欢wifi而不是计量连接的事实决定的。
我对此没有意见,但是在我的 wifi 网络无法访问互联网的情况下会发生什么?其他需要连接的应用程序停止工作,因为它们无法访问互联网。
我需要一个解决方案,允许我的应用程序通过 wifi 和 4g 进行通信,而不会阻止其他应用程序正常工作。
我的最小 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()”,我们稍后再讨论。
随后我创建了两种不同的改造服务:
此时在我的应用程序中,我能够重定向通过改造的流量,
但是我在项目中包含的库如何理解他们应该使用哪个网络?
答:他们不明白,实际上他们尝试使用wifi网络时出现超时错误。
当我将谷歌地图添加到我的应用程序时,我注意到了这种行为, Canvas 只显示了一个空网格。
由于无法通过我之前创建的公共(public)改造服务重定向谷歌地图流量,我不得不寻找另一种解决方案来解决这个问题。
这里是您之前见过的 ConnectivityManager.bindProcessToNetwork() !
https://developer.android.com/reference/android/net/ConnectivityManager#bindProcessToNetwork(android.net.Network) .
通过这种方法,我可以告诉我的应用程序的进程,将来创建的所有套接字都必须使用来自移动数据回调的网络。
使用这个技巧,谷歌地图和我无法控制连接的所有其他图书馆,他们将使用数据连接与互联网通信,因此他们将正常工作。
在某些手机中,尤其是旧版本的 android,仍然存在其他应用程序无法连接的问题,因为它们尝试使用 wifi 而不是使用移动数据。
作为用户,如果使用一个应用程序而所有其他应用程序都不再工作,我会感到非常沮丧。
所以我想了解是否有一种方法可以满足我的所有要求,无论 Android 版本和手机供应商如何,我的应用程序和其他应用程序都可以正常工作。
综上所述,我的问题是:
最佳答案
首先,关于你的问题:
在我看来,您正在寻找的是更改 Android 的默认路由机制。
也就是说,您希望所有到 WiFi 网络内服务器的流量都路由到 WiFi 网络,而所有其他流量都通过移动数据接口(interface)路由。有几种方法可以实现这一点:
关于Android,在连接到 wifi 时与移动数据通信,无需互联网访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61773466/