1. 程式人生 > 其它 >Android開發 WifiHelp 一個封裝好的WiFi功能操作類

Android開發 WifiHelp 一個封裝好的WiFi功能操作類

前言

  基於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,
}