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以下,基本都可以成功;
可以通過反覆進行此操作來提高成功率,但效果不大。