Android中WiFi模塊在應用層的開發接口以及使用方法
Android WiFi的掃描、連接、信息、以及WiFi熱點等等的實現
涉及到的類
- WifiManager ——入口類,Wifi相關的所有操作均通過此類
- WifiConfiguration——進行熱點連接時,通過該類為熱點創建一個配置,並由WifiManager以此配置生成一個networkId,后開始連接;此外,也用於表示一個已連接的熱點在本地的記錄
- WifiInfo——表示當前的wifi網絡連接信息
- ScanResult——掃描到的熱點信息類,每一個對象代表一個掃描到的熱點,其中包括若干該熱點信息
涉及到的廣播
- WifiManager.WIFI_STATE_CHANGED_ACTION ——wifi開關變化廣播
- WifiManager.SCAN_RESULTS_AVAILABLE_ACTION——熱點掃描結果通知廣播
- WifiManager.SUPPLICANT_STATE_CHANGED_ACTION——熱點連接結果通知廣播
- WifiManager.NETWORK_STATE_CHANGED_ACTION——網絡狀態變化廣播(與上一廣播協同完成連接過程通知)
相關屬性及概念
- networkId——連接某個wifi熱點時,系統會為該熱點生成一個networkId,在同一設備上,不同熱點的networkId是唯一的,通常情況下為大於0的整數,在某些設備上,恢復出廠后連接的第一個熱點networkId為0
- ssid——wifi熱點名稱,可重復
- bssid——類似於mac地址,但並不是路由器的mac地址,與ssid一起可作為熱點的唯一標識,同時該屬性每個熱點唯一不重復
- 親屬熱點——(本文設定概念)ssid相同,但bssid不同的所有熱點,互為親屬熱點,android設備會將ssid相同的所有親屬熱點當做一個熱點進行處理
熱點加密類型
目前,常見及需要處理的熱點,包括以下3大類:
- open——開放型網絡,即無加密,可直接連接
- wep——采用wep加密類型的熱點,已過時,不安全,容易被破解,目前使用率已不足10%
- wpa/wpa2——目前使用最廣泛,相對最安全,破解難度最大的加密類型
wps(wifi protected setup):是為了進一步增強wpa熱點及簡化連接過程的技術,不屬於加密類型。
開發細節
1 獲取WifiManager入口類實例:
1 wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
2 打開及關閉wifi
5.0系統的開啟熱點有問題,使用華為p9Android6.0手機測試確實開啟不了熱點,需要添加write_settings,添加上此權限就可以成功開啟了。
1 // true表示打開wifi開關,false表示關閉; 2 // 該方法的返回值僅代表操作是否成功,不代表wifi狀態的變化; 3 wifiManager.setWifiEnabled(true);
通過監聽廣播WifiManager.WIFI_STATE_CHANGED_ACTION ,來判斷真正的wifi開關變化,該廣播帶有一個int型的值來表示wifi狀態:
1 // 可以看到,該操作其實是一個異步操作,一般耗時在1~3秒之間。 2 int wifistate = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED); 4 switch (wifistate) { 5 case WifiManager.WIFI_STATE_DISABLED: 6 //wifi已關閉 7 break; 8 case WifiManager.WIFI_STATE_ENABLED: 9 //wifi已打開 10 break; 11 case WifiManager.WIFI_STATE_ENABLING: 12 //wifi正在打開 13 break; 14 default: 15 break; 16 }
3 周圍熱點掃描
1 // 開始掃描的接口,其返回值代表操作是否成功 2 wifiManager.startScan();
掃描結果:廣播通知:
1 // 獲取掃描結果 2 List<ScanResult> results = wifiManager.getScanResults(); 3 4 // 一般在主動調用startScan之后,大概2秒左右 5 // 會收到WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)廣播通知,該廣播包括一個boolean型的額外參數: 6 7 boolean isScanned = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true); 8 9 // 上面的值表示,掃描結果是否已可用,若可用,則可以使用getScanResults獲取結果,在結果沒有就緒之前,會返回null。
一般系統本身會調用startScan接口,而該操作相對比較耗電,因此在應用中要酌情使用,並不需要頻繁調用。
獲取wifi熱點:
1 public List<AccessPoint> getWiFiList() { 2 List<ScanResult> results = wifiManager.getScanResults(); 3 List<AccessPoint> aps = new ArrayList<AccessPoint>(); 4 DecimalFormat df = new DecimalFormat("#.##"); 5 for (ScanResult result : results) { 6 if (TextUtils.isEmpty(result.SSID)) { 7 continue; 8 } 9 10 AccessPoint accessPoint = new AccessPoint(); 11 accessPoint.setSsid(result.SSID); 12 accessPoint.setBssid(result.BSSID); 13 accessPoint.setEncryptionType(result.capabilities); 14 try { 15 double level = calculateSignalLevel(result.level, 5.0f) / 5.0; 16 level = Double.parseDouble(df.format(level)); 17 accessPoint.setSignalStrength((float) level * 100); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 int networkId = isConfigured(accessPoint); 22 if (networkId > -1) { 23 accessPoint.setNetworkId(networkId); 24 } 25 aps.add(accessPoint); 26 } 27 return aps; 28 } 29 30 public int isConfigured(AccessPoint ap) { 31 List<WifiConfiguration> configurations = wifiManager.getConfiguredNetworks(); 32 if (configurations == null || configurations.size() <= 0) { 33 Log.d("WIFIX","Config Aps are empty"); 34 return -1; 35 } 36 37 for (WifiConfiguration configuration : configurations) { 38 /** 39 * ssid in WifiConfiguration is always like "CCMC",and bssid is always null 40 */ 41 if (configuration.SSID.replace("\"","").trim().equals(ap.getSsid())) { 42 return configuration.networkId; 43 } 44 } 45 return -1; 46 }
ScanResult類
這個類主要是通過Wifi硬件的掃描來獲取一些周邊的wifi熱點(access point)的信息。該類主要有5個字段,
打印信息如下:
4 獲取已連接過的熱點
1 // 所有已經連接過的熱點,都會存在本地一個文件中,一般路徑為/data/misc/wifi/wpa_supplicant.conf(查看需root),而在程序中獲取則通過以下接口: 2 3 List<WifiConfiguration> configurations = wifiManager.getConfiguredNetworks();
獲取到的WiFiConfiguration對象中,只有ssid和networkId是一定有的,可以用於直接連接該熱點,其他信息如bssid,密鑰等信息基本都是空的。
5 獲取當前wifi連接信息
1 // 該對象代表當前已連接的熱點,信息,無連接時返回null; 2 // 該對象可獲取包括ssid,bssid,networkId等信息; 3 // 而ssid是包括了雙引號的,如“CCMC”,在之前的掃描結果ScanResult中,ssid並不帶雙引號。 4 5 WifiInfo info = wifiManager.getConnectionInfo();
6 連接指定熱點
連接一個未連接過的熱點時,需3步:
1)創建一個配置:WifiConfiguration
通過該類獲取一個wifi網絡的網絡配置,包括安全配置等。它包含6個子類,如下所示:
創建:
1 public WifiConfiguration createConfiguration(AccessPoint ap) { 2 String SSID = ap.getSsid(); 3 WifiConfiguration config = new WifiConfiguration(); 4 config.SSID = "\"" + SSID + "\""; 5 6 String encryptionType = ap.getEncryptionType(); 7 String password = ap.getPassword(); 8 if (encryptionType.contains("nopass")) { 9 config.wepKeys[0] = ""; 10 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 11 config.wepTxKeyIndex = 0; 12 13 } else if (encryptionType.contains("wep")) { 14 /** 15 * special handling according to password length is a must for wep 16 */ 17 int i = password.length(); 18 if (((i == 10 || (i == 26) || (i == 58))) && (password.matches("[0-9A-Fa-f]*"))) { 19 config.wepKeys[0] = password; 20 } else { 21 config.wepKeys[0] = "\"" + password + "\""; 22 } 23 config.allowedAuthAlgorithms 24 .set(WifiConfiguration.AuthAlgorithm.SHARED); 25 config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); 26 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 27 config.wepTxKeyIndex = 0; 28 } else if (encryptionType.contains("wpa")) { 29 config.preSharedKey = "\"" + password + "\""; 30 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); 31 } else { 32 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 33 } 34 return config; 35 }
2)生成一個networkId
1 WifiConfiguration config = createConfiguration(ap); 2 3 /** 4 * 一般情況下,對一個已經連接過的熱點(本地有連接記錄),進行addNetwork操作時,在api21及以上會返回一個小於0的networkId。
此時,進行下一步連接是沒有意義的,獲得一個小於0的networkId已經表示連接失敗。 5 */ 6 int networkId = networkId = wifiManager.addNetwork(config);
3)開始連接
1 wifiManager.enableNetwork(networkId, true);
對於已連接過的熱點:
1 // 獲取已連接過的熱點, 獲取到該熱點的networkId之后,可直接進行連接 2 List<WifiConfiguration> configurations = wifiManager.getConfiguredNetworks();
注意:
如嘗試一個新密碼,因為即使使用了錯誤的密碼連接,系統還是會為本次連接生成一個本地記錄,則必須在一開始,將本地記錄remove掉
***連接結果通過兩個廣播反饋:WifiManager.NETWORK_STATE_CHANGED_ACTION和WifiManager.SUPPLICANT_STATE_CHANGED_ACTION
其中,密碼錯誤的結果通知需通過第二個廣播判斷:
1 int error = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0); 2 if (WifiManager.ERROR_AUTHENTICATING == error) { 3 //密碼錯誤,認證失敗 4 }
其他結果均通過第一個廣播接收:
1 if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { 2 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 3 if (null != info) { 4 NetworkInfo.DetailedState state = info.getDetailedState(); 5 } 6 }
DetailedState系統定義:
android 系統把CONNECTING,AUTHENTICATING,OBTAINING_IPADDR都規為CONNECTING
1 /* 2 * IDLE:空閑 3 SCANNING:正在掃描 4 CONNECTING:連接中 5 AUTHENTICATING:正在進行身份驗證... 6 OBTAINING_IPADDR:正在獲取Ip地址 7 CONNECTED:已連接 8 SUSPENDED:已暫停 9 DISCONNECTING:正在斷開連接... 10 DISCONNECTED:已斷開 11 FAILED:失敗 12 BLOCKED:已阻止 13 VERIFYING_POOR_LINK:暫時關閉(網絡狀況不佳) 14 CAPTIVE_PORTAL_CHECK:判斷是否需要瀏覽器二次登錄(本人用6.0手機試了,好像不會走到這一步) 15 */ 16 17 public enum DetailedState { 18 /** Ready to start data connection setup. */ 19 IDLE, 20 /** Searching for an available access point. */ 21 SCANNING, 22 /** Currently setting up data connection. */ 23 CONNECTING, 24 /** Network link established, performing authentication. */ 25 AUTHENTICATING, 26 /** Awaiting response from DHCP server in order to assign IP address information. */ 27 OBTAINING_IPADDR, 28 /** IP traffic should be available. */ 29 CONNECTED, 30 /** IP traffic is suspended */ 31 SUSPENDED, 32 /** Currently tearing down data connection. */ 33 DISCONNECTING, 34 /** IP traffic not available. */ 35 DISCONNECTED, 36 /** Attempt to connect failed. */ 37 FAILED, 38 /** Access to this network is blocked. */ 39 BLOCKED, 40 /** Link has poor connectivity. */ 41 VERIFYING_POOR_LINK, 42 /** Checking if network is a captive portal */ 43 CAPTIVE_PORTAL_CHECK 44 }
7 斷開當前wifi連接
1 // 方法返回值代表當前操作是否成功,操作的最終結果,會在兩個廣播中有所反饋: 2 // (1)WifiManager.SUPPLICANT_STATE_CHANGED_ACTION 3 // (2)WifiManager.NETWORK_STATE_CHANGED_ACTION 4 5 // 並且斷開成功的廣播會發送若干次。 6 7 wifiManager.disconnect();
8 遺忘一個已連接過的熱點
1 // 返回值代表操作是否成功,該操作在api21以上的系統中,成功率在10%以下,在api21以下,基本都可以成功; 2 // 可以通過反復進行此操作來提高成功率,但效果不大。 3 boolean isRemoved = wifiManager.removeNetwork(networkId);