Android WiFi 开发


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

 

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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM