Android開發 WifiHelp 一個封裝好的WiFi功能操作類
阿新 • • 發佈:2022-05-24
前言
基於SDK API 28 版本的使用kotlin建造者模式封裝,將Wifi常用的搜尋,連線,斷開等等功能的封裝。一個類包含全部內容(包括了資料,廣播),已經經過了記憶體洩漏測試,可以直接將整個類複製黏貼到專案中使用。如果發現bug請在這篇部落格中留言。
轉載請註明來源:https://www.cnblogs.com/guanxinjing/p/16307287.html
使用
override fun onDestroy() { super.onDestroy() mWifiHelp.destroy() } override fun onResume() {super.onResume() if (mWifiHelp.isWifiEnabled()){ mWifiHelp.scan() } } private fun initWifi() { mWifiHelp = WifiHelp.Build(this) .setWifiStateChangedListener { when (it) { //WiFi已關閉 WifiManager.WIFI_STATE_DISABLED -> { mAdapter.refreshWifiEnable(false) } //WiFi已開啟 WifiManager.WIFI_STATE_ENABLED -> { mAdapter.refreshWifiEnable(true) } //WiFi狀態未知 WifiManager.WIFI_STATE_UNKNOWN -> { mAdapter.refreshWifiEnable(false) } } } .setNetworkStateChangedListener { mAdapter.refreshConnectWifiStatus( when (it) { NetworkInfo.DetailedState.AUTHENTICATING -> "驗證密碼" NetworkInfo.DetailedState.CONNECTING -> "正在連線" NetworkInfo.DetailedState.CONNECTED -> "" NetworkInfo.DetailedState.DISCONNECTING -> "正在斷開" NetworkInfo.DetailedState.DISCONNECTED -> "" NetworkInfo.DetailedState.FAILED -> "連線失敗" else -> "" } ) } .setScanCallback { mAdapter.isShowLoadIcon(false) mAdapter.refreshData(it) } .setAlreadyConnectionCallback { mAdapter.refreshConnectWifiData(it) } .setErrorAuthenticating { //密碼錯誤 Toast.makeText(this, it, Toast.LENGTH_SHORT).show() } .build() }
程式碼
import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.* import android.net.wifi.* import android.os.Parcelable import kotlinx.coroutines.* class WifiHelp { private lateinit var mWifiManager: WifiManager private var mBuild: Build? = null protected var mWiFiChangeReceiver: WiFiChangeReceiver? = WiFiChangeReceiver() private fun init(build: Build) { mWifiManager = build.mContext?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager mBuild = build build.mContext?.let { registerReceiver(it) } } fun destroy() { unregisterReceiver() mBuild?.mContext = null mBuild?.mWifiStateChangedListener = null mBuild?.mNetworkStateChangedListener = null mBuild?.mScanCallback = null mBuild?.mAlreadyConnectionCallback = null mBuild?.mErrorAuthenticating = null mBuild = null } fun isWifiEnabled(): Boolean { return mWifiManager.isWifiEnabled } fun setWifiEnabled(isEnabled: Boolean) = mWifiManager.setWifiEnabled(isEnabled) /** * 搜尋WiFi */ fun scan() { mWifiManager.isScanAlwaysAvailable mWifiManager.startScan() } /** * 重新整理已經連線的WiFi */ fun refreshConnectWifiData() { GlobalScope.launch(Dispatchers.Main) { val connectionWifi = withContext(Dispatchers.IO) { getConnectionWifi() } mBuild?.mAlreadyConnectionCallback?.invoke(connectionWifi) } } fun connect(wifiData: WifiData) { connect(wifiData, "") } fun connect(wifiData: WifiData, password: String) { val configurationList = mWifiManager.configuredNetworks //已經儲存密碼的WiFi val isSave = configurationList.any { removeQuotationMarks(it.SSID) == wifiData.ssid } if (isSave) { //如果是已經儲存密碼的的WiFi就重新連線 mWifiManager.enableNetwork(wifiData.netId, true) } else { val wifiConfiguration = WifiConfiguration() //清除一些預設wifi的配置 wifiConfiguration.allowedAuthAlgorithms.clear() wifiConfiguration.allowedGroupCiphers.clear() wifiConfiguration.allowedKeyManagement.clear() wifiConfiguration.allowedPairwiseCiphers.clear() wifiConfiguration.allowedProtocols.clear() wifiConfiguration.SSID = addQuotationMarks(wifiData.ssid) when (wifiData.capabilities) { WiFiPwdType.ESS -> { wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) } WiFiPwdType.WAP -> { wifiConfiguration.preSharedKey = addQuotationMarks(password) wifiConfiguration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN) wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP) wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) wifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP) wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP) wifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP) wifiConfiguration.allowedProtocols.set(WifiConfiguration.Protocol.RSN) wifiConfiguration.allowedProtocols.set(WifiConfiguration.Protocol.WPA) wifiConfiguration.status = WifiConfiguration.Status.ENABLED } WiFiPwdType.WEP -> { wifiConfiguration.wepKeys[0] = addQuotationMarks(password) wifiConfiguration.wepTxKeyIndex = 0; wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); } WiFiPwdType.WPA2_EAP -> { //不支援企業WiFi密碼 } } val netId = mWifiManager.addNetwork(wifiConfiguration) mWifiManager.enableNetwork(netId, true) } } /** * 斷開當前連線的網路 */ fun disconnect() { GlobalScope.launch(Dispatchers.IO) { val wifiData = getConnectionWifi() ?: return@launch mWifiManager.disconnect() mWifiManager.disableNetwork(wifiData.netId) mWifiManager.saveConfiguration() //自動重連其他可用網路 mWifiManager.reconnect() } } /** * 更換密碼WiFi */ @SuppressLint("MissingPermission") fun changeWifiPwd(wifiData: WifiData, password: String) { val wifiConfigurationList = mWifiManager.configuredNetworks for (item in wifiConfigurationList) { if (item.SSID == null) { continue } if (item.SSID == addQuotationMarks(wifiData.ssid)) { item.preSharedKey = "\"" + password + "\"" mWifiManager.disconnect() val id = mWifiManager.updateNetwork(item) if (id == -1) { //id如果等於 -1 就說明更新失敗了 return } mWifiManager.enableNetwork(id, true) //啟用連線WiFi mWifiManager.reconnect() } } } /** * 移除儲存的WiFi * * @param ssid */ fun removeSaveWifi(wifiData: WifiData) { val wifiConfigurationList = mWifiManager.configuredNetworks for (item in wifiConfigurationList) { if (item.SSID == addQuotationMarks(wifiData.ssid)) { mWifiManager.disconnect() mWifiManager.removeNetwork(item.networkId) mWifiManager.saveConfiguration() mWifiManager.reconnect() } } } private fun registerReceiver(context: Context) { val intentFilter = IntentFilter() //當前連線WiFi狀態的變化,這個監聽是指當前已經連線WiFi的斷開與重連的狀態 intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION) //WiFi開關狀態 intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION) //訊號變化 intentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION) //搜尋Wifi掃描已完成,並且結果可用 intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) //表明與接入點建立連線的狀態已經改變。請求者狀態是特定於 Wi-Fi 的 intentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION) context.registerReceiver(mWiFiChangeReceiver, intentFilter) } private fun unregisterReceiver() { mBuild?.mContext?.unregisterReceiver(mWiFiChangeReceiver) } /** * WiFi開關狀態 * WifiManager.WIFI_STATE_DISABLED //WiFi已關閉 * WifiManager.WIFI_STATE_DISABLING //WiFi關閉中 * WifiManager.WIFI_STATE_ENABLED //WiFi已開啟 * WifiManager.WIFI_STATE_ENABLING //WiFi開啟中 * WifiManager.WIFI_STATE_UNKNOWN //WiFi狀態未知 */ private fun getWifiState() = mWifiManager.wifiState @SuppressLint("MissingPermission") private suspend fun getScanDevice(): List<WifiData> { val list: List<ScanResult> = mWifiManager.scanResults //獲取WiFi列表 val configurationList = mWifiManager.configuredNetworks //已經儲存密碼的WiFi val connectionWifi = getConnectionWifi() val wifiList = mutableListOf<WifiData>() for (scanResult in list) { if (scanResult.SSID.isNullOrEmpty()) { continue } if (scanResult.SSID == connectionWifi?.ssid) { continue } val isRepeat = wifiList.any { it.ssid == scanResult.SSID } if (!isRepeat) { val configuration = configurationList.find { removeQuotationMarks(it.SSID) == scanResult.SSID } val netId = configuration?.networkId ?: 0 val wifiData = WifiData( netId, scanResult.SSID, formatLevel(scanResult.level), scanResult.BSSID, "", securityType(scanResult.capabilities), configuration != null ) wifiList.add(wifiData) } } //根據是否已經儲存密碼與訊號強度降序排序 wifiList.sortWith(kotlin.Comparator { u1, u2 -> if (u1.isSavePwd) { u2.isSavePwd.compareTo(u1.isSavePwd) } else { u2.rssi.compareTo(u1.rssi) } }) return wifiList } /** * 獲取當前連線的WiFi網路 */ private suspend fun getConnectionWifi(): WifiData? { val wifiInfo = mWifiManager.connectionInfo ?: return null if (wifiInfo.ssid.contains("unknown ssid")){ //在頻繁的切換WiFi與重連WiFi底層會返回 unknown ssid, 這裡將其排除掉 return null } return WifiData( wifiInfo.networkId, removeQuotationMarks(wifiInfo.ssid), formatLevel(wifiInfo.rssi), wifiInfo.bssid, getStringId(wifiInfo.ipAddress), WiFiPwdType.ESS, true ) } /** * 重新整理wifi的全部資料(搜尋的WiFi與當前連線WiFi資料) */ private fun refreshWifiData() { GlobalScope.launch(Dispatchers.IO) { val list = getScanDevice() val connectionWifi = getConnectionWifi() withContext(Dispatchers.Main) { mBuild?.mScanCallback?.invoke(list) mBuild?.mAlreadyConnectionCallback?.invoke(connectionWifi) } } } /** * 移除引號 * * @param content * @return */ private fun removeQuotationMarks(content: String): String { return content.substring(1, content.length - 1) } /** * 新增引號 * * @param content * @return */ private fun addQuotationMarks(content: String): String { return "\"" + content + "\"" } /** * 格式化訊號 * * WifiInfo.MIN_RSSI = -126; * WifiInfo.MAX_RSSI = 200; * * Quality Excellent Good Fair Poor * dBm -30 ~ -61 -63 ~ -73 -75 ~ -85 -87 ~ -97 * * @param rssi * @return */ private fun formatLevel(rssi: Int): Int { return if (rssi < -97) { 0 } else if (rssi < -87) { 1 } else if (rssi < -75) { 2 } else if (rssi < -63) { 3 } else { 4 } } /** * 將idAddress轉化成string型別的Id字串 * * @param idString * @return */ private fun getStringId(idString: Int): String { val sb = StringBuffer() var b = idString shr 0 and 0xff sb.append("$b.") b = idString shr 8 and 0xff sb.append("$b.") b = idString shr 16 and 0xff sb.append("$b.") b = idString shr 24 and 0xff sb.append(b) return sb.toString() } /** * ESS = 開放網路,不加密,無需密碼 * WEP = 舊的加密方式,不推薦使用,僅需密碼 * WAP = 最常見的加密方式,僅需密碼 * WPA2-EAP = 企業加密方式,ID+密碼驗證 */ private fun securityType(capabilities: String?): WiFiPwdType { if (capabilities == null || capabilities.isEmpty()) { return WiFiPwdType.ESS } // 如果包含WAP-PSK的話,則為WAP加密方式 if (capabilities.contains("WPA-PSK") || capabilities.contains("WPA2-PSK")) { return WiFiPwdType.WAP } else if (capabilities.contains("WPA2-EAP")) { return WiFiPwdType.WPA2_EAP } else if (capabilities.contains("WEP")) { return WiFiPwdType.WEP } else if (capabilities.contains("ESS")) { // 如果是ESS則沒有密碼 return WiFiPwdType.ESS } return WiFiPwdType.ESS } class Build(context: Context) { internal var mScanCallback: ((List<WifiData>?) -> Unit)? = null internal var mAlreadyConnectionCallback: ((WifiData?) -> Unit)? = null internal var mWifiStateChangedListener: ((Int) -> Unit)? = null internal var mNetworkStateChangedListener: ((NetworkInfo.DetailedState) -> Unit)? = null internal var mErrorAuthenticating: ((String) -> Unit)? = null internal var mContext: Context? = context /** * 設定WiFi掃描回撥 */ fun setScanCallback(callback: ((List<WifiData>?) -> Unit)): Build { mScanCallback = callback return this } /** * 設定當前正在連線的WiFi回撥 */ fun setAlreadyConnectionCallback(callback: (WifiData?) -> Unit): Build { mAlreadyConnectionCallback = callback return this } /** * 設定連線時密碼錯誤回撥 */ fun setErrorAuthenticating(callback: (String) -> Unit): Build { mErrorAuthenticating = callback return this } /** * WiFi開關狀態監聽 * WifiManager.WIFI_STATE_DISABLED //WiFi已關閉 * WifiManager.WIFI_STATE_DISABLING //WiFi關閉中 * WifiManager.WIFI_STATE_ENABLED //WiFi已開啟 * WifiManager.WIFI_STATE_ENABLING //WiFi開啟中 * WifiManager.WIFI_STATE_UNKNOWN //WiFi狀態未知 */ fun setWifiStateChangedListener(wifiStateChangedListener: (Int) -> Unit): Build { mWifiStateChangedListener = wifiStateChangedListener return this } /** * 當前連線WiFi網路狀態的變化,這個監聽是指當前已經連線WiFi的斷開與重連的狀態 * NetworkInfo.DetailedState.CONNECTED //已經連線 * NetworkInfo.DetailedState.DISCONNECTED //已經斷開 * NetworkInfo.DetailedState.IDLE //空閒中 * NetworkInfo.DetailedState.AUTHENTICATING //認證中 * NetworkInfo.DetailedState.BLOCKED //認證失敗 * NetworkInfo.DetailedState.CAPTIVE_PORTAL_CHECK //連線檢查 */ fun setNetworkStateChangedListener(networkStateChangedListener: (NetworkInfo.DetailedState) -> Unit): Build { mNetworkStateChangedListener = networkStateChangedListener return this } fun build(): WifiHelp { val wifiHelp = WifiHelp() wifiHelp.init(this) return wifiHelp } } /** * WiFi監聽 */ protected inner class WiFiChangeReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { //WiFi開關狀態 if (intent.action == WifiManager.WIFI_STATE_CHANGED_ACTION) { val switchState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0)//得到WiFi開關狀態值 mBuild?.mWifiStateChangedListener?.invoke(switchState) } //當前連線WiFi狀態的變化,這個監聽是指當前已經連線WiFi的斷開與重連的狀態 if (intent.action == WifiManager.NETWORK_STATE_CHANGED_ACTION) { //得到資訊包 val parcelableExtra: Parcelable? = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO) val networkInfo: NetworkInfo = parcelableExtra as NetworkInfo mBuild?.mNetworkStateChangedListener?.invoke(networkInfo.detailedState) //在每次連線成功or連線失敗後更新一次資料 if (networkInfo.detailedState == NetworkInfo.DetailedState.CONNECTED || networkInfo.detailedState == NetworkInfo.DetailedState.DISCONNECTED ) { refreshWifiData() } } //訊號變化 if (intent.action == WifiManager.RSSI_CHANGED_ACTION) { refreshConnectWifiData() } //搜尋完成 if (intent.action == WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) { refreshWifiData() } //請求變化 if (intent.action == WifiManager.SUPPLICANT_STATE_CHANGED_ACTION) { val linkWifiResult = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0) if (linkWifiResult == WifiManager.ERROR_AUTHENTICATING) { mBuild?.mErrorAuthenticating?.invoke("密碼錯誤") } } } } } /** * wifi資料 */ data class WifiData( val netId: Int, //WiFi網路id,只有已經儲存密碼的WiFi才有,用於重連網路 val ssid: String, //WiFi名稱 val rssi: Int, //WiFi訊號強度 0到4 0訊號最差 4訊號最好 val bssid: String?, //WiFi地址 val ipAddress: String?, //ip地址(連線WiFi後才會有) val capabilities: WiFiPwdType, //WiFi加密方式 val isSavePwd: Boolean //是否儲存密碼 ) enum class WiFiPwdType { //加密方式 /** * ESS = 開放網路,不加密,無需密碼 */ ESS, /** * WEP = 舊的加密方式,不推薦使用,僅需密碼 */ WEP, /** * WAP = 最常見的加密方式,僅需密碼 */ WAP, /** * WPA2-EAP = 企業加密方式,ID+密碼驗證 */ WPA2_EAP, }