最近在做两个设备间连接热点的工作 ,记录下一些坑的情况
wifiManager.disconnect()
wifiManager.enableNetwork(netId, true)
wifiManager.reconnect()
如果用这种方法,你会发现会多次弹出wlan授权弹框,不胜其烦。
而且有些厂商的rom如果你连接到一个不是internet可用的网络热点,会给你自动切换回原来可以用的wifi网络,额
如果用反射,可以做到只谈一次框,并且被系统自动切回来的概率会小很多,对的,你没看错,只是小很多。
wifiManager.disconnect()
你会发现断开操作在某些机型上不好使,那怎么解决呢,可以手动将此热点网络移除,再断开,
美中不足是又会被弹框授权一次。
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 在 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之后 ,系统推荐使用 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!!)
这里不是指网速,是指从开始去连接到连上热点的耗时
都是用wifiManager 去连接,所以速度差别不大,应该就是几秒钟的样子
如果用上面的WifiNetworkSpecifier去连接,你会发现在某些机型上,首次链接可能需要20秒,在某些android S(30) 的机型上,可能会快很多,大概是几秒的样子,
这里有分两种情况,是首次链接的热点还是已经连接过的热点,基本上再次连接都会很快,几秒钟的样子
首次链接的速度到底有没有办法提升呢,答案是有的?
用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() 之后,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() 之后 BroadcastReceiver onReceive 没有给出指定热点的扫描结果
虽然没有,但你 再扫描一次,此时,你不设置 setBssid 去连热点,发现速度也有很大提升。