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

版本情况差异

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 {

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 去连热点,发现速度也有很大提升。

文章链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。