前言
app里有个需求就是在应用内部开发一个wifi设置的功能,避免用户跳到手机wifi设置界面操作,之前没开发过这样的需求,只是简单的判断网络状态,不过心想应该不难,都是挺成熟的东西,其实做了后才知道还是有些坑的,同时也看了网络上的一些文章,只是大都是讲一些小的方面或者讲的很简陋,于是就自己重新整理了下wifi完整操作的内容;背景是app的wifi设置需求
其demo效果如图
需求
- 手机没有打开wifi,需要自动打开wifi
- 获取可用的wifi列表,如果某个wifi是连接状态,那就用(已连接)标注
- 时刻刷新wifi列表,也就是当有可用的wifi热点出现时,要在列表上体现,wifi列表按信号强度倒序排列
- 输入密码连接wifi,如果之前已经连接了某个wifi,那就断开那个连接,但是不能清除它的信息,否则下次连接那个wifi时还得重新输入密码
- 时刻监听wifi按钮的打开、关闭状态
- 获取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
// 打开WIFI
public void openWifi() {
if (!mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(true);
}
}
// 关闭WIFI
public 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_7BDE4C
public 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.33
public 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:fc
public 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列表体现的,这时候就需要广播了
@Override
public 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,那就断开当前wifi
if (!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还没有这个需求,就没有在这里添加了,等后面看看,如果需要就加上吧
转载:https://blog.csdn.net/qq_30993595/article/details/101621498