移动开发之Wifi列表获取功能
一、场景
业务需要通过App给设备配置无线网络连接,所以需要App获取附近的WiFi列表,并进行网络连接验证。
二、安卓端实现
1、阅读谷歌官网文档,关于Wifi 接口使用
https://developer.android.com/guide/topics/connectivity/wifi-scan?hl=zh-cn
文档的使用流程说的相当明了清晰,注册--扫描--获取。
但是其提到了关于Android 10 以上版本的特别说明, 而且看到代码中:
标明接口过期,但是实际调试使用,发现在10以上版本中也是能正常接收到广播获取扫描结果的。只要申请号对应的权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" /> <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_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
权限当然也需要动态申请:
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},WIFI_REQUEST_FOR_PERMISSION);
Android13 权限额外需求:
https://developer.android.com/guide/topics/connectivity/wifi-permissions
NEARBY_WIFI_DEVICES
这个在实际调试过程中发现,加了和没加都能够获取到wifi列表数据。
另外需要注意的是,定位权限是一回事,手机系统有没有打开定义又是另外一回事,所以在使用此功能前要先判断定位开关是否打开:
// 通过GPS卫星定位,定位级别可以精确到街(通过24颗卫星定位,在室外和空旷的地方定位准确、速度快) boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); // 通过WLAN或移动网络(3G/2G)确定的位置(也称作AGPS,辅助GPS定位。主要用于在室内或遮盖物(建筑群或茂密的深林等)密集的地方定位) boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); if (gps || network) {return true; }
再者就是WLAN 的开关有没有打开,安卓10以下的可以直接通过代码设置,10以上的需要跳转到设置界面,引导用户打开:
int wifiState = wifiManager.getWifiState(); if (WifiManager.WIFI_STATE_ENABLED != wifiState){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {ToastUtil.makeText(mContext,"请打开WiFi开关");startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));//startActivity(new Intent(Settings.Panel.ACTION_WIFI));finish();}else {wifiManager.setWifiEnabled(true);} }
2、网络连接验证调试
为了确认用户输入的密码是正确的,所以想对网络连接进行验证测试,此时就发现安卓10 上下版本的接口差异了:
API29 以下
https://developer.android.com/reference/android/net/wifi/WifiManager#addNetwork(android.net.wifi.WifiConfiguration)
添加 wifiManager.addNetwork 使能 wifiManager.enableNetwork 移除,此处如果是设置里面原有保存的,则无法移除,需要引导 wifiManager.removeNetwork
AndroidQ 以后:
https://developer.android.com/guide/topics/connectivity/wifi-suggest?hl=zh-cn
/*** Android API 29 之后的wifi连接验证* @param ssid 账号* @param pwd 密码, 目前都是用 WAP2 方式*/ @RequiresApi(api = Build.VERSION_CODES.Q) private void connectWifiAfterQ(String ssid, String pwd){WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder();builder.setSsid(ssid);builder.setWpa2Passphrase(pwd);WifiNetworkSpecifier wifiNetworkSpecifier = builder.build();NetworkRequest.Builder networkRequestBuilder1 = new NetworkRequest.Builder();networkRequestBuilder1.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);//networkRequestBuilder1.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {networkRequestBuilder1.setNetworkSpecifier(wifiNetworkSpecifier);}NetworkRequest networkRequest = networkRequestBuilder1.build();ConnectivityManager cm = (ConnectivityManager)getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);// 自己独有的callback中响应事件ConnectivityManager.NetworkCallback networkCallback = newConnectivityManager.NetworkCallback() {@Overridepublic void onAvailable(Network network) {super.onAvailable(network);Log.d(TAG, "onAvailable:" + network);//让本App能够使用到此网络,此时系统其他应用是无法联网的,不知道是不是bugif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {cm.bindProcessToNetwork(network);}}@Overridepublic void onLost(Network network) {Log.d(TAG, "The application no longer has a default network. The last default network was " + network);}@Overridepublic void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {Log.d(TAG, "The default network changed capabilities: " + networkCapabilities);}@Overridepublic void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {Log.d(TAG, "The default network changed link properties: " + linkProperties);}@Overridepublic void onUnavailable() {super.onUnavailable();Log.d(TAG, "onUnavailable:");EventBus.getDefault().post("onUnavailable");}};cm.requestNetwork(networkRequest, networkCallback); }
这种安卓10以上版本的则需要使用suggestion方式去请求网络,但是目前调试发现一个问题就是通过App成功连接到网络之后,手机系统的其他应用则不能通过WiFi联网,而官网给出的移除网络API使用不生效:
https://developer.android.com/reference/android/net/wifi/WifiManager#removeNetworkSuggestions(java.util.List%3Candroid.net.wifi.WifiNetworkSuggestion%3E)
查找了一通资料,最后貌似好像看到说是谷歌系统的bug,至今可能还没有修复:
https://issuetracker.google.com/issues/140398818/resources
在国内的华米OV几大机型上都测试了,具有系统其他应用不能通过WIFI上网的问题,由此影响用户体验,所以最终不进行网络连接测试,改为上报wifi名称和密码,由设备自己去验证网络连接,然后在APP中展示联网效果,由此来看,其他的IoT设备,例如百度音响,是否也是这么实现,并没有通过App来改变手机本身系统的WiFi连接。
但是安卓10以下版本是无此问题的。
参考实现demo:
https://github.com/zly394/WifiListDemo
https://github.com/lilongweidev/Android13Wifi
三、苹果端实现
https://developer.apple.com/documentation/networkextension/wi-fi_configuration/
刚开始看文档,以为会很简单,调用几个接口即可实现。
后来才知道这个所谓的热点助手才能实现获取列表功能,而且这个接口的使用权限要单独申请:
https://developer.apple.com/contact/request/hotspot-helper/
不出意外,这个申请果然被拒了,苹果认为只有运营商或者网络设备制造商才有需要此功能。
Thank you for your interest in the NEHotspotHelper API. This API is not designed for the use you’ve identified, so this request cannot be approved.
The NEHotspotHelper API is meant to be used by hotspot network implementers to connect their users to the internet via a large aggregated network of Wi-Fi Hotspots that they manage.NEHotspotHelper was designed to facilitate internet hotspot network connections and is not appropriate for apps trying to do IoT accessory integration, Wi-Fi location, or other low-level Wi-Fi tasks like signal strength. Specifically, NEHotspotHelper does not let your app initiate a local Wi-Fi scan, or access iOS’ internal list of nearby SSIDs.
Many perceived uses of NEHotspotHelper, such as the configuration of an IoT accessory, can be accomplished with NEHotspotConfiguration, which does not require an Apple-approved entitlement.
For information about enabling your app to configure an IoT accessory, please see the following article: Configuring a Wi-Fi Accessory to Join the User’s Network
For a complete explanation of Wi-Fi management APIs on iOS, please see Technote TN3111: iOS Wi-Fi API Overview.
For further technical assistance please visit the Apple Developer Forums.Thank You,
Apple Developer Relations
于是只能退而求其次,通过用户手动输入WiFi名称和密码,App端来进行网络连接校验,这其中也遇到了权限问题:
-(NSString *)getCurrentWifi{
NSString * wifiName = @"";
CFArrayRef wifiInterfaces = CNCopySupportedInterfaces();
if (!wifiInterfaces) {
wifiName = @"";
}
NSArray *interfaces = (__bridge NSArray *)wifiInterfaces;
for (NSString *interfaceName in interfaces) {
CFBridgingRetain(interfaceName);
CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName));
if (dictRef) {
NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef;
wifiName = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];
NSLog(@"接口查询当前连接的wifi名称为: %@", wifiName);
CFRelease(dictRef);
}
}
CFRelease(wifiInterfaces);
NSLog(@"最终确认当前连接的wifi名称为: %@", wifiName);
return wifiName;
}
就是如上这段代码刚开始始终是获取不到当前连接的WiFi名称的,而网络上查询的资料大部分都是用此方法。后面无意中在发现一个告警日志:
sent invalid result code [1] for Wi-Fi information request
通过网络搜索,原来需要开启capacity Access wifi information .
而不只是引入这两个框架即可:
//连接wifi的框架
#import <NetworkExtension/NetworkExtension.h>
//获取当前wifi的框架
#import <SystemConfiguration/CaptiveNetwork.h>
并且这个方法单独调用也是获取不到当前Wifi名称的,而是需要NEHotspotConfigurationManager 这个请求回调里面执行才可。(不知道是不是也只能查询到自己发起请求连接的WiFi,还是怎么回事)
NEHotspotConfiguration * configuration = [[NEHotspotConfiguration alloc] initWithSSID:wifiName passphrase:passwd isWEP:NO];
[[NEHotspotConfigurationManager sharedManager] applyConfiguration:configuration completionHandler:^(NSError * _Nullable error) {
if(error != nil){
NSLog(@"apply config error=%@ code=%ld", error.description, (long)error.code);
}else{
NSLog(@"apply config success ? wifiName=%@, passwd=%@", wifiName, passwd);
}
//有时无法加入WIFI,没有返回error
if ([[self getCurrentWifi] isEqualToString:wifiName]) {
if (error) {
//无法加入网络,需移除
[[NEHotspotConfigurationManager sharedManager] removeConfigurationForSSID:wifiName];
if(error.code == NEHotspotConfigurationErrorAlreadyAssociated){
//上报账号密码,这个是正确的连接, 应该不会出现这个逻辑了
NSLog(@"WiFi之前已经连接成功,不应该走这个逻辑了,前面每次都先移除了");
//[self ConfigSuccessBack];
}else{
[self alertConfigInfoError:NSLocalizedString(@"wifiConfigAccountPasswordError", nil)];
}
}else{
//连接wifi成功
NSLog(@"连接WiFi成功");
[self ConfigSuccessBack];
}
}else{
//无法加入网络,需移除
[[NEHotspotConfigurationManager sharedManager] removeConfigurationForSSID:wifiName];
[self alertConfigInfoError:NSLocalizedString(@"wifiConfigAccountPasswordError", nil)];
}
}];
总之一番操作之后,确认需要添加的Capacity 有如下三个:
+ com.apple.developer.networking.HotspotConfiguration
+ com.apple.developer.networking.networkextension
+ com.apple.developer.networking.wifi-info
最终苹果端这边还是可以成功验证网络是否正常连接,而且不影响系统重其他应用上网。
一个看似很简单的功能,前前后后,各种零散问题分析查阅调试验证,安卓的各种机型和版本,苹果的各种权限和邮件回复,让这个功能还是弄了蛮久,关键是这种功能涉及敏感安全,为了防止滥用,系统平台随着自己的不断完善发展,对其限制要求会越来越严格。
相关文章:

移动开发之Wifi列表获取功能
一、场景 业务需要通过App给设备配置无线网络连接,所以需要App获取附近的WiFi列表,并进行网络连接验证。 二、安卓端实现 1、阅读谷歌官网文档,关于Wifi 接口使用 https://developer.android.com/guide/topics/connectivity/wifi-scan?hl…...

MyBatisPlus - 实体类 的 常用注解
TableName(“表名”) 假设 表名是 book,实体类类名是 Book MyBatisPlus会进行自动映射 但如果 表名是 tab_book,实体类类名是 Book 那么MyBatisPlus就无法进行自动映射,需要我们使用 TableName注解 去指定实体类对应的表 如下 TableNa…...

vue3+ts+elementui-plus二次封装树形表格实现不同层级展开收起的功能
一、TableTreeLevel组件 <template><div classmain><div class"btns"><el-button type"primary" click"expandLevel(1)">展开一级</el-button><el-button type"primary" click"expandLevel(2…...

Qt之切换语言的方法(传统数组法与Qt语言家)
http://t.csdn.cn/BVigB 传统数组法: 定义一个字符串二维数组, QString weekStr[2][7] {"星期一","星期二","星期三","星期四","星期五","星期六","星期日",\ "Monday&…...

qt root start faild
深入解析chown -r root:root命令_笔记大全_设计学院 ffmpeg第五弹:QtSDLffmpeg视频播放演示_txp玩Linux的博客-CSDN博客...

数据结构—串
4.1串 4.1.1串的定义 串(String)——零个或多个任意字符组成的有限序列 S"a1 a2...an"串的定义——几个术语 子串:串中任意个连续字符组成的子序列称为该串的子串 例如,“abcde”的子串有: “ ”、“a”、…...

hive 全量表、增量表、快照表、切片表和拉链表
全量表:记录每天的所有的最新状态的数据,增量表:记录每天的新增数据,增量数据是上次导出之后的新数据。快照表:按日分区,记录截止数据日期的全量数据切片表:切片表根据基础表,往往只…...

数据结构07:查找[C++][B树Btree]
图源:文心一言 考研对于B树的要求重点在推理手算的部分,只参考王道论坛咸鱼老师的视频就可以了;若时间非常充裕的小伙伴,也可以往下滑了解一下代码~🥝🥝 备注: 这次的代码是从这里复制的&…...

在CSDN学Golang云原生(Kubernetes集群管理)
一,Node的隔离与恢复 在 Kubernetes 集群中,Node 的隔离与恢复通常可以通过以下方式实现: 使用 Taints 和 Tolerations 实现隔离 Taints 和 Tolerations 是 Kubernetes 中用于节点调度的机制。通过给节点添加 taints(污点&…...

WPF实战学习笔记18-优化设计TodoView
文章目录 优化设计TodoView修复新增项目无法编辑问题增加了对完成状态的区分增加了选项卡删除功能更新删除请求URI添加删除命令并初始化UI添加删除按钮更改控制器 增加查询结果为空的图片增加转换器修改UI添加资源、命名空间 添加相关元素 增加了根据状态查询的功能Mytodo.Serv…...

Python版day59
503. 下一个更大元素 II 给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数&…...

[SQL挖掘机] - 算术运算符
在 sql 中,算术运算符主要用于执行数值计算操作,并且在查询语句中具有重要的地位。下面是算术运算符在 sql 中的一些作用和地位: 进行数值计算:算术运算符可以对数值类型的数据进行加减乘除等数值计算操作。例如,可以…...

机器学习基础 数据集、特征工程、特征预处理、特征选择 7.27
机器学习基础 1. 数据集 2. 特征工程 3. 学习分类 4. 模型 5. 损失函数 6. 优化 7. 过拟合 8. 欠拟合数据集 又称资料集、数据集合或者资料集合,是一种由数据所组成的集合特征工程 1. 特征需求 2. 特征设计 3. 特征处理特征预处理、特征选择、特征降维 4. 特征验…...

Sass 常用的功能!
Sass 常用功能 Sass 功能有很多,这边只列举一些比较常用的。 嵌套规则 (Nested Rules) Sass 允许将一套 CSS 样式嵌套进另一套样式中,内层的样式将它外层的选择器作为父选择器。 编译前 .box {.box1 {background-color: red;}.box2 {background-col…...

chmod命令详细使用说明
chmod命令详细使用说明 chmod是Unix和类Unix系统上用于更改文件或目录权限的命令。它是"change mode"的缩写。在Linux和其他类Unix操作系统中,文件和目录具有权限位,用来控制哪些用户可以访问、读取、写入或执行它们。chmod命令允许用户修改这…...

ICC2如何计算Gate Count?
我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧?知识星球入口 我们认为gate count等于standard cell(非physical only)总面积 / 最小驱动二输入与非门面积。 ICC2没有专门的命令去报告gate count,只能自己计算,使用report_d…...

Qtday3作业
作业 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QPushButton> #include <QTextToSpeech> #include <QWidget> #include <QDebug> #include <QTimer> //定时器类 #include <QTime> //时间类 #include <QTimerEvent>…...

全球程序员需要知道的50+网址,有多少你第一次听说?
作为程序员,需要知道的50网址,有多少你第一次听说 GitHub (github.com): 最大的代码托管平台,开源项目和代码分享的社区。程序员可以在这里找到各种有趣的项目,参与开源贡献或托管自己的代码。 Stack Overflow (stackoverflow.co…...

Matlab中实现对一幅图上的局部区域进行放大
大家好,我是带我去滑雪! 局部放大图可以展示图像中的细节信息,使图像更加直观和精美,此次使用magnify工具实现对绘制的figure选择区域绘制,图像效果如下: 1、基本图像绘制 这里选择绘制一个散点图ÿ…...

mysql-速成补充
目录 1.演示事务 编辑 1.1 read-uncommitted 1.2 read-committed 1.3 repeatable read 1.4 幻读 1.5 serializable 1.6 savepoint 2 变量 2.1 语法 2.2 举例 3 存储过程和函数 3.1 特点和语法 3.2 举例 4.函数 4.1 语法 4.2 举例 5 流程控制 5.1 分…...

微信小程序,商城底部工具栏的实现
效果演示: 前提条件: 去阿里云矢量图标,下载8个图标,四个黑,四个红,如图: 新建文件夹icons,把图标放到该文件夹,然后把该文件夹移动到该项目的文件夹里面。如图所示 app…...

Lab———Git使用指北
Lab———Git使用指北 🤖:使用IDEA Git插件实际工作流程 💡 本文从实际使用的角度出发,以IDEA Git插件为基本讲述了如果使用IDEA的Git插件来解决实际开发中的协作开发问题。本文从 远程仓库中拉取项目,在本地分支进行开发&#x…...

ChatGPT的工作原理:从输入到输出
🌷🍁 博主 libin9iOak带您 Go to New World.✨🍁 🦄 个人主页——libin9iOak的博客🎐 🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~ἳ…...

redis数据库与主从复制
目录 一 基本操作 二 执行流程 三 reids持久化 四 rdb和aof持久化的过程 五 为什么会有内存碎片 六 redis组从复制 一 基本操作 set :存放数据 例如 set 键值 内容 set k kokoko k就是键值 kokoko就是内容 get:获取数据 例如 get k 就会出来 k对应的数据 keys 查询键…...

js加载和长任务
js加载和长任务 本文将讲解以下浏览器如何加载js,并介绍一些可以提高网页加载速度的方法。 Evaluate Script 如果我们在devtools的performance中分析过网站的加载性能,可能会看到一个很长的任务,叫做Evaluate Script. 在这种情况下&#x…...

利用Stable diffusion Ai 制作艺术二维码超详细参数和教程
大家有没有发现最近这段时间网上出现了各种各样的AI艺术二维码,这种二维码的出现,简直是对二维码的“颠覆式创新”,直接把传统的二维码提升了一个维度!作为设计师的我们怎么可以不会呢? 今天就教大家怎么制作这种超有艺…...

【C语言课程设计】图书管理系统
引言: 图书管理系统是一个重要的信息管理系统,对于图书馆和书店等机构来说,它能够方便地管理图书的录入、显示、查询、修改和删除等操作。本实验基于C语言开发了一个简单的图书管理系统,通过账户名和密码进行系统访问和权限控制&a…...

在 ArcGIS Pro 中使用 H3 创建蜂窝六边形
H3是Uber开发的分层索引系统,它使用六边形来平铺地球表面。H3在二十面体(一个具有20个三角形面和12个顶点的形状)上构建其六边形网格。由于仅用六边形不可能平铺二十面体,因此每个分辨率需要12个五边形来完成网格。分层索引网格意味着每个六边形都可以细分为子单元六边形。…...

创建Electron项目
一、使用vite 构建 electron项目 npm init vitelatest Need to install the following packages:create-vitelatest Ok to proceed? (y) y √ Project name: ... CertificateDownload √ Package name: ... certificatedownload √ Select a framework: Vue √ Select a var…...

Spring Boot实践一
一、Spring Boot简介 Spring Boot是一个基于Spring框架的快速开发应用程序的工具。它提供了一种快速、方便的方式来创建基于Spring的应用程序,而无需繁琐的配置。Spring Boot通过自动配置和约定大于配置的方式,使得开发者可以更加专注于业务逻辑的实现&…...