WIFI的连接与扫描

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34709057/article/details/79853589

参考:https://blog.csdn.net/free092875/article/details/52788652


一、 相关概念介绍

1 涉及到的类
  • WifiManager ——入口类,Wifi相关的所有操作均通过此类
  • WifiConfiguration——进行热点连接时,通过该类为热点创建一个配置,并由WifiManager以此配置生成一个networkId,后开始连接;此外,也用于表示一个已连接的热点在本地的记录
  • WifiInfo——表示当前的wifi网络连接信息
  • ScanResult——扫描到的热点信息类,每一个对象代表一个扫描到的热点,其中包括若干该热点信息
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热点及简化连接过程的技术,不属于加密类型。

5 相关的权限

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />//这个位置权限必须添加,
不然在扫描WIFI时,
getScanResults()的返回的集合为空的。

二 开发细节:

1 获取WifiManager入口类实例:

wifiManager = (WifiManager) getApplicationContext().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;
    case WifiManager.WIFI_STATE_DISABLING:
        Log.i(TAG, "onReceive: wifi正在关闭");
        break;
    case WifiManager.WIFI_STATE_UNKNOWN:
        Log.i(TAG, "onReceive: wifi未知");
        break;
}

该操作其实是一个异步操作,一般耗时在1~3秒之间。

3 周围热点扫描

wifiManager.startScan();
一般在主动调用startScan之后,大概2秒左右,会收到WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)广播通知,该广播包括一个boolean型的额外参数:
boolean isScanned = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true);

上面的值表示,扫描结果是否已可用,若可用,则可以使用getScanResults获取结果,在结果没有就绪之前,会返回null。

results = wifiManager.getScanResults();//这里的集合为空的话,就是没有位置权限

一般系统本身会调用startScan接口,而该操作相对比较耗电,因此在应用中要酌情使用,并不需要频繁调用。

4 获取已连接过的热点

所有已经连接过的热点,都会存在本地一个文件中,一般路径为/data/misc/wifi/wpa_supplicant.conf(查看需root),而在程序中获取则通过以下接口:

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

通过打印configurations.get(0).networkid可知,集合中的ID就是networkid。

获取到的WiFiConfiguration对象中,只有ssid和networkId是一定有的,可以用于直接连接该热点,其他信息如bssid,密钥等信息基本都是空的。(如何直接连接热点,下文叙述)

5 获取当前wifi连接信息

WifoInfo info=wifiManager.getConnectionInfo()
该对象代表当前已连接的热点,信息,无连接时返回null; 
该对象可获取包括ssid,bssid,networkId等信息,而ssid是包括了双引号的,如“CCMC”,在之前的扫描结果ScanResult中,ssid并不带双引号。

6 连接指定热点

连接一个未连接过的热点时,需3步: 
1)创建一个配置:WifiConfiguration

public WifiConfiguration createConfiguration(String ssid,String type,String password) {
    String SSID =ssid;//WIFI名称
    WifiConfiguration config = new WifiConfiguration();
    config.SSID = "\"" + SSID + "\"";

    String encryptionType = type;//WIFI的加密类型,如wpa,wpa2,wep等
    String password = password;//WIFI的密码
    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;
}
2)生成一个networkId

WifiConfiguration config = createConfiguration(ssid,type,password);

networkId = wifiManager.addNetwork(config);//生成一个networkId
一般情况下,对一个已经连接过的热点(本地有连接记录),进行以上操作时,在api21及以上会返回一个小于0的networkId,此时,进行下一步连接是没有意义的,获得一个小于0的networkId已经表示连接失败。

3)开始连接

wifiManager.enableNetwork(networkId, true);//开始连接

根据这三步后会有第9点的连接已经连接过的指定


对于已经连接过的热点,通过小项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) {
                //密码错误,认证失败
            }
  • 1
  • 2
  • 3
  • 4

其他结果均通过第一个广播接收:

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();
            }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
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
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

7 断开当前wifi连接

wifiManager.disconnect()
  • 1

以上接口返回值代表当前操作是否成功,操作的最终结果,会在两个广播中有所反馈: 
WifiManager.SUPPLICANT_STATE_CHANGED_ACTION 
WifiManager.NETWORK_STATE_CHANGED_ACTION

并且断开成功的广播会发送若干次。

8 遗忘一个已连接过的热点

boolean isRemoved = wifiManager.removeNetwork(networkId)
  • 1

返回值代表操作是否成功,该操作在api21以上的系统中,成功率在10%以下,在api21以下,基本都可以成功; 
可以通过反复进行此操作来提高成功率,但效果不大。

9 连接指定连接过的WIFI

在广播 中

case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
    boolean isScanned = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true);
    if (isScanned) {
        results = wifiManager.getScanResults();
        for (int i = 0; i < results.size(); i++) {//扫描得到的结果
            for (int j = 0; j < configurations.size(); j++) {//已经连接过的WIFI集合
                if (configurations.get(j).SSID.equals("\"YANG\"") && results.get(i).SSID.equals("YANG")) {
                    wifiManager.enableNetwork(configurations.get(j).networkId, true);
                    return;
                }
            }
        }
    }
    break;
因为连接指定的WIFI,起码要在你周围吧,要不然怎么连接?所以我就放在扫描结果的集合循环中。如果只是单纯的连接WIFI,不考虑是否存在的话可以在得到已经连接过的WIFI集合中得到:

for (int j = 0; j < configurations.size(); j++) {
    if (configurations.get(j).SSID.equals("\"YANG\"")) {
        wifiManager.enableNetwork(configurations.get(j).networkId, true);
        return;
    }
}


猜你喜欢

转载自blog.csdn.net/qq_34709057/article/details/79853589