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操作"//网络名称
    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))

就一句话搞定

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341