Wi-Fi

  • 前言
  • 需求
  • 实现
    • 扫描wifi列表
    • wifi广播
    • wifi连接登录
  • 总结

前言

app里有个需求就是在应用内部开发一个wifi设置的功能,避免用户跳到手机wifi设置界面操作,之前没开发过这样的需求,只是简单的判断网络状态,不过心想应该不难,都是挺成熟的东西,其实做了后才知道还是有些坑的,同时也看了网络上的一些文章,只是大都是讲一些小的方面或者讲的很简陋,于是就自己重新整理了下wifi完整操作的内容;背景是app的wifi设置需求

其demo效果如图

需求

  1. 手机没有打开wifi,需要自动打开wifi
  2. 获取可用的wifi列表,如果某个wifi是连接状态,那就用(已连接)标注
  3. 时刻刷新wifi列表,也就是当有可用的wifi热点出现时,要在列表上体现,wifi列表按信号强度倒序排列
  4. 输入密码连接wifi,如果之前已经连接了某个wifi,那就断开那个连接,但是不能清除它的信息,否则下次连接那个wifi时还得重新输入密码
  5. 时刻监听wifi按钮的打开、关闭状态
  6. 获取wiif的mac地址和ip地址

实现

既然需求已经明确了,那就动手吧,这里面涉及到的api主要有

  • WifiManager:此类提供了用于管理Wi-Fi连接所有方面的主要API,比如查看wifi状态,打开关闭wifi按钮,获取wifi的ip地址,名称,路由器wifi的mac地址及本机无线网卡的mac地址,扫描wifi列表,已配置的网络列表,连接wifi等
  • WifiInfo:封装了一个已连接的wifi对象,可以获取当前这个连接的wifi的一些信息,比如wifi名,网络id,状态
  • WifiConfiguration:看名字也知道是保存了一些wifi配置的信息,比如WiFi名,wifi密码,代理,加密算法,密钥等,通常用于在连接wifi的时候,需要配置这个wifi的信息,这个信息就是用这个类来表示的,不过这个类在api29中标识为废弃,将来它将成为仅系统使用的对象,使用使用WifiNetworkSpecifier.Builder创建NetworkSpecifier和WifiNetworkSuggestion.Builder创建WifiNetworkSuggestion来代替
  • ScanResult:当扫描周围wifi热点的时候,会得到一个列表,其泛型就是它,也就是说一个ScanResult是封装了一个wifi热点信息的对象,包括WiFi名,信号强度,路由器mac地址,加密方案等

使用前需要申请相关权限

    <uses-permission android:name="android.permission.INTERNET"></uses-permission><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <!-- 允许程序改变网络链接状态 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <!-- 允许程序访问网络链接状态 --><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!-- 允许程序访问WIFI网络状态信息 --><uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <!-- 允许程序改变WIFI链接状态 -->

光有这个网络权限还不够,还需要申请定位权限,这点其实挺烦的,给用户带来困惑,你就是设置个wifi,为啥还要申请定位权限,是不是想干啥xxx;感觉没有ios做的清晰,wifi就是wifi,定位就是定位

    <!--使用wifi及蓝牙功能 需要开启定位才能搜索到蓝牙设备或者wifi--><!--如果app targets 在android9.0(api 28)或者更低,只需要定义ACCESS_COARSE_LOCATION权限--><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

为了方便调用,新建一个wifi功能的统一管理类,如下

public class WifiManager {private android.net.wifi.WifiManager mWifiManager;private Context                      mContext;private static class Holder{private static final WifiManager INSTANCE = new WifiManager();}private WifiManager(){mContext = Configurator.getConfig().getApplicationContext();}public static WifiManager getDefault(){return Holder.INSTANCE;}public void openWifiManager(){mWifiManager = (android.net.wifi.WifiManager)mContext .getSystemService(Context.WIFI_SERVICE);}}

接下来就将一些通用的功能写在这个类里,比如

检测wifi是否打开

    public boolean checkWifiOpen(){return mWifiManager.isWifiEnabled();}

打开和关闭wifi

    // 打开WIFIpublic void openWifi() {if (!mWifiManager.isWifiEnabled()) {mWifiManager.setWifiEnabled(true);}}// 关闭WIFIpublic void closeWifi() {if (mWifiManager.isWifiEnabled()) {mWifiManager.setWifiEnabled(false);}}

有同学可能想了,方法调用了,那怎么知道打开或者关闭成功了呢?很简单,通过接受wifi相应的广播就可以知道了,放在后面讲

获取wifi状态

    public String getWifiState(){switch (mWifiManager.getWifiState()) {case android.net.wifi.WifiManager.WIFI_STATE_DISABLED:return "WI-FI未开启";case android.net.wifi.WifiManager.WIFI_STATE_DISABLING:return "WI-FI正关闭中";case android.net.wifi.WifiManager.WIFI_STATE_ENABLED:return "WI-FI已开启";case android.net.wifi.WifiManager.WIFI_STATE_ENABLING:return "WI-FI正在开启中";case android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN:return "WI-FI功能未知";}return "WIFI功能未知";}

获取连接wifi的名称,通过WifiInfo 对象获取,需要注意的是返回的名称里带有“,所以需要替换掉,这在后面登录wifi时也需要添加上

    //获取连接wifi名称 TP-LINK_7BDE4Cpublic String getWifiSSID(){final android.net.wifi.WifiManager manager = mWifiManager;if(manager.isWifiEnabled()){WifiInfo wifiInfo = manager.getConnectionInfo();if (wifiInfo == null) {return null;}String ssid = wifiInfo.getSSID();if(!TextUtils.isEmpty(ssid)){if(ssid.contains("\"")){return ssid.replaceAll("\"","");}else{return ssid;}}}return null;}

获取ip地址

    //获取wifi的ip地址 这是分配给终端的 ,每个终端不同 比如10.47.105.33public String getWifiAddress(){final android.net.wifi.WifiManager manager = mWifiManager;if(manager.isWifiEnabled()){WifiInfo wifiInfo = manager.getConnectionInfo();if (wifiInfo == null) {return null;}String address = formatIpAddress(wifiInfo.getIpAddress());return address;}return null;}

获取路由器的mac地址

    //获取当前连接的wifi路由器的MAC地址 ,每个终端不同 00:1a:95:94:ea:fcpublic String getRouteMacAddress(){final android.net.wifi.WifiManager manager = mWifiManager;if(manager.isWifiEnabled()){WifiInfo wifiInfo = manager.getConnectionInfo();if (wifiInfo == null) {return null;}String macAddress;macAddress = wifiInfo.getBSSID();return macAddress;}return null;}

获取手机无线网卡的mac地址,这里需要注意版本的区别,在6.0之前是可以通过WifiInfo 对象获取,但是6.0开始,google意识到这侵犯了用户隐私,于是屏蔽了该api,开发者只能获取到02:00:00:00:00:00这样的字符串,需要通过一些特殊的方法获取,比如代码里这样

    /*** 获取本机无线网卡的MAC地址* @return*/public String getLocalMacAddress(){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {return getMacAddr();} else {final android.net.wifi.WifiManager manager = mWifiManager;if(manager.isWifiEnabled()){WifiInfo wifiInfo = manager.getConnectionInfo();if (wifiInfo == null) {return null;}String macAddress;macAddress = wifiInfo.getMacAddress();return macAddress;}}return null;}public String getMacAddr() {try {List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());for (NetworkInterface nif : all) {if (!nif.getName().equalsIgnoreCase("wlan0")) continue;byte[] macBytes = nif.getHardwareAddress();if (macBytes == null) {return "";}StringBuilder res1 = new StringBuilder();for (byte b : macBytes) {res1.append(String.format("%02X:",b));}if (res1.length() > 0) {res1.deleteCharAt(res1.length() - 1);}return res1.toString();}} catch (Exception ex) {}return "02:00:00:00:00:00";}

扫描wifi列表

接下来比较重要的一步就是获取周围的wifi热点,既然在手机里做wifi设置功能,总得获取到周围的wifi列表吧,通过如下方法即可实现

    public List<ScanResult> getScanResults(){final android.net.wifi.WifiManager wifiManager = mWifiManager;List<ScanResult> scanResults = wifiManager.getScanResults();return scanResults;}

直接使用ScanResult这个系统api不太方便,我们可以自定义一个wifi对象,然后将其进行转换,比如

public class WifiBean extends SheetItem implements Comparable<WifiBean>{private String wifiName;private String signalLevel;private String routeMac;private String capabilities;private String pwd;private String state;
}
  • wifiName就是WiFi名,对应着ScanResult的SSID,这里需要注意有很多ScanResult的SSID是空的
  • signalLevel是信号强度,对应着ScanResult的level,但这个值是负数,可以通过WifiManager.calculateSignalLevel(scanResults.get(0).level,10)计算它的强度,区间[0,10)
  • routeMac就是路由器WIFI的MAC地址,对应ScanResult的BSSID
  • capabilities就是加密方案,对应着ScanResult的capabilities,包括接入点支持的认证、密钥管理、加密机制等

其它两个字段是自己维护的,pwd就是wifi登录密码,用户输入的;state就是某个wifi热点的状态,有已连接 正在连接 未连接 三种状态;判断wifi列表中的某个wifi是不是当前连接的wifi,通过比对mac地址就行了

wifi广播

还有一个动态刷新的问题,因为上面的方法获取到的wifi列表是一次性的,后面新增加的wifi或者消失的wifi是没办法在这个wifi列表体现的,这时候就需要广播了

    @Overridepublic void registerReceiver() {connectReceiver = new NetConnectReceiver();IntentFilter filter = new IntentFilter();//监听网络连接状态广播filter.addAction(android.net.wifi.WifiManager.NETWORK_STATE_CHANGED_ACTION);//监听wifi开关变化的状态filter.addAction(android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION);//监听wifi列表变化,比如减少或新增一个wifi热点filter.addAction(android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);//监听wifi连接的过程, 包含可能会出现连接错误的错误码filter.addAction(android.net.wifi.WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);Configurator.getConfig().getApplicationContext().registerReceiver(connectReceiver,filter);}

WifiManager.SCAN_RESULTS_AVAILABLE_ACTION这个广播可以知道当前设备wifi列表的变化,其它几个广播是跟wifi连接有关的,后面连接的时候在讲

接下来在onReceive方法中判断下就行了

if (android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {// 这个监听wifi列表的变化,比如新搜索到了或者减少了一个wifi热点mEvent.setTag(WIFI_HOTSPOT_ADD);EventBus.getDefault().post(mEvent);
}

wifi连接登录

接下来就是最重要的一步了,登录wifi,但在登录前需要做一些判断,比如

  • 当前手机正在尝试连接另一个wifi,但是因为某些原因,比如密码错误造成身份验证失败,连接断开,这时候就需要移除掉这个wifi对应的网络配置
  • 当前手机正与另一个wifi连接上了,那需要禁用掉这个wifi,这里切记不要像第一步那样直接移除掉它的网络配置,因为下次要是重新连接这个wifi的话,就需要再输一次密码了,那用户肯定对你这个app心里是有一万个草泥马的
  • 如果手机已经与用户选的这个wifi连接上了,那就不需要再重复登录了,直接返回
public boolean checkWifiConnect(String ssid){WifiInfo connectionInfo = mWifiManager.getConnectionInfo();if (connectionInfo != null) {SupplicantState supplicantState = connectionInfo.getSupplicantState();if (SupplicantState.DISCONNECTED == supplicantState) {if (connectionInfo.getNetworkId() != -1) {mWifiManager.removeNetwork(connectionInfo.getNetworkId());}return false;}String connectSsid = connectionInfo.getSSID();//如果当前已经连接了网络,但需要连接到其它wifi,那就断开当前wifiif (!TextUtils.isEmpty(connectSsid) && !TextUtils.equals(ssid,connectSsid)) {if (connectionInfo.getNetworkId() != -1) {mWifiManager.disableNetwork(connectionInfo.getNetworkId());}return false;}if (TextUtils.equals(ssid,connectSsid)) {return true;}}return false;}

最后就是登录wifi了,这里主要是操作WifiConfiguration这个对象,这里也分几步:

  • 第一步就是格式化wifi名和密码,需要在用户输入的WiFi名和密码字符串前后都加上"字符
  • 第二步就是看看这个wifi之前是不是已经登录过了,如果登录过了就没必要重复配置了,这是通过在设备保存的WifiConfiguration列表中找一找有没有跟用户选的这个wifi名一样的,如果有就直接启用这个wifi就行了
  • 第三步就需要重新生成一个WifiConfiguration了,然后配置它的wifi名,密码,加密方式,至于为什么需要这样配置,大家可以参考android系统源码,因为手机系统有一个自带的系统设置app,里面有设置wifi的模块,所以大家如果做的功能,系统也自带了,那可以参考系统是如何实现的,我这里是android7.0下面的,所以源码路径在
    android-7.0.0_r1\packages\apps\Settings\src\com\android\settings\wifi\WifiConfigController.java
    public int connectWifi(String targetSsid,String targetPsd,String cipher){// 1、注意热点和密码均包含引号String ssid = '"' + targetSsid + '"';String psd = '"' + targetPsd + '"';if (checkWifiConnect(ssid)) {return WIFI_LOGIN_RESULT_REPEAT;}//2、配置wifi信息WifiConfiguration wifiConfig = findConfigWork(ssid);if (wifiConfig != null) {L.i(TAG,"connectWifi wifiConfig="+wifiConfig.SSID);mCurnentNetId = wifiConfig.networkId;mWifiManager.enableNetwork(mCurnentNetId,true);} else {L.i(TAG,"connectWifi ssid="+ssid+",psd="+psd);wifiConfig = new WifiConfiguration();wifiConfig.SSID = ssid;switch (getCipherType(cipher)) {case WIFI_ENCRYPTION_WPA:wifiConfig.preSharedKey = psd;wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);break;case WIFI_ENCRYPTION_WEP:wifiConfig.wepKeys[0] = psd;wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);break;case WIFI_ENCRYPTION_OPEN://无需密码 直连wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);break;default:}mCurnentNetId = mWifiManager.addNetwork(wifiConfig);mWifiManager.enableNetwork(mCurnentNetId,true);}return mCurnentNetId;}

至于登录结果也是通过广播来获取的,下面这个广播监听wifi的连接状态,即是否连上了一个有效无线路由

if (android.net.wifi.WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {Parcelable parcelable = intent.getParcelableExtra(android.net.wifi.WifiManager.EXTRA_NETWORK_INFO);if (parcelable == null) {return;}NetworkInfo info = (NetworkInfo) parcelable;NetworkInfo.State state = info.getState();switch (state) {case CONNECTED:// 已连接上MessageEvent event = new MessageEvent();event.setTag(WIFI_STATE_CONNECTED);EventBus.getDefault().post(event);break;case CONNECTING://正在连接MessageEvent event2 = new MessageEvent();event2.setTag(WIFI_STATE_ON_CONNECTING);EventBus.getDefault().post(event2);break;case DISCONNECTED://已断开WifiManager.getDefault().removeNetWork();MessageEvent event3 = new MessageEvent();event3.setTag(WIFI_STATE_DISCONNECT);EventBus.getDefault().post(event3);break;case DISCONNECTING://断开中break;default:}}

这个监听wifi的打开与关闭,与wifi的连接无关

if (android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {int state = intent.getIntExtra(android.net.wifi.WifiManager.EXTRA_WIFI_STATE, 0);switch (state) {case android.net.wifi.WifiManager.WIFI_STATE_DISABLED://wifi已关闭MessageEvent event = new MessageEvent();event.setTag(WIFI_STATE_UNOPEN);EventBus.getDefault().post(event);break;case android.net.wifi.WifiManager.WIFI_STATE_DISABLING://wifi关闭中/*MessageEvent event2 = new MessageEvent();event2.setTag(WIFI_STATE_UNOPEN);EventBus.getDefault().post(event2);*/break;case android.net.wifi.WifiManager.WIFI_STATE_ENABLED://wifi已打开MessageEvent event3 = new MessageEvent();event3.setTag(WIFI_STATE_OPENED);EventBus.getDefault().post(event3);break;case android.net.wifi.WifiManager.WIFI_STATE_ENABLING://wifi打开中MessageEvent event4 = new MessageEvent();event4.setTag(WIFI_STATE_ON_OPENING);EventBus.getDefault().post(event4);break;default:}}

总结

到这里基本上wifi该有的功能已经够app使用了,当然还有一些小的方面,比如设置代理等,因为目前我的app还没有这个需求,就没有在这里添加了,等后面看看,如果需要就加上吧

Android wifi列表扫描 密码连接 多个wifi切换登录 广播状态等都在这里相关推荐

  1. android专题-蓝牙扫描、连接、读写

    android专题-蓝牙扫描.连接.读写 概念 外围设备 可以被其他蓝牙设备连接的外部蓝牙设备,不断广播自身的蓝牙名及其数据,如小米手环.共享单车.蓝牙体重秤 中央设备 可以搜索并连接周边的外围设备, ...

  2. wi-fi和路由器怎么连接_解决Wi-Fi问题的最简单方法:严重移动路由器

    wi-fi和路由器怎么连接 Are there Wi-Fi dead zones in your house? Before you do anything drastic, you might be ...

  3. proc wifi 开启_49.Linux-wpa_cli使用之WIFI开启,扫描热点,连接热点,断开热点,WIFI关闭(49)...

    本章学习内容: 1.WIFI如何开启 2.扫描热点 3.连接热点 4. 断开热点 5.关闭WIFI 本节使用的是wpa_supplicant工具,它主要包含wpa_supplicant(命令行模式)与 ...

  4. linux自动wifi断开重连,49.Linux-wpa_cli使用之WIFI开启,扫描热点,连接热点,断开热点,WIFI关闭(49)...

    1.WIFI如何开启ifconfig wlan0 up wpa_supplicant -Dnl80211 -i wlan0 -c /data/wpa_supplicant.conf -B//根据配置文 ...

  5. W800/W801学习记录网络部分(一):WIFI的扫描和连接

    文章目录 前言 一.WIFI扫描 1.函数解析 2.演示 二.连接WIFI 1.函数解析 2.演示 三.进群讨论分享! 前言 本文主要在官方DEMO上分析扫描和连接WIFI的步骤和所调用的函数. 一. ...

  6. win10没有wifi列表,显示不出可用WiFi

    不知道为什么最近,每隔一段时间都遇到WiFi列表显示不出的问题: (图片网上找的,因为我的已经解决了) 开始的时候我也找博客,度娘什么的,按照他们的来,前一两次还行之后就不行了. 上网搜也找不到原因, ...

  7. 亲测绝对有用,,电脑不显示可连接WiFi列表,无法连接WiFi的解决办法。

    正常情况下,大家通过鼠标右键点击wifi-->打开"网络和internet"设置-->高级网络设置中点击更改适配器选项,应该出现如下图中的情况. 也有一部分电脑会出现下 ...

  8. android 连接eap wifi,在Android中以编程方式连接WPA2企业WiFi连接

    因为我的AP具有相同的SSID,所以我想通过使用连接正确的网络奥克.现在我用过这个answer但我不需要澄清,因为这个回答是非常古老的.这里我附上一些关于连接说明的屏幕截图. 在这你可以看到身份和密码 ...

  9. 重装笔记本系统,WiFi标志不亮,连接不了WIFI

    方法一:检查物理WiFi键是否一开,按FN+F5(ThinkPad)等 方法二:下载驱动,下载安装后,还要点击安装刚才安装目录下的autorun.exe(用该方法已解决)

最新文章

  1. 幽门螺杆菌感染会增加代谢综合征和糖尿病风险,竟然“男女有别”?
  2. iis布置asp.net网站——服务应用程序不可用
  3. python编写程序模拟硬币的投掷、假设0表示硬币的反面_修改了Python中的硬币投掷程序,无法完成循环...
  4. loadRunner12试用
  5. 解决办法:“Cannot lock storage /data/hadoop/hdfs/name. The directory is already locked.”
  6. python24点4张扑克_Python实现扑克24点小游戏 ,从此我就没输过
  7. VC++ .net 2005运行库解析
  8. mysql重装_连我mysql读你文件
  9. indesign使用教程,如何将图形添加到项目?
  10. python爬取qq好友网络状态_Python爬虫实战----爬取QQ空间好友说说并生成词云(超详细)...
  11. 基于STM32的(NB-IOT(BC26))温湿度监测系统
  12. mysql中drop语法错误_MySQL DROP TABLE操作以及 DROP 大表时的注意事项
  13. KnockOut+TypeScript+上传图片(oos功能)以及导入Excel文件(oos功能)并回显插入
  14. JAVA web中的一点东西
  15. Excel的图表:组成元素、图表类型与用途、图表可视化大全
  16. 哈里波特与魔法石pdf_哈里·罗伯茨(CSS)CSS框架的命运与失败
  17. 匹马抢三关:讯飞翻译机3.0的破障之战
  18. 【git 整理提交】git rebase -i 命令详解
  19. ssl证书购买后如何认证签发
  20. 工作流+BPM+BPMN

热门文章

  1. 6面阿里拿下30K*15薪Offer!聊聊面试要注意的二三事...
  2. Java报告比较日期,Java Date Compare 日期比较
  3. H5 CSS hack 和浏览器内核
  4. STM32入门 - 芯片简介
  5. 计算机专业必须要读的专业书推荐
  6. 注解方式实现logback日志脱敏
  7. Parameter - Out Of Values报错解决方法
  8. taobao平台上关键字的API接口接入说明
  9. Origin_正态检验以及频率统计
  10. Linux mmap内存映射