1. 程式人生 > >Android WiFi模組功能開發

Android WiFi模組功能開發

一 概要

本文介紹了Android中WiFi模組在應用層的開發介面以及使用方法,包括一些常見問題的處理建議,最後會提供一個github工程作為參考Demo。

二 相關概念介紹

1 涉及到的類
  • WifiManager ——入口類,Wifi相關的所有操作均通過此類
  • WifiConfiguration——進行熱點連線時,通過該類為熱點建立一個配置,並由WifiManager以此配置生成一個networkId,後開始連線;此外,也用於表示一個已連線的熱點在本地的記錄
  • WifiInfo——表示當前的wifi網路連線資訊
  • ScanResult——掃描到的熱點資訊類,每一個物件代表一個掃描到的熱點,其中包括若干該熱點資訊

AccessPoint:本文定義物件,方便描述和講解,結構如下:

public class AccessPoint {
    private String ssid;
    private String bssid;
    private String password;
    private float signalStrength;  // 0~100
    private String encryptionType;
    private int networkId;
    /**
     * aps are relative AccessPoints who share the same ssid while different bssid
     * we will treat them as one hotspot
     */
private ArrayList<AccessPoint> relativeAPs; }
2 涉及到的廣播
  • WifiManager.WIFI_STATE_CHANGED_ACTION ——wifi開關變化廣播
  • WifiManager.SCAN_RESULTS_AVAILABLE_ACTION——熱點掃描結果通知廣播
  • WifiManager.SUPPLICANT_STATE_CHANGED_ACTION——熱點連線結果通知廣播
  • WifiManager.NETWORK_STATE_CHANGED_ACTION——網路狀態變化廣播(與上一廣播協同完成連線過程通知)

3 相關屬性及概念

  • networkId——連線某個wifi熱點時,系統會為該熱點生成一個networkId,在同一裝置上,不同熱點的networkId是唯一的,通常情況下為大於0的整數,在某些裝置上,恢復出廠後連線的第一個熱點networkId為0
  • ssid——wifi熱點名稱,可重複
  • bssid——類似於mac地址,但並不是路由器的mac地址,與ssid一起可作為熱點的唯一標識,同時該屬性每個熱點唯一不重複
  • 親屬熱點——(本文設定概念)ssid相同,但bssid不同的所有熱點,互為親屬熱點,android裝置會將ssid相同的所有親屬熱點當做一個熱點進行處理

4 熱點加密型別

目前,常見及需要處理的熱點,包括以下3大類:

  • open——開放型網路,即無加密,可直接連線
  • wep——採用wep加密型別的熱點,已過時,不安全,容易被破解,目前使用率已不足10%
  • wpa/wpa2——目前使用最廣泛,相對最安全,破解難度最大的加密型別

wps(wifi protected setup):是為了進一步增強wpa熱點及簡化連線過程的技術,不屬於加密型別。

三 開發細節

1 獲取WifiManager入口類例項:

wifiManager = (WifiManager) context
                    .getSystemService(Context.WIFI_SERVICE);

2 開啟及關閉wifi

wifiManager.setWifiEnabled(true)

true表示開啟wifi開關,false表示關閉,該方法的返回值僅代表操作是否成功,不代表wifi狀態的變化;
通過監聽廣播WifiManager.WIFI_STATE_CHANGED_ACTION ,來判斷真正的wifi開關變化,該廣播帶有一個int型的值來表示wifi狀態:

int wifistate = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                WifiManager.WIFI_STATE_DISABLED);
        switch (wifistate) {
            case WifiManager.WIFI_STATE_DISABLED:
                //wifi已關閉
                break;
            case WifiManager.WIFI_STATE_ENABLED:
                //wifi已開啟
                break;
            case WifiManager.WIFI_STATE_ENABLING:
                //wifi正在開啟
                break;
            default:
                break;
        }

可以看到,該操作其實是一個非同步操作,一般耗時在1~3秒之間。

3 周圍熱點掃描

wifiManager.startScan()

以上方法為開始掃描的介面,其返回值代表操作是否成功,掃描結果通過另外一個介面獲取:

List<ScanResult> results = wifiManager.getScanResults();

一般在主動呼叫startScan之後,大概2秒左右,會收到WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)廣播通知,該廣播包括一個boolean型的額外引數:

boolean isScanned = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true);

上面的值表示,掃描結果是否已可用,若可用,則可以使用getScanResults獲取結果,在結果沒有就緒之前,會返回null。

一般系統本身會呼叫startScan介面,而該操作相對比較耗電,因此在應用中要酌情使用,並不需要頻繁呼叫。

4 獲取已連線過的熱點

所有已經連線過的熱點,都會存在本地一個檔案中,一般路徑為/data/misc/wifi/wpa_supplicant.conf(檢視需root),而在程式中獲取則通過以下介面:

List<WifiConfiguration> configurations = wifiManager.getConfiguredNetworks();

獲取到的WiFiConfiguration物件中,只有ssid和networkId是一定有的,可以用於直接連線該熱點,其他資訊如bssid,金鑰等資訊基本都是空的。(如何直接連線熱點,下文敘述)

5 獲取當前wifi連線資訊

WifiInfo info = wifiManager.getConnectionInfo();

該物件代表當前已連線的熱點,資訊,無連線時返回null;
該物件可獲取包括ssid,bssid,networkId等資訊,而ssid是包括了雙引號的,如“CCMC”,在之前的掃描結果ScanResult中,ssid並不帶雙引號。

6 連線指定熱點

連線一個未連線過的熱點時,需3步:
1)建立一個配置:WifiConfiguration

    public WifiConfiguration createConfiguration(AccessPoint ap) {
        String SSID = ap.getSsid();
        WifiConfiguration config = new WifiConfiguration();
        config.SSID = "\"" + SSID + "\"";

        String encryptionType = ap.getEncryptionType();
        String password = ap.getPassword();
        if (encryptionType.contains("wep")) {
            /**
             * special handling according to password length is a must for wep
             */
            int i = password.length();
            if (((i == 10 || (i == 26) || (i == 58))) && (password.matches("[0-9A-Fa-f]*"))) {
                config.wepKeys[0] = password;
            } else {
                config.wepKeys[0] = "\"" + password + "\"";
            }
            config.allowedAuthAlgorithms
                    .set(WifiConfiguration.AuthAlgorithm.SHARED);
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;
        } else if (encryptionType.contains("wpa")) {
            config.preSharedKey = "\"" + password + "\"";
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
        } else {
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
        }
        return config;
    }

*網上流傳多處建立configuration的程式碼,但大都未經過驗證,以上程式碼已經經過了線上版本測試,準確可用。
判斷加密型別的方式,可以優化,本處僅示例。*
2)生成一個networkId

 WifiConfiguration config = createConfiguration(ap);

        /**
         * networkId is bigger than 0 in most time, 0 in few time and smaller than 0 in no time
         */
 int networkId = networkId = wifiManager.addNetwork(config);

一般情況下,對一個已經連線過的熱點(本地有連線記錄),進行以上操作時,在api21及以上會返回一個小於0的networkId,此時,進行下一步連線是沒有意義的,獲得一個小於0的networkId已經表示連線失敗。

3)開始連線

wifiManager.enableNetwork(networkId, true)

對於已經連線過的熱點,通過小項4 中的方式,獲取到該熱點的networkId之後,可直接進行第三步的連線,無需1)2);
若有必要進行12步(如嘗試一個新密碼,因為即使使用了錯誤的密碼連線,系統還是會為本次連線生成一個本地記錄),則必須在一開始,將本地記錄remove掉,remove操作將在下文介紹。

連線結果通過兩個廣播反饋:WifiManager.NETWORK_STATE_CHANGED_ACTION和WifiManager.SUPPLICANT_STATE_CHANGED_ACTION

其中,密碼錯誤的結果通知需通過第二個廣播判斷:

 int error = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0);
            if (WifiManager.ERROR_AUTHENTICATING == error) {
                //密碼錯誤,認證失敗
            }

其他結果均通過第一個廣播接收:

if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
            NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
            if (null != info) {
                NetworkInfo.DetailedState state = info.getDetailedState();
            }
}
public enum DetailedState {
        /** Ready to start data connection setup. */
        IDLE,
        /** Searching for an available access point. */
        SCANNING,
        /** Currently setting up data connection. */
        CONNECTING,
        /** Network link established, performing authentication. */
        AUTHENTICATING,
        /** Awaiting response from DHCP server in order to assign IP address information. */
        OBTAINING_IPADDR,
        /** IP traffic should be available. */
        CONNECTED,
        /** IP traffic is suspended */
        SUSPENDED,
        /** Currently tearing down data connection. */
        DISCONNECTING,
        /** IP traffic not available. */
        DISCONNECTED,
        /** Attempt to connect failed. */
        FAILED,
        /** Access to this network is blocked. */
        BLOCKED,
        /** Link has poor connectivity. */
        VERIFYING_POOR_LINK,
        /** Checking if network is a captive portal */
        CAPTIVE_PORTAL_CHECK
    }

7 斷開當前wifi連線

wifiManager.disconnect()

以上介面返回值代表當前操作是否成功,操作的最終結果,會在兩個廣播中有所反饋:
WifiManager.SUPPLICANT_STATE_CHANGED_ACTION
WifiManager.NETWORK_STATE_CHANGED_ACTION

並且斷開成功的廣播會發送若干次。

8 遺忘一個已連線過的熱點

boolean isRemoved = wifiManager.removeNetwork(networkId)

返回值代表操作是否成功,該操作在api21以上的系統中,成功率在10%以下,在api21以下,基本都可以成功;
可以通過反覆進行此操作來提高成功率,但效果不大。

四 Sample地址