Android Wifi掃描和連接監(jiān)聽(2022/08/16)

腦圖

wifi_xmind.png

1.WifiManager

基礎(chǔ)功能

interface IWifiManager {
    // wifi是否打開
    fun isWifiEnabled(): Boolean

    // 操作wifi開關(guān)
    fun switchWifi(isOpen: Boolean)

    // 開始掃描附近wifi
    fun startScan()

    // 掃描后邑退,獲取掃描wifi列表
    fun getScanResults(): MutableList<ScanResult>

    // 主動連接wifi品嚣,password為空則為無密碼
    fun connectWifi(wifiBean: WifiBean, password: String?): Boolean

    // 忘記wifi
    fun forgetWifi(wifiBean: WifiBean): Boolean
}

實現(xiàn)流程

1.權(quán)限檢測

  /**
    @description 權(quán)限相關(guān)
     */
    private val wifiPermission = activity.multiplePermissionsForResult()
    private val permissionList = arrayOf(
        Manifest.permission.CHANGE_WIFI_STATE,
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.WRITE_SETTINGS //用于sdk29調(diào)用系統(tǒng)wifi設(shè)置權(quán)限用
    )

activity.multiplePermissionsForResult():

使用了registerForActivityResult(resultContract: ActivityResultContract<I, O>他巨,callback: ActivityResultCallback<O>) 這是新的權(quán)限申請方式居扒,具體可看官方文檔酷含。方法在LifecycleOwner中携添,調(diào)用所需的倆個參數(shù)全蝶,resultContract是請求結(jié)果協(xié)議類涯曲,用于包裹輸入值I和輸出值O雀监,其中輸入值例如權(quán)限傳遞就是String(Manifest.permission.CHANGE_WIFI_STATE)双吆,輸出值是Boolean,返回權(quán)限授權(quán)結(jié)果会前。第二個參數(shù)值callback好乐,是onActivityResult的結(jié)果,用于最終結(jié)果回調(diào)瓦宜。

Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION

這三個權(quán)限是為了兼容8.0蔚万、9.0中所需申請的wifi權(quán)限,用于wifi狀態(tài)的監(jiān)聽和wifi掃描

Manifest.permission.WRITE_SETTINGS //用于sdk29調(diào)用系統(tǒng)wifi設(shè)置權(quán)限用

 private fun checkPermission(block: () -> Unit) {
        if (permissionList.hasPermission()) {
            block.invoke()
        } else {
            wifiPermission.launch(permissionList) {
                // 檢測權(quán)限完整
                var hasPermission = false
                for (entry in it) {
                    if (!entry.value) {
                        hasPermission = false
                        break
                    } else {
                        hasPermission = true
                    }
                }
                if (hasPermission) block.invoke()
            }
        }
    }

2.基礎(chǔ)功能的具體實現(xiàn)

// 系統(tǒng)WifiManager的獲取
    val wifiManager by lazy {
        activity.getSystemService(Context.WIFI_SERVICE) as WifiManager
    }

    override fun isWifiEnabled(): Boolean {
        return wifiManager.isWifiEnabled
    }

    override fun switchWifi(isOpen: Boolean) {
//        新版本如果是android29的临庇,只能通過上面這種凡是操作wifi
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//            val panelIntent = Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY)
//            activity.startActivity(panelIntent)
//        } else {
//            wifiManager.isWifiEnabled = isOpen
//        }
        wifiManager.isWifiEnabled = isOpen
    }

    override fun startScan() {
        checkPermission {
            wifiManager.startScan()
        }
    }

    /**
    @description 獲取系統(tǒng)掃描的wifi列表信息
     */
    override fun getScanResults(): MutableList<ScanResult> {
        //過濾空的ssid并去重
        val resultMap =
            wifiManager.scanResults?.filter { !it.SSID.isNullOrBlank() }?.groupBy { it.SSID }
        val scanResults = mutableListOf<ScanResult>()
        if (resultMap.isNullOrEmpty()) {
            return scanResults
        }

        for ((_, value) in resultMap) {
            // 取首個值
            value.firstOrNull()?.let {
                scanResults.add(it)
            }
        }
        return scanResults
    }

    /**
    @description 連接有密碼的wifi反璃,只支持版本低于29
    @return 是否連接成功
     */
    override fun connectWifi(wifiBean: WifiBean, password: String?): Boolean {
        if (!wifiManager.isWifiEnabled) {
            return false
        }
        // 無密碼連接
        if (password == null) {
            val scanResult = wifiManager.scanResults.filter { it.SSID == wifiBean.ssid }
            scanResult.forEach {
                if (connectScanResult(it)) {
                    return true
                }
            }
            return false
        } else {
            val padWifiNetwork =
                createWifiConfig(
                    wifiBean.ssid ?: "",
                    password,
                    WifiBean.getCapabilityTag(wifiBean.capabilities)
                )
            return wifiManager.enableNetwork(wifiManager.addNetwork(padWifiNetwork), true)
        }
    }

    override fun forgetWifi(wifiBean: WifiBean): Boolean {
        val configurations = wifiManager.configuredNetworks
        for (configuration in configurations) {
            val replace = configuration.SSID.replace("\"", "")
            if (replace == wifiBean.ssid) {
                var removeResult = wifiManager.removeNetwork(configuration.networkId)
                removeResult = removeResult and wifiManager.saveConfiguration()
                return removeResult
            }
        }
        return false
    }

主要對IWifiMananger的接口方法進(jìn)行實現(xiàn),也是定義了對外調(diào)用最近本的幾個方法

3.其他功能方法

/**
@description 主動連接掃描到的wifi
@param scanResult 系統(tǒng)wifi掃描返回的信息類
@return 是否連接成功
*/
private fun connectScanResult(scanResult: ScanResult?): Boolean {
    return if (scanResult != null) {
        // 1.查找曾經(jīng)的wifi配置假夺,已連上系統(tǒng)會存儲下來
        val config = getWifiConfig(scanResult.SSID)
        if (config != null && config.status != WifiConfiguration.Status.DISABLED) {
            if (BuildConfig.DEBUG) {
                logSettingD(TAG, "找到了歷史wifi:{scanResult.SSID}")
            }
            // 1.1找到以后調(diào)用連接
            wifiManager.enableNetwork(config.networkId, true)
        } else {
            // 2.沒有wifi配置淮蜈,判斷當(dāng)前模板wifi加密方式
            val capabilityTag = WifiBean.getCapabilityTag(scanResult.capabilities)
            if (capabilityTag == WifiBean.CapabilityTag.NONE) {
                // 2.1無加密,直接連接
                val padWifiNetwork =
                    createWifiConfig(
                        scanResult.SSID,
                        capabilityTag = WifiBean.getCapabilityTag(scanResult.capabilities)
                    )
                val netId = wifiManager.addNetwork(padWifiNetwork)
                if (BuildConfig.DEBUG) {
                    logSettingD(TAG, "不需要密碼連接wifi:{scanResult.SSID}")
                }
                wifiManager.enableNetwork(netId, true)
                wifiManager.reconnect()
            } else {
                // 2.2有加密已卷,并且曾經(jīng)也沒有wifi配置梧田,自然不需要主動連接
                if (BuildConfig.DEBUG) {
                    logSettingD(TAG, "需要密碼連接wifi:${scanResult.SSID}")
                }
                false
            }
        }
    } else {
        if (BuildConfig.DEBUG) {
            logSettingD(TAG, "connectWifi 沒有找到")
        }
        false
    }
}

/**
@description 將"ssid"替換成ssid
*/
private fun getWifiConfig(ssid: String?): WifiConfiguration? {
    return wifiManager.configuredNetworks.firstOrNull {
        it.SSID.replace("\"", "") == ssid
    }
}

/**
@description 根據(jù)ssid、密碼侧蘸、加密方式裁眯,生產(chǎn)wifi配置信息
*/
private fun createWifiConfig(
    ssid: String,
    password: String = "",
    capabilityTag: WifiBean.CapabilityTag
): WifiConfiguration {
    // 1.初始化WifiConfiguration
    val config = WifiConfiguration()
    config.allowedAuthAlgorithms.clear()
    config.allowedGroupCiphers.clear()
    config.allowedKeyManagement.clear()
    config.allowedPairwiseCiphers.clear()
    config.allowedProtocols.clear()

    //指定對應(yīng)的SSID
    config.SSID = "\"" + ssid + "\""

    // 2.如果之前有類似的配置
    val tempConfig = wifiManager.configuredNetworks.singleOrNull { it.BSSID == "\"$ssid\"" }
    if (tempConfig != null) {
        //則清除舊有配置  不是自己創(chuàng)建的network 這里其實是刪不掉的
        val isDisable = wifiManager.disableNetwork(tempConfig.networkId)
        val isRemove = wifiManager.removeNetwork(tempConfig.networkId)
        val isSave = wifiManager.saveConfiguration()
        if (BuildConfig.DEBUG) {
            logSettingD(TAG, "清除wifi配置:${tempConfig.SSID + (isDisable && isRemove && isSave)}")
        }
    }

    // 3.根據(jù)加密方式生成不同配置
    when (capabilityTag) {
        WifiBean.CapabilityTag.NONE -> {
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
            //以WEP加密的場景
        }
        WifiBean.CapabilityTag.WEP -> {
            config.hiddenSSID = true
            config.wepKeys[0] = "\"" + password + "\""
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED)
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
            config.wepTxKeyIndex = 0
            //以WPA加密的場景
        }
        WifiBean.CapabilityTag.PSK -> {
            config.preSharedKey = "\"" + password + "\""
            config.hiddenSSID = true
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
            config.status = WifiConfiguration.Status.ENABLED
        }
        else -> {

        }
    }
    return config
}

/**
@description 獲取wifi列表信息,WifiBean是期望的數(shù)據(jù)類型
 */
fun getWifiBeanList(): List<WifiBean> {
    //按照強(qiáng)度排序
    val wifiList = getScanResults()
        .sortedByDescending { it.level }
        .map { scanResult ->
            WifiBean.transform(scanResult)
        }

    val connectionInfoSsid = wifiManager.connectionInfo?.ssid?.replace("\"", "")
    logSettingD(WifiProxy.TAG, "getWifiBeanList: connectionInfoSsid = $connectionInfoSsid")

    return changeWifiListBySsid(connectionInfoSsid, WifiBean.ConnectStatus.CONNECTED, wifiList)
}

/**
@description 修改匹配后的wifiBean列表
@param ssid 要匹配的ssid
@param targetStatus 目標(biāo)狀態(tài)
@param list 要修改的wifiList數(shù)據(jù)
 */
fun changeWifiListBySsid(
    ssid: String?,
    targetStatus: WifiBean.ConnectStatus,
    list: List<WifiBean>
): MutableList<WifiBean> {
    val temps: MutableList<WifiBean> = mutableListOf()
    temps.addAll(list)
    // 為空直接返回原有數(shù)據(jù)列表
    if (ssid == null) return temps

    for (i in list.indices) {
        val temp = list[i]
        if (ssid == temp.ssid) {
            temp.connectStatus = targetStatus
            temps.remove(temp)
            temps.add(0, temp)
        } else {
            temp.connectStatus = WifiBean.ConnectStatus.DISCONNECT
        }
    }
    return temps
}

2.WifiScanReceiver

/**
@description Wifi連接狀態(tài)接收器
@param switchListener wifi開關(guān)狀態(tài)監(jiān)聽
@param scanSucListener wifi掃描結(jié)果是否成功
@param connectListener wifi請求連接的狀態(tài)監(jiān)聽
 */
class WifiScanReceiver(
    private val wifiManager: WifiManager,
    private val switchListener: (Boolean) -> Unit,
    private val scanSucListener: (Boolean) -> Unit,
    private val connectListener: (String, Boolean) -> Unit,
) : BroadcastReceiver() {

    companion object {
        const val TAG = "WifiScanReceiver"
    }

    fun register(activity: AppCompatActivity) {
        activity.registerReceiver(this, IntentFilter().apply {
            addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
            addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
            addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)
            addAction(WifiManager.RSSI_CHANGED_ACTION)
        })
    }

    override fun onReceive(context: Context?, intent: Intent?) {
        logSettingD(TAG, "onReceive: action = ${intent?.action}")
        intent?.run {
            when (action) {
                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION -> {
                    scanSucListener.invoke(isScanSuc(intent))
                }
                WifiManager.WIFI_STATE_CHANGED_ACTION -> {
                    handleSwitchChange(intent)
                }
                WifiManager.SUPPLICANT_STATE_CHANGED_ACTION -> {
                    handleSupplicantState(intent)
                }
                WifiManager.RSSI_CHANGED_ACTION ->{

                }
            }
        }
    }

    /**
    @description 處理wifi連接請求的狀態(tài)變化
     */
    private fun handleSupplicantState(intent: Intent) {
        // 密碼錯誤
        val error = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1)
        if (error == WifiManager.ERROR_AUTHENTICATING) {
            wifiManager.connectionInfo?.ssid?.replace("\"", "")?.let {
                logSettingD(TAG, "獲取連接狀態(tài):密碼錯誤")
                connectListener.invoke(it, true)
            }
            return
        }
        // 獲取連接狀態(tài)
        val supplicantState: SupplicantState? =
            intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)
        when (supplicantState) {
            // 成功
            SupplicantState.COMPLETED -> {
                logSettingD(TAG, "獲取連接狀態(tài):成功")
                wifiManager.connectionInfo?.ssid?.replace("\"", "")?.let {
                    connectListener.invoke(it, false)
                }
            }
            // 不活躍的
            SupplicantState.INACTIVE -> {
                logSettingD(TAG, "獲取連接狀態(tài):不活躍的")
            }
            // 接口禁用
            SupplicantState.INTERFACE_DISABLED -> {
                logSettingD(TAG, "獲取連接狀態(tài):接口禁用")
            }
            SupplicantState.DISCONNECTED -> {
                logSettingD(TAG, "獲取連接狀態(tài):斷開連接")
            }
            SupplicantState.SCANNING -> {
                logSettingD(TAG, "獲取連接狀態(tài):正在掃描")
            }
            SupplicantState.AUTHENTICATING -> {
                logSettingD(TAG, "獲取連接狀態(tài):正在驗證")
            }
            SupplicantState.ASSOCIATING -> {
                logSettingD(TAG, "獲取連接狀態(tài):正在關(guān)聯(lián)")
            }
            SupplicantState.ASSOCIATED -> {
                logSettingD(TAG, "獲取連接狀態(tài):已經(jīng)關(guān)聯(lián)")
            }
            SupplicantState.FOUR_WAY_HANDSHAKE -> {
                logSettingD(TAG, "獲取連接狀態(tài):四次握手")
            }
            SupplicantState.GROUP_HANDSHAKE -> {
                logSettingD(TAG, "獲取連接狀態(tài):組握手")
            }
            SupplicantState.DORMANT -> {
                logSettingD(TAG, "獲取連接狀態(tài):休眠")
            }
            SupplicantState.UNINITIALIZED -> {
                logSettingD(TAG, "獲取連接狀態(tài):未初始化")
            }
            SupplicantState.INVALID -> {
                logSettingD(TAG, "獲取連接狀態(tài):無效的")
            }
            else -> {
                logSettingD(TAG, "wifi連接結(jié)果通知")
            }
        }
    }

    /**
    @description 處理wifi掃描結(jié)果是否成功
     */
    private fun isScanSuc(intent: Intent): Boolean {
        if (wifiManager.isWifiEnabled) {
            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
            } else {
                true
            }
        }
        return false
    }

    /**
    @description 處理wifi開關(guān)狀態(tài)變化
     */
    private fun handleSwitchChange(intent: Intent) {
        val state =
            intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)
        logSettingD(TAG, "handleStateChange: state = ${getWifiStateStr(state)}")
        if (state == WifiManager.WIFI_STATE_ENABLED) {
            switchListener.invoke(true)
        } else if (state == WifiManager.WIFI_STATE_DISABLING || state == WifiManager.WIFI_STATE_DISABLED) {
            switchListener.invoke(false)
        }
    }

    private fun getWifiStateStr(state: Int): String {
        return when (state) {
            WifiManager.WIFI_STATE_DISABLED -> "Wifi已關(guān)閉"
            WifiManager.WIFI_STATE_DISABLING -> "Wifi關(guān)閉中"
            WifiManager.WIFI_STATE_ENABLED -> "Wifi已打開"
            WifiManager.WIFI_STATE_ENABLING -> "Wifi打開中"
            WifiManager.WIFI_STATE_UNKNOWN -> "Wifi未知狀態(tài)"
            else -> "Wifi未知狀態(tài)"
        }
    }
}

主要用于配合wifiManager讳癌,獲取到三種監(jiān)聽

1.SCAN_RESULTS_AVAILABLE_ACTION:監(jiān)聽掃描結(jié)果穿稳,掃描成功或失敗會觸發(fā)

2.WIFI_STATE_CHANGED_ACTION:處理wifi開關(guān)狀態(tài)變化時,進(jìn)行回調(diào)處理

3.SUPPLICANT_STATE_CHANGED_ACTION處理wifi連接請求的狀態(tài)變化

sdk29以后也出現(xiàn)了其他新的api監(jiān)聽回調(diào)晌坤,wifiManager.registerXXX()就能監(jiān)聽對應(yīng)狀態(tài)逢艘,但由于不兼容舊設(shè)備,因此全部wifi狀態(tài)都用廣播方式獲取泡仗,廣播獲取頻率會受到具體限制埋虹,具體情況詳見官方文檔

3.ConnectManager

/**
@author: Zed.Qiu
@date: 2022/8/15
@description: 用于管理網(wǎng)絡(luò)狀態(tài)發(fā)生變化的回調(diào),以及處理信號變化回調(diào)
 */
class TyphurConnectManager(private var context: Context = TyphurCoreConst.app) {
    companion object {
        const val TAG = "TyphurConnectManager"

        @Volatile
        var instance: TyphurConnectManager? = null
        fun getInstance(context: Context = TyphurCoreConst.app): TyphurConnectManager {
            if (instance == null) {
                synchronized(TyphurConnectManager::class.java) {
                    if (instance == null) {
                        instance = TyphurConnectManager(context)
                    }
                }
            }
            instance?.context = context
            return instance!!
        }

        /**
        @description 是否有網(wǎng)絡(luò)連接
         */
        fun isNetworkAvailable(context: Context): Boolean {
            val connectivityManager =
                context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // 當(dāng)前是否有網(wǎng)絡(luò)
                val nw = connectivityManager.activeNetwork ?: return false
                val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
                return when {
                    actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
                    // 蜂窩網(wǎng)
                    actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
                    // 以太網(wǎng)
                    actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
                    // vpn
                    actNw.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> true
                    else -> false
                }
            } else {
                return connectivityManager.activeNetworkInfo?.isConnected ?: false
            }
        }
    }


    private var isRegister = false

    /**
    @description 網(wǎng)絡(luò)狀態(tài)變化回調(diào)管理列表
    */
    private val networkCallbackList = mutableListOf<ConnectivityManager.NetworkCallback>()
    /**
    @description 網(wǎng)絡(luò)連接管理器
     */
    private val connectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    private val networkCallback = object :
        ConnectivityManager.NetworkCallback() {
        override fun onUnavailable() {
            super.onUnavailable()
            logSettingD(TAG, "NetworkCallback onUnavailable! 網(wǎng)絡(luò)不可訪問")
            networkCallbackList.forEach { callback ->
                callback.onUnavailable()
            }
        }

        override fun onAvailable(network: Network) {
            super.onAvailable(network)
            // 網(wǎng)絡(luò)可訪問時綁定
            val bindResult = connectivityManager.bindProcessToNetwork(network)
            logSettingD(TAG, "NetworkCallback onAvailable! 網(wǎng)絡(luò)已連接 bindResult = $bindResult")
            networkCallbackList.forEach { callback ->
                callback.onAvailable(network)
            }
        }

        override fun onLosing(network: Network, maxMsToLive: Int) {
            super.onLosing(network, maxMsToLive)
            logSettingD(TAG, "NetworkCallback onLosing! 網(wǎng)絡(luò)斷開連接中")
            networkCallbackList.forEach { callback ->
                callback.onLosing(network, maxMsToLive)
            }
        }

        override fun onLost(network: Network) {
            super.onLost(network)
            logSettingD(TAG, "NetworkCallback onLost! 網(wǎng)絡(luò)已斷開連接")
            networkCallbackList.forEach { callback ->
                callback.onLost(network)
            }
        }

        override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
        ) {
            super.onCapabilitiesChanged(network, networkCapabilities)
            logSettingD(TAG, "NetworkCallback onCapabilitiesChanged! 信號強(qiáng)度發(fā)生變化")
            logSettingD(TAG, "networkCapabilities = $networkCapabilities")
            networkCallbackList.forEach { callback ->
                callback.onCapabilitiesChanged(network, networkCapabilities)
            }
        }
    }
    init {
        register()
    }

    fun register(builder :NetworkRequest.Builder? =null) {
        if (isRegister) return
        if (builder == null){
            registerNetworkCallback()
        }else{
            connectivityManager.registerNetworkCallback(builder.build(), networkCallback)
        }
        isRegister = true
    }

    fun unRegister() {
        connectivityManager.bindProcessToNetwork(null)
        connectivityManager.unregisterNetworkCallback(networkCallback)
        networkCallbackList.clear()
        isRegister = false
    }

    /**
    @description 添加監(jiān)聽回調(diào)
    */
    fun addCallback(callback: ConnectivityManager.NetworkCallback?) {
        callback?.run {
            if (!networkCallbackList.contains(this)) {
                networkCallbackList.add(this)
            }
        }
    }

    /**
    @description 移除監(jiān)聽回調(diào)
     */
    fun removeCallback(callback: ConnectivityManager.NetworkCallback?) {
        networkCallbackList.remove(callback)
    }

    /**
    @description 注冊wifi網(wǎng)絡(luò)狀態(tài)回調(diào)
     */
    private fun registerNetworkCallback() {
        val builder = NetworkRequest.Builder().apply {
            addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
            addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
            addTransportType(NetworkCapabilities.TRANSPORT_VPN)
            addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        }
        connectivityManager.registerNetworkCallback(builder.build(), networkCallback)
    }
}

主要功能:

1.持有系統(tǒng)ConnectivityManager娩怎,并注冊了相關(guān)網(wǎng)絡(luò)變化回調(diào)

2.isNetworkAvailable()搔课,可判斷當(dāng)前網(wǎng)絡(luò)是否可達(dá)

3.生命周期跟隨Application,只需注冊一次,可在任一地方進(jìn)行對網(wǎng)絡(luò)狀態(tài)的監(jiān)聽

調(diào)用方式如下:

// 獲取TyphurConnectManager對象
private val typhurConnectManager by lazy { TyphurConnectManager.getInstance() }

// 添加網(wǎng)絡(luò)回調(diào), isConnect代表是否連接爬泥, level代表信號強(qiáng)度
typhurConnectManager.addCallback(TyphurConnectCallback { isConnect, level ->
    refreshByNetworkChange(isConnect, level)
}})

4.TyphurConnectCallback


/**
@author: Zed.Qiu
@date: 2022/8/15
@description: 默認(rèn)網(wǎng)絡(luò)回調(diào)
@param listener 參數(shù)1 是否聯(lián)網(wǎng) 參數(shù)2 wifi信號等級
 */
class TyphurConnectCallback(var listener: Function2<Boolean, WifiBean.LEVEL, Unit>) : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        super.onAvailable(network)
        onNetworkChange()
    }

    override fun onLosing(network: Network, maxMsToLive: Int) {
        super.onLosing(network, maxMsToLive)
        onNetworkChange()
    }

    override fun onLost(network: Network) {
        super.onLost(network)
        onNetworkChange()
    }

    /**
    @description 網(wǎng)絡(luò)發(fā)生變化
    */
    private fun onNetworkChange() {
        val networkAvailable =
            TyphurConnectManager.isNetworkAvailable(TyphurCoreConst.app)
        TyphurCoreConst.mainScope.launchOnUi {
            listener.invoke(networkAvailable, WifiBean.LEVEL.LEVEL_UNKNOW)
        }
    }

    /**
    @description desc
    @param methodParameters
    @return methodReturnType
    */
    override fun onCapabilitiesChanged(
        network: Network,
        networkCapabilities: NetworkCapabilities
    ) {
        super.onCapabilitiesChanged(network, networkCapabilities)
        TyphurCoreConst.mainScope.launchOnUi {
            val networkAvailable =
                TyphurConnectManager.isNetworkAvailable(TyphurCoreConst.app)
            val level = WifiBean.calculateLevel(networkCapabilities.signalStrength)
            listener.invoke(networkAvailable, level)
        }
    }
}

只是對系統(tǒng)的網(wǎng)絡(luò)回調(diào)做了一層簡單的封裝柬讨,暴露期望的回調(diào)監(jiān)聽提供給其他人

5.WifiBean

1.屬性介紹

屬性名稱 類型 作用
wifiName String wifi名稱
levelValue Int 原始的信號值
ssid String wifi唯一標(biāo)識
bssid String 無線網(wǎng)址的mac地址
connectStatus ConnectStatus ConnectStatus.DISCONNECT默認(rèn)未不連接

2.方法介紹

方法名 作用
fun isCapabilityEAP(capabilities: String?): Boolean 是否為企業(yè)加密wifi eap
fun getCapabilityTag(capabilities: String?): CapabilityTag 獲取加密類型標(biāo)識
fun isCapabilityNone(capabilities: String?): Boolean 是否為不加密wifi
fun getCapabilityStr(capability: CapabilityTag): String 獲取加密方式字符
fun calculateLevel(levelValue: Int): LEVEL 計算信號等級
fun transform(scanResult: ScanResult): WifiBean ScanResult:掃描結(jié)果類轉(zhuǎn)WifiBean

3.具體實現(xiàn)

/**
 * @author zed
 */
@Keep
data class WifiBean(
    var wifiName: String? = null,
    var levelValue: Int = 0,
    var ssid: String? = null,
    var bssid: String? = null,
    var connectStatus: ConnectStatus = ConnectStatus.DISCONNECT,
    /**
     * 加密方式
     */
    var capabilities: String? = null
) {
    companion object {

        const val CAPABILITY_NONE_STR = "NONE"
        const val CAPABILITY_WEP_STR = "WEP"
        const val CAPABILITY_PSK_STR = "PSK"
        const val CAPABILITY_EAP_STR = "EAP"

        /**
        @description 獲取加密類型標(biāo)識
         */
        fun getCapabilityTag(capabilities: String?): CapabilityTag {
            capabilities?.run {
                return when {
                    this.contains(CAPABILITY_WEP_STR) -> CapabilityTag.WEP
                    this.contains(CAPABILITY_PSK_STR) -> CapabilityTag.PSK
                    this.contains(CAPABILITY_EAP_STR) -> CapabilityTag.EAP
                    else -> CapabilityTag.NONE
                }
            }
            return CapabilityTag.NONE
        }

        /**
        @description 是否為企業(yè)加密wifi eap
         */
        fun isCapabilityEAP(capabilities: String?): Boolean {
            return CapabilityTag.EAP == getCapabilityTag(capabilities)
        }

        /**
        @description 是否為不加密wifi
         */
        fun isCapabilityNone(capabilities: String?): Boolean {
            return CapabilityTag.NONE == getCapabilityTag(capabilities)
        }

        /**
        @description 獲取加密方式字符
         */
        fun getCapabilityStr(capability: CapabilityTag): String {
            return when (capability) {
                CapabilityTag.WEP -> CAPABILITY_WEP_STR
                CapabilityTag.PSK -> CAPABILITY_PSK_STR
                CapabilityTag.EAP -> CAPABILITY_EAP_STR
                else -> CAPABILITY_NONE_STR
            }
        }

        /**
        @description 信號等級, level1 > level2 > level3
         */
        const val LEVEL1_ABS_VALUE = 50
        const val LEVEL2_ABS_VALUE = 75
        const val LEVEL3_ABS_VALUE = 90

        /**
        @description 計算信號等級
         */
        fun calculateLevel(levelValue: Int): LEVEL {
            val absValue = abs(levelValue)
            return when {
                absValue < LEVEL1_ABS_VALUE -> {
                    LEVEL.LEVEL1
                }
                absValue < LEVEL2_ABS_VALUE -> {
                    LEVEL.LEVEL2
                }
                absValue < LEVEL3_ABS_VALUE -> {
                    LEVEL.LEVEL3
                }
                absValue >= LEVEL3_ABS_VALUE -> {
                    LEVEL.LEVEL4
                }
                else -> {
                    LEVEL.LEVEL_UNKNOW
                }
            }
        }

        fun transform(scanResult: ScanResult): WifiBean {
            return WifiBean().apply {
                wifiName = scanResult.SSID?.replace("\"", "")
                ssid = scanResult.SSID
                bssid = scanResult.BSSID
                connectStatus = ConnectStatus.DISCONNECT
                capabilities = scanResult.capabilities
                levelValue = scanResult.level
            }
        }
    }

    /**
    @description 加密標(biāo)識
    */
    enum class CapabilityTag {
        WEP, PSK, EAP, NONE
    }

    /**
    @description Wifi信號等級
     */
    enum class LEVEL {
        LEVEL_UNKNOW, LEVEL1, LEVEL2, LEVEL3, LEVEL4
    }

    /**
    @description 連接狀態(tài)
     */
    enum class ConnectStatus {
        DISCONNECT, CONNECTED, CONNECTING
    }
}

6.WifiProxy

基本功能

/**
@author: Zed.Qiu
@date: 2022/8/2
@description: wifi代理類接口
 */
interface IWifiProxy {

    fun register()

    fun unRegister()

    /**
    @description 檢測wifi是否連接
     */
    fun checkIsOpenWifi()

    /**
    @description 主動連接wifi
     */
    fun connectWifi(wifiBean: WifiBean, password: String?)
}

具體實現(xiàn)

/**
@author: Zed.Qiu
@date: 2022/8/2
@description: Wifi狀態(tài)代理類
 */

class WifiProxy(
    val activity: AppCompatActivity,
    private val wifiBeanListListener: ((List<WifiBean>) -> Unit),
    private val switchListener: Function1<Boolean, Unit>,
    private val connectListener: (String, Boolean) -> Unit
)  :IWifiProxy{
    companion object {
        const val TAG: String = "WifiProxy"
    }

    val typhurWifiManager by lazy {
        TyphurWifiManager.getInstance(activity)
    }

    private val typhurConnectManager by lazy {
        TyphurConnectManager.getInstance(activity)
    }

    private var receiver: WifiScanReceiver? = null

    private val networkCallback = object :
        ConnectivityManager.NetworkCallback() {
        override fun onUnavailable() {
            super.onUnavailable()
            activity.lifecycleScope.launchOnUi {
                wifiBeanListListener.invoke(typhurWifiManager.getWifiBeanList())
            }
        }

        override fun onAvailable(network: Network) {
            super.onAvailable(network)
            // 網(wǎng)絡(luò)可訪問時綁定

            activity.lifecycleScope.launchOnUi {
                wifiBeanListListener.invoke(typhurWifiManager.getWifiBeanList())
            }
        }

        override fun onLost(network: Network) {
            super.onLost(network)
            activity.lifecycleScope.launchOnUi {
                wifiBeanListListener.invoke(typhurWifiManager.getWifiBeanList())
            }
        }
    }

    override fun register() {
        registerWifiNetworkCallback()
        registerWifiScanReceiver()
    }

    override fun unRegister() {
        // 解綁當(dāng)前網(wǎng)絡(luò)
        activity.unregisterReceiver(receiver)
        typhurConnectManager.removeCallback(networkCallback)
    }

    /**
    @description 注冊wifi網(wǎng)絡(luò)狀態(tài)回調(diào)
     */
    private fun registerWifiNetworkCallback() {
        typhurConnectManager.register()
        typhurConnectManager.addCallback(networkCallback)
    }

    /**
    @description 注冊wifi掃描接收器
     */
    private fun registerWifiScanReceiver() {
        if (receiver == null) {
            receiver = WifiScanReceiver(
                typhurWifiManager.wifiManager, onWifiEnable(),
                onScanWifiList(), connectListener
            ).apply {
                register(activity)
            }
        }
    }

    /**
    @description 掃描返回最終的WifiBean列表
     */
    private fun onScanWifiList(): (Boolean) -> Unit = {
        if (it) {
            wifiBeanListListener.invoke(typhurWifiManager.getWifiBeanList())
        } else {
            wifiBeanListListener.invoke(listOf())
        }
    }

    /**
    @description wifi開關(guān)狀態(tài)變化
     */
    private fun onWifiEnable(): (Boolean) -> Unit = {
        switchListener.invoke(it)
        if (it) {
            typhurWifiManager.startScan()
        } else {
            wifiBeanListListener.invoke(listOf())
        }
    }

    /**
     * 檢查是否打開wifi
     */
    override fun checkIsOpenWifi() {
        val isWifiEnabled = typhurWifiManager.isWifiEnabled()
        switchListener.invoke(isWifiEnabled)
        if (isWifiEnabled) {
            typhurWifiManager.startScan()
        }
    }

    /**
    @description 連接wifi
    */
    override fun connectWifi(wifiBean: WifiBean, password: String?){
  /*      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            connectWifiSdk29(wifiBean, password)
        }else{
            typhurWifiManager.connectWifi(wifiBean, password)
        }*/
        typhurWifiManager.connectWifi(wifiBean, password)
    }

   /* *//**
    @description 版本大于等于29的新連接方法
    *//*
    private fun connectWifiSdk29(wifiBean: WifiBean, password: String?) {
        val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
            .apply {
                setSsid(wifiBean.ssid ?: "")
                if (password != null) {
                    setWpa2Passphrase(password)
                }
            }.build()

        val networkRequest = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .setNetworkSpecifier(wifiNetworkSpecifier)
            .build()
        connectivityManager.requestNetwork(networkRequest, networkCallback)
    }

    *//**
    @description 企業(yè)wifi EAP連接袍啡,暫不能實現(xiàn)
     *//*
    fun connectWifiEAP(wifiBean: WifiBean, identity: String, password: String) {
        val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
            .setSsid(wifiBean.ssid ?: "")
            .setWpa2EnterpriseConfig(WifiEnterpriseConfig().apply {
                eapMethod = WifiEnterpriseConfig.Eap.PEAP
                this.identity = identity
                this.password = password
                phase2Method = WifiEnterpriseConfig.Phase2.NONE
            })
            .build()

        val networkRequest = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .setNetworkSpecifier(wifiNetworkSpecifier)
            .build()
        connectivityManager.requestNetwork(networkRequest, networkCallback)
    }
*/
}

本質(zhì)上踩官,是持有倆個Manager,分別是TyphurConnectManager(管理網(wǎng)絡(luò)狀態(tài)變化)和TyphurWifiManager(管理wifi列表信息變化)境输,另外對sdk版本29蔗牡,做了一些差異化的網(wǎng)絡(luò)連接處理,由于系統(tǒng)會強(qiáng)制彈窗wifi列表嗅剖,并且不能連接上部分類型wifi辩越,因此在這里注釋調(diào)了,但舊方法還是可以用的信粮,只是target sdk必須是28黔攒。android在sdk29開始就不允許應(yīng)用層面去修改wifi相關(guān)配置,在sdk31也做了大量方法舍棄强缘,在綜合設(shè)備兼容后督惰,還是考慮用舊版本實現(xiàn)。

compileSdk Integer.parseInt(project.findProperty("android.compile.sdk"))

defaultConfig {
    applicationId "com.typhur.module.demo"
    minSdk Integer.parseInt(project.findProperty("android.min.sdk"))
    targetSdk Integer.parseInt(project.findProperty("android.target.sdk"))
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

//gradle.properties文件
#android sdk info
android.compile.sdk=32
android.min.sdk=28
android.target.sdk=28

7.WifiLifecycleHelper

/**
@author: Zed.Qiu
@date: 2022/8/2
@description: Wifi生命周期管理器
 */
class WifiLifecycleHelper(
    private val activity: AppCompatActivity,
    private val wifiProxy: WifiProxy
) : LifecycleEventObserver {
    init {
        activity.lifecycle.addObserver(this)
    }

    private lateinit var typhurWifiManager: TyphurWifiManager

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        when (event) {
            Lifecycle.Event.ON_CREATE -> {
                lifeCycleCreate()
            }
            Lifecycle.Event.ON_START -> {

            }
            Lifecycle.Event.ON_RESUME -> {

            }
            Lifecycle.Event.ON_PAUSE -> {

            }
            Lifecycle.Event.ON_STOP -> {

            }
            Lifecycle.Event.ON_DESTROY -> {
                lifeCycleDestroy()
            }
            Lifecycle.Event.ON_ANY -> {

            }
        }
    }

    private fun lifeCycleCreate() {
        typhurWifiManager = TyphurWifiManager.getInstance(activity)
        wifiProxy.register()
        wifiProxy.checkIsOpenWifi()
    }

    private fun lifeCycleDestroy() {
        wifiProxy.unRegister()
    }

    fun isWifiEnabled(): Boolean {
        return typhurWifiManager.isWifiEnabled()
    }

    fun switchWifi(wifiEnabled : Boolean){
        typhurWifiManager.switchWifi(wifiEnabled)
    }

    fun forgetWifi(wifiBean: WifiBean){
        typhurWifiManager.forgetWifi(wifiBean)
    }

    fun changeWifiListBySsid(
        ssid: String?,
        targetStatus: WifiBean.ConnectStatus,
        list: List<WifiBean>
    ): MutableList<WifiBean> {
        return typhurWifiManager.changeWifiListBySsid(ssid, targetStatus, list)
    }

    fun connectWifi(wifiBean: WifiBean, password: String?){
        wifiProxy.connectWifi(wifiBean, password)
    }

    fun connectWifiEAP(wifiBean: WifiBean, identity: String, password: String){
//        wifiProxy.connectWifiEAP(wifiBean, identity, password)
    }
}

只是簡單的生命周期輔助類旅掂,對所有方法同意封裝了下

8.UI調(diào)用

wifiHelper = WifiLifecycleHelper(
    this@SettingWifiActivity, WifiProxy(this,
        { list ->
            Log.d("WifiProxy", "refreshList: ${list.toJsonString()}")
            adapter.refreshList(list)
        },
        {
            mBinding.btnTop.text = if (it) "關(guān)" else "開"
        }, { ssid, isPwError ->
            if (isPwError) {
                Toast.makeText(this, "${connectWifiBean?.ssid} 密碼錯誤", Toast.LENGTH_SHORT)
                    .show()
            }
        })
)
// 主動連接wifi赏胚,兼容sdk版本
wifiHelper.connectWifi(wifiBean, password)  
// 當(dāng)前是否有網(wǎng)絡(luò)連接
val networkAvailable = WifiProxy.isNetworkAvailable(context)  
// 判斷當(dāng)前wifi是否開啟
val wifiEnabled = wifiHelper.isWifiEnabled()  
// 切換開關(guān)
wifiHelper.switchWifi(!wifiEnabled)  
// 忘記wifi
wifiHelper.forgetWifi(wifiBean)  
// 修改wifi列表中某個wifi的連接狀態(tài)
wifiHelper.changeWifiListBySsid(
 wifiBean.ssid, wifiBean.connectStatus, adapter.currentList)  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辞友,隨后出現(xiàn)的幾起案子栅哀,更是在濱河造成了極大的恐慌,老刑警劉巖称龙,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異戳晌,居然都是意外死亡鲫尊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門沦偎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疫向,“玉大人,你說我怎么就攤上這事豪嚎∩ν眨” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵侈询,是天一觀的道長舌涨。 經(jīng)常有香客問我,道長扔字,這世上最難降的妖魔是什么囊嘉? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任温技,我火速辦了婚禮,結(jié)果婚禮上扭粱,老公的妹妹穿的比我還像新娘舵鳞。我一直安慰自己,他們只是感情好琢蛤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布蜓堕。 她就那樣靜靜地躺著,像睡著了一般博其。 火紅的嫁衣襯著肌膚如雪俩滥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天贺奠,我揣著相機(jī)與錄音霜旧,去河邊找鬼。 笑死儡率,一個胖子當(dāng)著我的面吹牛挂据,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播儿普,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼崎逃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了眉孩?” 一聲冷哼從身側(cè)響起个绍,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浪汪,沒想到半個月后巴柿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡死遭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年广恢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呀潭。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡钉迷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钠署,到底是詐尸還是另有隱情糠聪,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布谐鼎,位于F島的核電站舰蟆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夭苗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一信卡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧题造,春花似錦傍菇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淮悼,卻和暖如春咐低,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袜腥。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工见擦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人羹令。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓鲤屡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親福侈。 傳聞我的和親對象是個殘疾皇子酒来,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容