在 Android Q 上结合 WiFi 说明符和建议 API

Luj*_*a93 5 android internet-connection android-networking android-wifi android-10.0

我正在构建一个应用程序,其中与第二个设备的连接是本质。因此,我使用了WifiNetworkSpecifier API。但是,一旦用户离开并返回 Wi-Fi 边界,应用程序必须能够自动重新连接到目标网络。因此,我使用了WifiNetworkSuggestion API。但是,我在那里遇到了几个问题:

  1. 使用说明符 API 连接到 SSID 并确认建议 API 生成的推送通知后,建议 API 似乎不起作用,直到我手动断开与 SSID 的连接(取消注册先前分配给说明符请求的网络回调)或杀死应用程序。
  2. 如果用户之前使用 OS Wi-Fi 管理器(例如热点)连接到的外围存在另一个网络,Android 将优先考虑该网络,因此我的应用程序的建议 API 永远不会自动重新连接到想要的和可访问的 SSID。

到目前为止,根据我的经验和理解(这可能是错误的),似乎我们必须手动取消注册先前分配给说明符请求的网络回调,或者终止应用程序,然后让建议 API 完成它的工作,直到它可以工作适当地。如果边界中存在其他网络(用户之前使用 OS Wi-Fi 管理器连接到该网络),这可能会出现问题。在这种情况下,我们永远不会自动重新连接到应用程序定义的 SSID,建议 API 也永远不会工作。

问题是:如何将这两个 API 结合起来,以便能够连接到 SSID,但又可以自动重新连接,而不会进行手动断开用户连接或杀死应用程序等丑陋的黑客攻击,这也不能给我们任何保证?

在我看来,这个使用新网络 API 的全新实现做得不好,它给开发人员带来了很多问题和限制,或者至少它的文档很差。

这是用于发出请求的代码。请注意,我连接的设备没有实际的互联网访问权限,它只是用作 p2p 网络。

@RequiresApi(api = Build.VERSION_CODES.Q)
private fun connectToWiFiOnQ(wifiCredentials: WifiCredentials, onUnavailable: () -> Unit) {

    val request = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .setNetworkSpecifier(createWifiNetworkSpecifier(wifiCredentials))
        .build()

    networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            super.onAvailable(network)
            connectivityManager.bindProcessToNetwork(network)
        }

        override fun onUnavailable() {
            super.onUnavailable()
            onUnavailable.invoke()
        }
    }

    networkCallback?.let {
        addNetworkSuggestion(wifiCredentials)
        connectivityManager.requestNetwork(request, it)
    }
}

@RequiresApi(api = Build.VERSION_CODES.Q)
private fun addNetworkSuggestion(wifiCredentials: WifiCredentials) {
    wifiManager.addNetworkSuggestions(listOf(createWifiNetworkSuggestion(wifiCredentials))).apply {
        if (this != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
            if (this == WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP) {
                wifiManager.removeNetworkSuggestions(emptyList())
                addNetworkSuggestion(wifiCredentials)
            }
        }
    }

    suggestionBroadcastReceiver?.let { context.unregisterReceiver(it) }
    suggestionBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action != WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)
                return

            // Post connection processing..
        }
    }

    context.registerReceiver(
        suggestionBroadcastReceiver, IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)
    )
}

@RequiresApi(api = Build.VERSION_CODES.Q)
private fun createWifiNetworkSpecifier(wifiCredentials: WifiCredentials): WifiNetworkSpecifier {
    return when (wifiCredentials.authenticationType.toLowerCase()) {
        WifiCipherType.NOPASS.name.toLowerCase() -> WifiNetworkSpecifier.Builder()
            .setSsid(wifiCredentials.networkSSID)
            .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
            .build()
        WifiCipherType.WPA.name.toLowerCase() -> WifiNetworkSpecifier.Builder()
            .setSsid(wifiCredentials.networkSSID)
            .setWpa2Passphrase(wifiCredentials.password)
            .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
            .build()
        else -> WifiNetworkSpecifier.Builder()
            .setSsid(wifiCredentials.networkSSID)
            .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
            .build()
    }
}

@RequiresApi(api = Build.VERSION_CODES.Q)
private fun createWifiNetworkSuggestion(wifiCredentials: WifiCredentials): WifiNetworkSuggestion {
    return when (wifiCredentials.authenticationType.toLowerCase()) {
        WifiCipherType.NOPASS.name.toLowerCase() -> WifiNetworkSuggestion.Builder()
            .setSsid(wifiCredentials.networkSSID)
            .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
            .build()
        WifiCipherType.WPA.name.toLowerCase() -> WifiNetworkSuggestion.Builder()
            .setSsid(wifiCredentials.networkSSID)
            .setWpa2Passphrase(wifiCredentials.password)
            .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
            .build()
        else -> WifiNetworkSuggestion.Builder()
            .setSsid(wifiCredentials.networkSSID)
            .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
            .build()
    }
}
Run Code Online (Sandbox Code Playgroud)

Don*_*dal 0

调用建议 APIonAvailable对我有用。这样用户就不会同时看到两个弹出窗口。

val networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        connectivityManager.bindProcessToNetwork(network)
        addNetworkSuggestion(wifiCredentials)
    }
}
Run Code Online (Sandbox Code Playgroud)