Android连接WiFi再探索

来源:互联网 发布:mac最下面的图标移上去 编辑:程序博客网 时间:2024/06/05 19:18

应用场景

在安卓app上,用户输入WiFi名称(SSID)和密码,试图连接这个WiFi。那么用户输入的WiFi就有各种情况了,这个WiFi可以没有密码,也可以通过不同的加密方式加密。而不同的加密方式,需要写不同的代码才能使WiFi连接成功。无论百度还是谷歌,搜出来的代码大都是针对WPA/WPA2加密方式的,即使有些考虑到了无密码和WEP加密方式的WiFi连接,代码也都写得不清不楚,看着实在糟心。于是在查阅了N多资料,看了WifiConfiguration的源码后,有了这篇文章。不保证本文的所有内容完全正确(因为我也只是半桶水),但至少可以给大家一条思路,供参考。

错误做法

先说说一个基本没啥卵用的做法吧。代码如下:

    private int getSecurityType(WifiConfiguration config) {        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {            return SECURITY_PSK;        }        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {            return SECURITY_EAP;        }        return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;    }

这段代码是我在网上搜到的,很多地方都有引用到,应该是互抄。有什么问题呢?且不说通过密码管理方式倒推出加密方式的逻辑是否严谨,只说一点,方法的入参WifiConfiguration config从何而来。网上给了答案,通过wifiManager.getConfiguredNetworks()呀,这不就拿到了。请注意,wifiManager.getConfiguredNetworks()是获取配置好的网络连接,换句话说,你的设备曾经连上过这些WiFi,设备才能保存这些config。而我的诉求是在从没有连接过这些WiFi的情况下去连接,config并不存在。所以,这条路走不通,歇菜。

正确做法

正确的做法是:通过ScanResult获取WiFi的信息。

List results = wifiManager.getScanResults();for (int i = 0; i < results.size(); i++) {    ScanResult item = (ScanResult) results.get(i);}

代码很好理解,拿到扫描到的WiFi列表。

if (item.SSID.equals(ssid)) {    WifiConfiguration config = createWifiInfo(ssid, pwd, item);    int netID = wifiManager.addNetwork(config);    boolean bRet = wifiManager.enableNetwork(netID, true);    Log.d(TAG, (bRet ? "Connect wifi ok" : "Connect wifi failed") + ",ssid=" + item.SSID);}

通过SSID锁定我们的目标WiFi,并生成连接需要的WifiConfiguration

    private WifiConfiguration createWifiInfo(String SSID, String Password, ScanResult scanResult) {        WifiConfiguration config = new WifiConfiguration();        config.allowedAuthAlgorithms.clear();        config.allowedGroupCiphers.clear();        config.allowedKeyManagement.clear();        config.allowedPairwiseCiphers.clear();        config.allowedProtocols.clear();        config.SSID = "\"" + SSID + "\"";        config.status = WifiConfiguration.Status.ENABLED;        String firstCapabilities = scanResult.capabilities.substring(1, scanResult.capabilities.indexOf("]"));        String[] capabilities = firstCapabilities.split("-");        String auth = capabilities[0];        String keyMgmt = "";        String pairwiseCipher = "";        if (capabilities.length > 1) {            keyMgmt = capabilities[1];        }        if (capabilities.length > 2) {            pairwiseCipher = capabilities[2];        }        /**         *  设置认证方式和密码(如果需要的话)         *         *  Open System authentication (required for WPA/WPA2)         *  public static final int OPEN = 0;         *  Shared Key authentication (requires static WEP keys)         *  public static final int SHARED = 1;         *  LEAP/Network EAP (only used with LEAP)         *  public static final int LEAP = 2;         */        if (auth.contains("EAP")) {            //EAP            config.preSharedKey = "\"" + Password + "\"";            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.LEAP);        } else if (auth.contains("WPA")) {            //WPA/WPA2            config.preSharedKey = "\"" + Password + "\"";            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);        } else if (auth.contains("WEP")) {            //WEP            config.wepKeys[0] = "\"" + Password + "\"";            config.wepTxKeyIndex = 0;            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);        } else {            //NONE        }        /**         *  设置加密协议         *  WPA/IEEE 802.11i/D3.0         *  public static final int WPA = 0;         *  WPA2/IEEE 802.11i         *  public static final int RSN = 1;         */        if (auth.contains("WPA2")) {            config.allowedProtocols.set(WifiConfiguration.Protocol.RSN);        } else if (auth.contains("WPA")) {            config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);        }        /**         *  设置密码管理方式         *         *  WPA is not used; plaintext or static WEP could be used.         *  public static final int NONE = 0;         *  WPA pre-shared key (requires {@code preSharedKey} to be specified).         *  public static final int WPA_PSK = 1;         *  WPA using EAP authentication. Generally used with an external authentication server.         *  public static final int WPA_EAP = 2;         *  IEEE 802.1X using EAP authentication and (optionally) dynamically generated WEP keys.         *  public static final int IEEE8021X = 3;         */        if (!keyMgmt.equals("")) {            if (keyMgmt.contains("IEEE802.1X")) {                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);            } else if (auth.contains("WPA") && keyMgmt.contains("EAP")) {                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);            } else if (auth.contains("WPA") && keyMgmt.contains("PSK")) {                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);            }        } else {            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);        }        /**         *  设置PairwiseCipher和GroupCipher         */        if (!pairwiseCipher.equals("")) {            if (pairwiseCipher.contains("CCMP")) {                config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);                config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);            }            if (pairwiseCipher.contains("TKIP")) {                config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);                config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);            }        }        return config;    }

关键逻辑讲解

所谓的关键逻辑就是createWifiInfo这个方法了。方法很长,一点点看吧。

理论储备

ScanResult的capabilities属性很重要,它描述了认证、密钥管理、接入点所支持的加密方案。列一下常见的capabilities:
1.[WPA2-PSK-CCMP][ESS]
2.[WPA2-PSK-CCMP+TKIP][ESS]
3.[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS]
4.[WPS][ESS]
5.[ESS]
6.……
如果你用iPhone开个手机热点的话,capabilities就是第一个。如果你家里用的是小米的路由器,没有做过特殊设置,capabilities就是第三个。第四第五个是无密码的情况,根据路由器的不同,可能是四也可能是五。

对于前三种情况,我们看到第一个[]里面都有2个-,也就是说第一个[]里用-分隔后可以得到三个信息[Authentication Algorithm - Key Management Algorithm - Pairwise Cipher],中文[认证算法-秘钥管理算法-成对加密]。

代码解析

后面要怎么做就很清楚啦,拿到这三个信息,挨个解析就可以了。

        /**         *  设置认证算法和密码(如果需要的话)         *         *  Open System authentication (required for WPA/WPA2)         *  public static final int OPEN = 0;         *  Shared Key authentication (requires static WEP keys)         *  public static final int SHARED = 1;         *  LEAP/Network EAP (only used with LEAP)         *  public static final int LEAP = 2;         */        if (auth.contains("EAP")) {            //EAP            config.preSharedKey = "\"" + Password + "\"";            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.LEAP);        } else if (auth.contains("WPA")) {            //WPA/WPA2            config.preSharedKey = "\"" + Password + "\"";            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);        } else if (auth.contains("WEP")) {            //WEP            config.wepKeys[0] = "\"" + Password + "\"";            config.wepTxKeyIndex = 0;            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);        } else {            //NONE        }

auth的可选值有EAP、WPA、WPA2、WEP,如果都匹配不上就是NONE(无加密)。顺说一句,WiFi安全程度按NONE->WEP->WPA->WPA2->EAP的顺序增强。其中WPA/WPA2在这块代码中要做的事情一样,所以并起来写了。

config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);

WEP分支中的这两行代码和其他的不太一样,先hold住不讲,后面会解释。

        /**         *  设置安全协议         *  WPA/IEEE 802.11i/D3.0         *  public static final int WPA = 0;         *  WPA2/IEEE 802.11i         *  public static final int RSN = 1;         */        if (auth.contains("WPA2")) {            config.allowedProtocols.set(WifiConfiguration.Protocol.RSN);        } else if (auth.contains("WPA")) {            config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);        }

源码写得很清楚,auth为WPA2需要设置Protocol为RSN,auth为WPA则需要设置Protocol为WPA。网上搜到的代码有不少都没有设置allowedProtocols,那么在连接WPA2-PSK加密方式的WiFi时,就无法连上。

        /**         *  设置秘钥管理方式         *         *  WPA is not used; plaintext or static WEP could be used.         *  public static final int NONE = 0;         *  WPA pre-shared key (requires {@code preSharedKey} to be specified).         *  public static final int WPA_PSK = 1;         *  WPA using EAP authentication. Generally used with an external authentication server.         *  public static final int WPA_EAP = 2;         *  IEEE 802.1X using EAP authentication and (optionally) dynamically generated WEP keys.         *  public static final int IEEE8021X = 3;         */        if (!keyMgmt.equals("")) {            if (keyMgmt.contains("IEEE802.1X")) {                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);            } else if (auth.contains("WPA") && keyMgmt.contains("EAP")) {                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);            } else if (auth.contains("WPA") && keyMgmt.contains("PSK")) {                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);            }        } else {            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);        }

秘钥管理方式被分成了四类:NONE、WPA_PSK、WPA_EAP、IEEE8021X。这里的WPA可以是WPA也可以是WPA2(即第一步的auth),进一步解释,[WPA2-PSK-CCMP+TKIP]和[WPA-PSK-CCMP+TKIP]这两种capabilities对应的秘钥管理方式都是WPA_PSK。

        /**         *  设置PairwiseCipher和GroupCipher         */        if (!pairwiseCipher.equals("")) {            if (pairwiseCipher.contains("CCMP")) {                config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);                config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);            }            if (pairwiseCipher.contains("TKIP")) {                config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);                config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);            }        }

注意,pairwiseCipher只有在WPA/WPA2的加密方式里才有。所以,回到第一步,WEP分支中比其他分支多了这两行代码

config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);

是因为WEP加密方式中也需要allowedGroupCiphers,而capabilities里没有显式表达出来,只有在解析auth的时候就设置好。EAP类型的认证不需要设置这个参数。

总结

看完这么长串的代码,真的是头痛病都要发作了。尤其是那个讨厌的EAP,既可以出现在第一位作为认证方式,又能出现在第二位作为秘钥管理方式(WPA_EAP)。而我这里并没有EAP加密方式的WiFi,所以这块代码我不保证可以用。但是WPA/WPA2加密方式的WiFi亲测是可以连接成功的。

最后P个S。
注意这行代码:

boolean bRet = wifiManager.enableNetwork(netID, true);

如果bRet为true,也请不要高兴太早。bRet为true,只能说明系统可以用配置好的信息去尝试连接(尝试、尝试、尝试,重要的事情说三遍!)。尝试的意思就是,如果密码不对,那么最终还是无法连上WiFi的!所以要谨记,enableNetwork返回的仅仅是尝试连接的结果,最终是否连上,此刻是不知道的。如果想知道,可以注册一个BroadcastReceiver接收全局的WiFi状态。有空我会再补一篇文章把这里梳理一下。

本文肯定会有疏漏,请大家多多指正。