Android應用內連接WIFI(適配Android10)

2023.9.25更新:
這個文章只能提供作為參考,前段時間調研了一下android應用內連接wifi的情況,
目前有三種方式
1.直接添加wifi配置,直接進行連接(android 10及以后禁止添加wifi配置 想通過此方法在高版本android實現連接 只能將項目target sdk設置為小于等于28 但這樣做又會導致谷歌市場拒絕上架 國內應用市場似乎不受影響)
2.通過suggest進行連接(系統看網絡情況決定是否要連接)
3.通過平p2p方式連接 (無法上網 用于數據傳輸場景 不能用)

代碼只有參考作用 推薦方式1 但要將target sdk設置為小于等于28 如果你想上架谷歌市場,目前沒有一個比較好的方案實現應用內連接


本文主要參考了官方的開發文檔

適用于互聯網連接的 WLAN 建議 API
適用于對等連接的 WLAN 網絡請求 API

一.Android 10版本和10以下關于wifi連接的區別

1.Android10不允許應用添加系統的網絡配置,但是官方提供了一個新的方案來讓應用進行連接wifi

這個新的方案就是“向系統提建議”,就是我在應用中告訴系統,這里有一個wifi可以連接,它的名稱是什么什么,密碼是什么什么。
系統收到這個建議后,會根據不同情況來決定是否要接受這個應用的建議。如果接受了就會發出廣播通知你,提出建議后只需要準備好一個廣播接收器就好了
但是實際測試下來感覺系統很高傲呀,在本身已經連接其他wifi的情況下根本就不會理你,這該怎么辦呢

我在文檔上還發現了一個P2P的連接:適用于對等連接的 WLAN 網絡請求 API
嘗試用這個來進行wifi連接,居然可行,那就暫時先這樣了

2.Android10不允許應用打開/關閉wifi開關

這個沒辦法,Android 10只能打開設置中的wifi界面讓用戶自己打開了

二. 一個wifi連接工具類

使用之前一定要請求位置權限,因為要獲取wifi列表,而根據wifi列表是可以計算出位置信息的

 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

wifi連接工具類:


@SuppressLint("MissingPermission")
class WifiTools {
    //位置權限!!
    companion object {
        val instance: WifiTools by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { WifiTools() }
    }

    private val TAG = "wifi操作"http://網絡名稱
    private val context = App.context
    private val wifiManager =
        context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager

    @SuppressLint("MissingPermission")
    fun connectWifi(ssid: String, password: String) {
        openWifi()
        val resssss=wifiManager.scanResults
        resssss.size
        val scanResult = wifiManager.scanResults.singleOrNull { it.SSID == ssid }
        if (scanResult == null) {
            Toast.makeText(context, context.getString(R.string.search_wifi_fail), Toast.LENGTH_SHORT).show()
            return
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                connectByP2P(ssid, password)
                return
            }
            var isSuccess = false
            //如果找到了wifi了,從配置表中搜索該wifi的配置config,也就是以前有沒有連接過
            //注意configuredNetworks中的ssid,系統源碼中加上了雙引號,這里比對的時候要去掉
            val config =
                wifiManager.configuredNetworks.singleOrNull { it.SSID.replace("\"", "") == ssid }
            isSuccess = if (config != null) {
                //如果找到了,那么直接連接,不要調用wifiManager.addNetwork  這個方法會更改config的!
                wifiManager.enableNetwork(config.networkId, true)
            } else {
                // 沒找到的話,就創建一個新的配置,然后正常的addNetWork、enableNetwork即可
                val padWifiNetwork =
                    createWifiConfig(
                        scanResult.SSID,
                        password,
                        getCipherType(scanResult.capabilities)
                    )
                val netId = wifiManager.addNetwork(padWifiNetwork)
                wifiManager.enableNetwork(netId, true)
            }
            if (isSuccess) {
                Toast.makeText(context, context.getString(R.string.connect_success), Toast.LENGTH_SHORT).show()

            } else {
                Toast.makeText(context, context.getString(R.string.connect_fail), Toast.LENGTH_SHORT).show()

            }

        }
    }

    private fun openWifi() {
        if (!wifiManager.isWifiEnabled) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                //請用戶手動打開wifi
                Toast.makeText(context, context.getString(R.string.open_wifi_hint), Toast.LENGTH_SHORT).show()
                //這里可以使用event bus代替 在activity接收到后 打開wifi的設置界面
                DarkmagicMessageManager.send(MessageAction.OPENWIFISETTING)
            } else {
                wifiManager.isWifiEnabled = true
            }
        }
    }

    private fun startScantWifi() {
        val wifiScanReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                Log.d(TAG, "Wifi掃描完成")
                val results = wifiManager.scanResults//結果

            }
        }
        val intentFilter = IntentFilter()
        intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
        context.registerReceiver(wifiScanReceiver, intentFilter)
        wifiManager.startScan()
    }

    //Android8以下 通過Config連接Wifi
    private fun connectByConfig() {

    }

    //Android10以上 通過P2P連接Wifi
    @RequiresApi(Build.VERSION_CODES.Q)
    private fun connectByP2P(ssid: String, password: String) {
        val specifier = WifiNetworkSpecifier.Builder()
            .setSsid(ssid)
            .setWpa2Passphrase(password)
            .build()
        val request =
            NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .setNetworkSpecifier(specifier)
                .build()

        val connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val networkCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network?) {
                // do success processing here..
                Toast.makeText(context, context.getString(R.string.connect_success), Toast.LENGTH_SHORT).show()
                
            }

            override fun onUnavailable() {
                // do failure processing here..
                Toast.makeText(context, context.getString(R.string.connect_fail), Toast.LENGTH_SHORT).show()
            }
        }

        connectivityManager.requestNetwork(request, networkCallback)

    }

    //Android10以上,通過suggestion連接WIFI
    private fun connectBySug(ssid: String, password: String) {
        val suggestion = WifiNetworkSuggestion.Builder()
            .setSsid(ssid)
            .setWpa2Passphrase(password)
            .setIsAppInteractionRequired(true) // Optional (Needs location permission)
            .build()
        val suggestionsList = listOf(suggestion)
        //wifiManager.removeNetworkSuggestions(suggestionsList)
        val status = wifiManager.addNetworkSuggestions(suggestionsList)
        Log.d(TAG, status.toString())
        if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {

        }
        val intentFilter = IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
        val broadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                if (!intent.action.equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
                    return
                }
            }
        };
        context.registerReceiver(broadcastReceiver, intentFilter);

    }


    private fun createWifiConfig(
        ssid: String,
        password: String,
        type: WifiCapability
    ): WifiConfiguration {
        //初始化WifiConfiguration
        val config = WifiConfiguration()
        config.allowedAuthAlgorithms.clear()
        config.allowedGroupCiphers.clear()
        config.allowedKeyManagement.clear()
        config.allowedPairwiseCiphers.clear()
        config.allowedProtocols.clear()

        //指定對應的SSID
        config.SSID = "\"" + ssid + "\""

        //如果之前有類似的配置
        val tempConfig = wifiManager.configuredNetworks.singleOrNull { it.SSID == "\"$ssid\"" }
        if (tempConfig != null) {
            //則清除舊有配置  不是自己創建的network 這里其實是刪不掉的
            wifiManager.removeNetwork(tempConfig.networkId)
            wifiManager.saveConfiguration()
        }

        //不需要密碼的場景
        if (type == WifiCapability.WIFI_CIPHER_NO_PASS) {
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
            //以WEP加密的場景
        } else if (type == WifiCapability.WIFI_CIPHER_WEP) {
            config.hiddenSSID = true
            config.wepKeys[0] = "\"" + password + "\""
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED)
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
            config.wepTxKeyIndex = 0
            //以WPA加密的場景,自己測試時,發現熱點以WPA2建立時,同樣可以用這種配置連接
        } else if (type == WifiCapability.WIFI_CIPHER_WPA) {
            config.preSharedKey = "\"" + password + "\""
            config.hiddenSSID = true
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
            config.status = WifiConfiguration.Status.ENABLED
        }

        return config
    }

    private fun getCipherType(capabilities: String): WifiCapability {
        return when {
            capabilities.contains("WEB") -> {
                WifiCapability.WIFI_CIPHER_WEP
            }
            capabilities.contains("PSK") -> {
                WifiCapability.WIFI_CIPHER_WPA
            }
            capabilities.contains("WPS") -> {
                WifiCapability.WIFI_CIPHER_NO_PASS
            }
            else -> {
                WifiCapability.WIFI_CIPHER_NO_PASS
            }
        }
    }
}

enum class WifiCapability {
    WIFI_CIPHER_WEP, WIFI_CIPHER_WPA, WIFI_CIPHER_NO_PASS
}

注釋也給的很清晰,簡單說一下,android10采用p2p連接wifi,android10以下采用原本的添加wifi配置的方式進行連接。

用法:

 WifiTools.instance.connectWifi(ssid, password)

三. 后續一些想說的話

以后遇到問題多多研究一下官方的開發者文檔,真的非常詳細且規范。很多問題國內的各家CSDN,博客園,甚至簡書,內容都太陳舊過時了。只有官方我文檔是和系統同步進行更新的。

在實際測試中,發現競品居然可以在android10上直接連接wifi,這是我沒有想到的。也就是說這個P2P連接wifi的方式并非最優解,看來學習之路漫漫呀

對了關于打開系統設置的wifi界面代碼在這里:

startActivity(Intent(android.provider.Settings.ACTION_WIFI_SETTINGS))

就一句話搞定

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。