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状态。有空我会再补一篇文章把这里梳理一下。
本文肯定会有疏漏,请大家多多指正。
- Android连接WiFi再探索
- android wifi 连接笔记
- android Wifi自动连接
- Android 自动WiFi连接?
- android WIFI连接开发
- android WIFI连接开发
- android wifi连接
- Android连接WIFI
- android WIFI连接开发
- android Wifi自动连接
- Android wifi连接
- android WIFI连接开发
- android-wifi连接
- Android,WiFi连接eap
- Android wifi 连接问题
- android Wifi自动连接
- android WIFI连接开发
- android 监听wifi连接
- Java对象的访问定位
- folium遇到的坑 不显示颜色
- 6.17
- HUD 1008 Elevator
- thinkPHP5-toArray()方法
- Android连接WiFi再探索
- 文件尾条件
- 这可能是最好的RxJava 2.x 入门教程(三)
- CentOS6.6下配置SSH免密码登录
- 密码编码学初探——消息认证码
- AndroidStudio 3.0 与 Unity3d交互
- JS中的构造函数
- bootstrap, boosting, bagging 几种方法的联系
- 判断某个String类型的list里的值是否在HashMap类型List中