Android WiFi 開發

Android中WiFi模塊在應用層的開發接口以及使用方法android

Android WiFi的掃描、鏈接、信息、以及WiFi熱點等等的實現git

涉及到的類
  • 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大類:github

  • open——開放型網絡,即無加密,可直接鏈接
  • wep——採用wep加密類型的熱點,已過期,不安全,容易被破解,目前使用率已不足10%
  • wpa/wpa2——目前使用最普遍,相對最安全,破解難度最大的加密類型

wps(wifi protected setup):是爲了進一步加強wpa熱點及簡化鏈接過程的技術,不屬於加密類型。api

開發細節

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個字段,this

打印信息以下:

 

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);

 

DEMO: https://github.com/charlesgrant/WIFIDemo 

相關文章
相關標籤/搜索