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


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

    版本情况差异

    Android P(28) 之前

    连接的情况
    wifiManager.disconnect()
    wifiManager.enableNetwork(netId, true)
    wifiManager.reconnect()
    
    • 1
    • 2
    • 3
    如果用这种方法,你会发现会多次弹出wlan授权弹框,不胜其烦。
    而且有些厂商的rom如果你连接到一个不是internet可用的网络热点,会给你自动切换回原来可以用的wifi网络,额
    如果用反射,可以做到只谈一次框,并且被系统自动切回来的概率会小很多,对的,你没看错,只是小很多。
    
    • 1
    • 2
    • 3
    断开连接的情况
    wifiManager.disconnect()
    
    • 1
    你会发现断开操作在某些机型上不好使,那怎么解决呢,可以手动将此热点网络移除,再断开,
    美中不足是又会被弹框授权一次。
    
    • 1
    • 2
    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()
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Android P(android 28)

    android 在 android P及之后禁止随意java 反射,这里推荐一个反射库
    android P(28) 上使用反射的库  https://github.com/LSPosed/AndroidHiddenApiBypass
    
    • 1
    • 2
    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)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    Android Q(29) 之后

    android Q之后 ,系统推荐使用 WifiNetworkSpecifier 去连接,可以做到只是你的这个app 去使用这个热点,其他app还可以走4g。
    连接热点,
    这里有个坑 就是  网络连接断开不反注册监听的话, 当连上wifi 热点后,手动关掉wifi开关, 再打开的时候,系统会自动重连
    
    • 1
    • 2
    • 3
    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!!)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    连接速度差异

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

    Android Q(29) 之前

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

    Android Q(29) 之后

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

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

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

    手段一 WifiNetworkSuggestion

    用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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    手段二 先扫再连,
    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())
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    可能大家觉得,我去连热点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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    wifiManager.startScan()没有指定热点结果

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

  • 相关阅读:
    基于JAVA社区管理与服务计算机毕业设计源码+系统+mysql数据库+lw文档+部署
    模拟电路(详细版)--放大电路的频率效应(RC电路)
    vs2012里面成功编译Notepad++源码
    解析五育融合之下的steam教育模式
    【计算机系统基础1】gdb、gcc简易使用指南
    小波神经网络的基本原理,小波神经网络训练过程
    java发送媒体类型为multipart/form-data的请求
    自定义jenkins镜像提示FontConfiguration.head错误
    微信小程序——后台交互
    【壁纸小程序】搭建自己的壁纸小程序-微信抖音双端
  • 原文地址:https://blog.csdn.net/wangyun522/article/details/125619440