Android连接WiFi再探索
应用场景
在安卓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")) {//EAPconfig.preSharedKey = "\"" + Password + "\"";config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.LEAP);} else if (auth.contains("WPA")) {//WPA/WPA2config.preSharedKey = "\"" + Password + "\"";config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);} else if (auth.contains("WEP")) {//WEPconfig.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")) {//EAPconfig.preSharedKey = "\"" + Password + "\"";config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.LEAP);} else if (auth.contains("WPA")) {//WPA/WPA2config.preSharedKey = "\"" + Password + "\"";config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);} else if (auth.contains("WEP")) {//WEPconfig.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设置IP为静态IP
设置静态IP需要先忘记WiFi密码再重新连接,会记住是哪个APP连接WiFi public class WifiConnet {private static WifiConnet utils = nu ...
- Android连接WiFi
WiFi打开.连接.关闭 /*** WiFi连接管理* 申请权限* <uses-permission android:name="android.permission.ACCESS_W ...
- Android连接Wifi详细源码
因项目需求,做一个有关wifi的Demo,现已经上传到GitHub 上面地址:https://github.com/git-xuhao/WifiDemo 源码片段 ? 1 2 3 4 5 6 7 8 ...
- Android 连接wifi 检测是否需要portal 认证
前言 最近在做一个新需求,就是针对开机欢迎引导页,当连接如星巴克,KFC,地铁上等公共场所的WIFI时,都需要认证功能,而我要调起这个认证页面呈现给用户,登陆任何网页会跳转到wifi得认证页面,输入账 ...
- mac os android连接wifi密码,Mac使用小技巧:找回WiFi密码
macOS是Apple苹果Mac上运行的操作系统,它拥有大量隐藏的快捷方式,键盘命令和功能.最近小编研究了一下这个操作系统,发现了一个能够快速找到WiFi密码的小技巧,这个非常适合像小编这种经常记不住 ...
- android连接wifi后移动数据,手机连接WiFi的时候,数据网络开着会耗费流量吗
习惯性动作--手机连接的WiFi就关闭数据网络.这样做的原因是? 手机在连上wifi之后,虽然说手机会默认使用wifi连接,进行网络的传输,但如果我们没有关闭数据网络,在wifi信号不强的情况下,手机 ...
- 如何关闭android bt配对窗口,Android 连接SSID隐藏网络以及 LEAP 认证的方法
题目很拗口,其实就是在搜狐怎么连入'sohu-office' wifi 话说 Android 虽然从 1.6 就开始支持 802.1x 认证,但是配置 UI 上却只有很少的几项,如果想要做 LEAP ...
- android获取连接wifi名称,android 获取当前连接WIFI名称的有关问题
android 获取当前连接WIFI名称的问题 我在程序内通过以下方法获取当前连接的WIFI名称(SSID),这个也是网上找别人的,但在我程序内就获取不到呢? 1.在AndroidManifest.x ...
- Android 反射、代理调用系统隐藏API方法与接口类连接Wi-Fi
本文转载自:http://www.xwdoor.net/android-reflection-proxy-call-system-hidden-api-method-and-interface-cla ...
最新文章
- Algorithms_二叉树的前序遍历、中序遍历、后续遍历(深度优先)
- php 新浪面试题,新浪网技术部笔试题
- Gartner认为安全性将取代成本和敏捷性成为政府部门采用云服务的首要原因
- OpenCV/CUDA/Qt 环境配置小结
- New module changes in Go 1.16
- c#中ref和out 关键字
- L1-067 洛希极限 (10 分)-PAT 团体程序设计天梯赛 GPLT
- 链表的应用 —— 实现 LRU(least recently unused)
- 跑酷游戏的一些bug总结(滥用FixedUpdate的坑)
- 试卷模板 html,试卷模板怎么转换a4Word
- 【动画消消乐】HTML+CSS 自定义加载动画 055
- 自然辩证法与计算机科学与技术,自然辩证法与计算机科学技术的研究.pdf
- Linux基础操作命令
- python12306买票_Python 使用 selenium 实现半自动购买12306火车票
- ruby 去除字符空格
- 高可用pxc+rocketmq+es+redis+minio+keepalive+haproxy 实操
- VS2015+opencv3.1+WIN7 计算机中丢失MSVCR120D.dll错误解决方法
- 22.3TB全国1-20级谷歌离线卫星影像终于出炉了
- 计算机音乐谱红昭愿,天谕手游红昭愿乐谱代码是什么-天谕手游红昭愿乐谱代码分享_快吧手游...
- MapINFO栅格图像载入方法
热门文章
- 软件测试岗月薪3w+的测试,到底强在哪里?测试开发真的就是天花板了吗?
- linux内核结构介绍及驱动引入
- matlab 4轴机器人建模,SCARA机器人运动学分析及MATLAB建模仿真.pdf
- 【数字IC/FPGA】门控时钟
- 内大计算机学院研究生奖学金,计算机学院2020级硕士研究生学业奖学金评审结果公示...
- android系统中区分UDisk和SDCard
- mdx词典包_欧路词典PC端 词库安装 渲染
- Online Multiple Object Tracking with Cross-Task Synerg
- python批量放大图片
- python批量修改字典的键或值