用 WifiManager 代码连接热点的一些坑

最近在做两个设备间连接热点的工作 ,记录下一些坑的情况

版本情况差异

Android P(28) 之前
连接的情况
wifiManager.disconnect()
wifiManager.enableNetwork(netId, true)
wifiManager.reconnect()
如果用这种方法,你会发现会多次弹出wlan授权弹框,不胜其烦。
而且有些厂商的rom如果你连接到一个不是internet可用的网络热点,会给你自动切换回原来可以用的wifi网络,额
如果用反射,可以做到只谈一次框,并且被系统自动切回来的概率会小很多,对的,你没看错,只是小很多。
断开连接的情况
wifiManager.disconnect()
你会发现断开操作在某些机型上不好使,那怎么解决呢,可以手动将此热点网络移除,这里有一个前提,这个热点是你自己addConfig 添加的,否则没有权限断开,
美中不足是又会被弹框授权一次。
try {
     val mWifiConfigList = wifiManager.configuredNetworks
      for (item in mWifiConfigList) {
          if (item.SSID != null && item.SSID.contains("你的热点名称", true)) {
              Timber.d("item.SSID %s", item.SSID)
              wifiManager.removeNetwork(item.networkId)
          }
      }
      wifiManager.disconnect()
      } catch (e: Exception) {
          e.printStackTrace()
      }
Android P(android 28)
android 在 android P及之后禁止随意java 反射,这里推荐一个反射库
android P(28) 上使用反射的库  https://github.com/LSPosed/AndroidHiddenApiBypass
var connectMethod: Method? = null
for (methodSub in wifiManager.javaClass.declaredMethods) {
    if ("connect".equals(methodSub.name, ignoreCase = true)) {
        val types = methodSub.parameterTypes
        if (types.isNotEmpty()) {
            if ("int".equals(types[0].name, ignoreCase = true)) {
                connectMethod = methodSub
                break
            }
        }
    }
}

if (connectMethod != null) {
     Timber.d(
          "connectAP connectMethod %s Build.VERSION.SDK_INT %s",
          connectMethod,
          Build.VERSION.SDK_INT
      )
      try {
          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
              connectMethod.invoke(wifiManager, netId, null)
          } else {
              // android P(28) 之后反射被禁止,需要绕过
              HiddenApiBypass.invoke(
                  WifiManager::class.java,
                  wifiManager,
                  "connect",
                  netId,
                  null
              )
          }
      } catch (e: Exception) {
          Timber.d("connectAP  反射 exception %s", e.printDetail())
      }
  } else {
      Timber.d("connectAP 此版本 %s 未找到反射方法", Build.VERSION.SDK_INT)
  }
Android Q(29) 之后
[WifiNetworkSpecifier官方链接](https://developer.android.com/guide/topics/connectivity/wifi-bootstrap?hl=zh-cn) 
android Q之后 ,系统推荐使用 WifiNetworkSpecifier 去连接,可以做到只是你的这个app 去使用这个热点,其他app还可以走4g。
连接热点,
这里有个坑 就是  网络连接断开不反注册监听的话, 当连上wifi 热点后,手动关掉wifi开关, 再打开的时候,系统会自动重连
val cm: ConnectivityManager =
            context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE)
                as ConnectivityManager

        val builder: WifiNetworkSpecifier.Builder = WifiNetworkSpecifier.Builder()
        builder.setSsid(ssid)
        builder.setWpa2Passphrase(password)

        val request: NetworkRequest = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .setNetworkSpecifier(builder.build())
            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
            .build()

networkCallback =
            object : ConnectivityManager.NetworkCallback() {
                override fun onAvailable(network: Network) {
                    super.onAvailable(network)
                    curNetwork = network
                    Timber.e("ConnectivityManager.NetworkCallback Ap热点 onAvailable:  network: $network")
                    cm.bindProcessToNetwork(network) // 绑定进程以后,默认dns解析,创建socket等都会走这个网络
                    action?.invoke(available, network)
                }

                override fun onUnavailable() {
                    super.onUnavailable()
                    Timber.e("ConnectivityManager.NetworkCallback Ap热点 onUnavailable ")
                    curNetwork = null
                    cm.bindProcessToNetwork(null)
                    // 网络连接断开不反注册监听的话, 当连上wifi 热点后,手动关掉, 再打开的时候,会自动重连
                    cm.unregisterNetworkCallback(this)
                    action?.invoke(unAvailable, null)
                }

                override fun onLost(network: Network) {
                    super.onLost(network)
                    Timber.e("ConnectivityManager.NetworkCallback Ap热点 onLost:  network: $network")
                    curNetwork = null
                    cm.bindProcessToNetwork(null)
                    // 网络连接断开不反注册监听的话, 当连上wifi 热点后,手动关掉, 再打开的时候,会自动重连
                    cm.unregisterNetworkCallback(this)
                    action?.invoke(lost, network)
                }

                override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
                    super.onBlockedStatusChanged(network, blocked)
                    Timber.d(
                        "ConnectivityManager.NetworkCallback Ap热点  onBlockedStatusChanged network %s  blocked %s",
                        network,
                        blocked
                    )
                }
            }
        cm.requestNetwork(request, networkCallback!!)

连接速度差异

这里不是指网速,是指从开始去连接到连上热点的耗时

Android Q(29) 之前

都是用wifiManager 去连接,所以速度差别不大,应该就是几秒钟的样子

Android Q(29) 之后

如果用上面的WifiNetworkSpecifier去连接,你会发现在某些机型上,首次链接可能需要20秒,在某些android S(30) 的机型上,可能会快很多,大概是几秒的样子,

这里有分两种情况,是首次链接的热点还是已经连接过的热点,基本上再次连接都会很快,几秒钟的样子

首次链接的速度到底有没有办法提升呢,答案是有的?

手段一 WifiNetworkSuggestion

WifiNetworkSpecifier官方文档参考
用WifiNetworkSpecifier 连接之前先去 添加 WifiNetworkSuggestion
这种方法对部分 Android Q 之后的机型好用,而且感觉是越新的android 系统 越好使,android S 上提升很明显。首次会有一个系统弹框,在华为鸿蒙上没有。

val nowTime = System.currentTimeMillis()
        val suggestion1 = WifiNetworkSuggestion.Builder()
            .setSsid(ssid)
            .setWpa2Passphrase(password)
            .setIsAppInteractionRequired(false) // Optional (Needs location permission)
            .build()

        val suggestionsList = listOf(suggestion1)

        val wifiManager = context.getSystemService(WIFI_SERVICE) as WifiManager

        val status = wifiManager.addNetworkSuggestions(suggestionsList)
手段二 先扫再连,
private fun getScanResult(ssid: String, context: Context): Flow<ScanResult?> {
        return callbackFlow {
            val intentFilter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
            val wifiManager = context.getSystemService(WIFI_SERVICE) as WifiManager
            var index = 0
            var scanJob: Job? = null
            val receiver = object : BroadcastReceiver() {

                override fun onReceive(context: Context, intent: Intent) {

                    Timber.d("afterAndroidQConnect onReceive intent %s ", intent)
                    val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
                    Timber.d("afterAndroidQConnect onReceive success %s ", success)

                    val findMatch = wifiManager.scanResults.find {
                        it.SSID == ssid
                    }

                    if (findMatch != null) {
                        Timber.d("afterAndroidQConnect onReceive findMatch %s ", findMatch)
                        scanResultList.clear()
                        scanResultList.add(findMatch)
                        closeScanReceiver(context, this)
                        trySend(findMatch)
                        close()
                    } else {
                        scanJob?.cancel()
                        scanJob = launch {
                            if (index < 2) {
                                delay(startScanDelay)
                                index++
                                Timber.d("afterAndroidQConnect onReceive 没找到热点,startScan() again  重试: %s", index)
                                wifiManager.startScan()
                            }
                        }
                    }
                }
            }

            val starScanResult = wifiManager.startScan()
            Timber.d("afterAndroidQConnect startScan 开始 starScanResult %s", starScanResult)
            context.registerReceiver(receiver, intentFilter)

            // 设置超时时间
            delay(closeScanBroadcastDealy)
            Timber.d("afterAndroidQConnect startScan 超时")
            trySend(null)
            close()
            awaitClose {
                Timber.d("afterAndroidQConnect callbackFlow awaitClose")
                closeScanReceiver(context, receiver)
            }
        }
    }

private fun closeScanReceiver(context: Context, receiver: BroadcastReceiver) {
        try {
            context.unregisterReceiver(receiver)
        } catch (e: IllegalArgumentException) {
            Timber.e(e.printDetail())
        }
    }

可能大家觉得,我去连热点WifiNetworkSpecifier 已经帮我去扫描并连接了,为啥我还要手动扫描, 答案就是,先扫描能大大加速连接的过程。

这里分两种情况,

wifiManager.startScan()返回指定热点结果

① wifiManager.startScan() 之后,BroadcastReceiver onReceive 方法给你返回找到的scanResult的, 你可能纳闷,找到这个有啥用?

onReceive 返回你给指定的热点结果,这个时候, 还记得我们原来连接热点的方式么?这个时候就可以加一个选项,有了这个选项,就能加速你的热点连接,巨大的加速, 这就是
.setBssid(MacAddress.fromString(scanResult.BSSID))
此项加速连接热点的进程, 如果已经连接过,并且缓存过的话,更是几乎可以秒连

val builder: WifiNetworkSpecifier.Builder = WifiNetworkSpecifier.Builder()
    builder.setSsid(ssid)
    builder.setWpa2Passphrase(password)
    // 此项加速连接热点的进程, 如果已经连接过,并且缓存过的话,更是几乎可以秒连
    builder.setBssid(MacAddress.fromString(scanResult.BSSID))
wifiManager.startScan()没有指定热点结果

② wifiManager.startScan() 之后 BroadcastReceiver onReceive 没有给出指定热点的扫描结果
虽然没有,但你 再扫描一次,此时,你不设置 setBssid 去连热点,发现速度也有很大提升。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值