提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
咳咳,首先可能进来的各位心里在骂娘,这一看就是标题狗,哈哈哈哈哈哈。各位,来了就别走,进来坐坐。且听小生细细道来。
Wifi功能感觉一直都是开发本地应用的一个痛点,很多时候无从下手。里面设计到的各种Api看着也是一头雾水,理不清楚部分逻辑应该如何实现。依着自己的工作经验,今天在这里简单记录一下,当然都是一些前辈们的经验,我只是个搬运工而已。如果有不当之处还请看客老爷们指出,大家共同进步。
一、Wifi在应用层面涉及哪些东西?
小码农大概归纳了一下几点:
1.通过WifiManager扫描得到的ScanResult的list集合;
2.WifiManager里对已经配置过的wifi项保存起来的WifiConfiguration;
3.对扫描列表具体的wifi项输入密码(部分wifi玩心跳不设置密码,耗子尾汁)进行连接;
4.连接状态的展示;
5.用户操作对配置列表具体的wifi项进行连接、忘记密码、断开连接等;
6.系统会不听使唤自动对已配置过的wifi进行连接。
二、使用步骤
1.一些必要的准备工作
准备工作:先拿到wifiManager,然后需要注册监听一些系统广播,如:WifiManager.WIFI_STATE_CHANGED_ACTION (开关已打开、已关闭、打开中、关闭中)
WifiManager.SCAN_RESULTS_AVAILABLE_ACTION(扫描结果的广播)
WifiManager.SUPPLICANT_STATE_CHANGED_ACTION(密码验证过程发生error的广播)
WifiManager.NETWORK_STATE_CHANGED_ACTION(当前网络状态改变的广播)
代码如下:
mWifiManager = (WifiManager) application.getSystemService(Context.WIFI_SERVICE);
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
registerReceiver(mWifiReceiver, mIntentFilter);
首先需要判断wifi的开关是否打开,wifiManager.isWifiEnabled() 获取当前wifi是否处于打开状态。可根据该状态显示UI开关,当然更好的方式是等待接收到系统广播WIFI_STATE_CHANGED_ACTION时更新UI。下面将接收到广播后的相关处理逻辑罗列出来:
代码如下:
private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case WifiManager.WIFI_STATE_CHANGED_ACTION:
updateWifiState(mWifiManager.getWifiState());
break;
case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
//系统会将扫描结果通过该广播回传回来,
if (!mWifiManager.isWifiEnabled()){
return;
}
boolean isResultUpdated = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
Log.d(TAG, "###Scan Wifi Action, isResultUpdated = "+isResultUpdated+"### scanResults = "+mWifiManager.getScanResults());
if (isResultUpdated){
//整理获取扫描列表数据列表数据
resolveWifiData();
//更新扫描状态UI
updateData(mLiveDataScanStarted, false);
}
break;
case WifiManager.NETWORK_STATE_CHANGED_ACTION://网络状态变化广播
Log.d(TAG, "##NETWORK_STATE_CHANGED_ACTION##");
NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
updateNetwork(networkInfo);
break;
case WifiManager.SUPPLICANT_STATE_CHANGED_ACTION://密码验证广播
int error = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
if (error == WifiManager.ERROR_AUTHENTICATING) {
//密码验证过程出现了错误,可在此处去更新UI逻辑,如:提示用户密码错误的弹窗等。
Log.d(TAG, "updateNetworkInfo#SUPPLICANT_STATE_CHANGED_ACTION#error=ERROR_AUTHENTICATING");
}
break;
default:
break;
}
}
};
2.扫描列表和配置列表的获取和必要的处理
如1所示的广播所示,在接收到扫描列表广播之后即可通过wifiManger去拿到扫描出的ScanResult,这个系统类里面涵盖了wifi相关的ssid、bssid、加密方式、信号强弱等级等信息。咱们可以利用这些信息将此封装成供UI展示的JavaBean对象,用以处理跟界面交互的相关逻辑。
注意事项:
需要将已经配置过的wifi信息,即同SSID的wifiConfiguration从扫描列表里面移除掉。为什么要移除呢,因为咱UI上不能又显示已保存又在下面需要输入密码进行连接吧。这种骚操作还是别干了,哈哈哈哈。
代码如下:
List<ScanResult> dataResults = mWifiManager.getScanResults;
if (null != dataResults){
List<WifiBean> wifiBeans = WifiApiUtil.getWifiBean(dataResults);
//配置STATE_CONNECTED和STATE_SAVED类型wifi
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
List<String> configSSIDs = new ArrayList<>();
for (WifiConfiguration config: configs) {
configSSIDs.add(WifiApiUtil.parseSSID(config.SSID));
WifiBean wifiBean = findConfigWifiBean(wifiBeans, config);
if (wifiBean != null){
//说明找到了相关scanResult,需要判断当前config的连接状态
//iphone断开热点再打开热点会改变BSSID的值,导致热点无法重新去连接,此处重新去对wifiConfig的BSSID赋值
//只有当扫描出来的频段为1的时候才去更新BSSID的值,防止有些同名不同频段(2.4G和5G)更新之后信号不准连接不上的问题
if (wifiBean.getScanResults().size() == 1){
config.BSSID = wifiBean.getScanResults().get(0).BSSID;
}
if (wifiBean.getSsid().equals(WifiApiUtil.parseSSID(wifiInfo.getSSID()))){
NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
mListConfig.add(0, WifiBean.obtainConfiguration(
wifiBean, config, networkInfo.isConnected() ? wifiInfo.getRssi() : wifiBean.getScanResults().get(0).level,
networkInfo.isConnected() ? WifiBean.STATE_CONNECTED : WifiBean.STATE_SAVED));
}else {
mListConfig.add(WifiBean.obtainConfiguration(wifiBean, config,
wifiBean.getScanResults().get(0).level, WifiBean.STATE_SAVED));
}
}else {
//表示这个config没有找到对应的scanResult
if (isConfigEqualWifiInfo(config, wifiInfo)){
//无扫描信息,但是当前配置的wifi是连接状态
NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
Log.d(TAG, "### current networkInfo.isConnected: "+networkInfo.isConnected()+", networkInfo: "+networkInfo);
mListConfig.add(0, WifiBean.obtainConfiguration(config, wifiInfo.getRssi(),
networkInfo.isConnected() ? WifiBean.STATE_CONNECTED : WifiBean.STATE_SAVED));
}
}
}
updateData(mLiveDataConfigList, mListConfig);
//配置STATE_SCAN类型的wifi
for (WifiBean wifiBean : wifiBeans) {
if (configSSIDs.contains(wifiBean.getSsid())){
continue;
}
if (wifiBean.getScanResults() != null && wifiBean.getScanResults().size() > 0){
mListScan.add(WifiBean.obtainScanResult(wifiBean, wifiBean.getScanResults().get(0), WifiBean.STATE_SCAN));
}
}
updateData(mLiveDataScanList, mListScan);
}
文中的mListScan是一个泛型类型为WifiBean的list集合,该集合里面是排除了wifiConfiguration的扫描列表,而mListConfig则是已保存的列表集合,同样是泛型类型为WifiBean。可能有小伙伴比较疑惑这个WifiBean是个什么东西,我贴出WifiBean的代码,其实就是一层新的封装而已:
public class WifiBean {
//wifi名称
private String ssid;
//wifi路由器的 mac 地址
private String bssid;
//信号等级
private int level;
//当前状态
private int state;
//安全性
private int security;
//网络id
private int networkId;
//wifi具体信息:Configuration/ScanResult/WifiInfo
private Object data;
public static WifiBean obtainScanResult(WifiBean bean, @NonNull ScanResult result, int state){
bean.setSsid(result.SSID);
bean.setBssid(result.BSSID);
bean.setState(state);
bean.setLevel(result.level);
bean.setSecurity(WifiApiUtil.getSecurity(result.capabilities));
bean.setNetworkId(-1);
bean.setData(result);
return bean;
}
public static WifiBean obtainConfiguration(@NonNull WifiConfiguration wc, int level, int state){
WifiBean bean = new WifiBean();
bean.setSsid(WifiApiUtil.parseSSID(wc.SSID));
bean.setBssid(wc.BSSID);
bean.setState(state);
bean.setLevel(level);
// bean.setLevel(wc.)//无信号等级,需要scan之后才能获取得到
bean.setSecurity(WifiApiUtil.getSecurity(wc));
bean.setNetworkId(wc.networkId);
bean.setData(wc);
return bean;
}
public static WifiBean obtainConfiguration(WifiBean bean, @NonNull WifiConfiguration wc, int level, int state){
bean.setSsid(WifiApiUtil.parseSSID(wc.SSID));
bean.setBssid(wc.BSSID);
bean.setState(state);
bean.setLevel(level);
// bean.setLevel(wc.)//无信号等级,需要scan之后才能获取得到
bean.setSecurity(WifiApiUtil.getSecurity(wc));
bean.setNetworkId(wc.networkId);
bean.setData(wc);
return bean;
}
}
中间有个findConfigWifiBean,比较的就是WifiBean和WifiConfiguration两者的ssid是否一直,一致则返回该WifiBean,不一致则直接返回null。至此,扫描列表和配置列表的工作基本结束了。
3.对网络状态的处理
wifi的网络状态有以下几种,可以在源码NetworkInfo的DetailedState去查看,这是个泛型类。系统定义如下:
public static enum DetailedState {
IDLE,
SCANNING,
CONNECTING,
AUTHENTICATING,
OBTAINING_IPADDR,
CONNECTED,
SUSPENDED,
DISCONNECTING,
DISCONNECTED,
FAILED,
BLOCKED,
VERIFYING_POOR_LINK,
CAPTIVE_PORTAL_CHECK;
private DetailedState() {
}
}
在网络状态广播里面,通过intent去获取NetworkInfo信息,如下:
NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
NetworkInfo.DetailedState state = networkInfo.getDetailedState();
switch(state){
case AUTHENTICATING:
case OBTAINING_IPADDR:
case CONNECTING:
break;
case CONNECTED:
break;
case DISCONNECTED:
break;
case DISCONNECTING:
break;
default:
break;
}
小伙伴们可以根据具体的网络状态去更新配置列表的相关信息,尤其注意在这里进行编码时是比较容易出现bug的,要多加测试才行。所有的网络状态交由系统处理,只需在此处进行网络状态监听并处理完数据之后再进行UI更新操作。要注意更新UI时要放到主线程里面。
4.与Wifi相关Api接口工具类
直接贴出代码吧,可直接采用CV大法。(CV大法好啊,顶呱呱的好哇。)
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class WifiApiUtil {
private static final String TAG = "WifiApiUtil";
public static boolean isWifiEnable(WifiManager wifiManager){
return wifiManager != null && wifiManager.isWifiEnabled();
}
public static boolean setWifiEnable(WifiManager wifiManager, boolean enable){
return wifiManager != null && wifiManager.setWifiEnabled(enable);
}
// 获取 WIFI 的状态.
public static int getWifiState(WifiManager manager) {
/* WiFi的状态目前有五种, 分别是:
* WifiManager.WIFI_STATE_ENABLING: WiFi正要开启的状态, 是 Enabled 和 Disabled 的临界状态;
* WifiManager.WIFI_STATE_ENABLED: WiFi已经完全开启的状态;
* WifiManager.WIFI_STATE_DISABLING: WiFi正要关闭的状态, 是 Disabled 和 Enabled 的临界状态;
* WifiManager.WIFI_STATE_DISABLED: WiFi已经完全关闭的状态;
* WifiManager.WIFI_STATE_UNKNOWN: WiFi未知的状态, WiFi开启, 关闭过程中出现异常, 或是厂家未配备WiFi外挂模块会出现的情况;
*/
return manager == null ? WifiManager.WIFI_STATE_UNKNOWN : manager.getWifiState();
}
// 获取扫描的WIFI
public static List<ScanResult> getScanResult(WifiManager manager) {
return manager == null ? null : manager.getScanResults();
}
// 获取已经保存过的配置 WIFI.
public static List<WifiConfiguration> getConfiguredNetworks(WifiManager manager) {
return manager == null ? null : manager.getConfiguredNetworks();
}
//将原始的SSID的"\"替换掉
public static String parseSSID(String SSID){
return SSID == null ? "" : SSID.replace("\"","");
}
/**
* 获取配置的wifi的加密方式
* @param config 传入wificonfiguration
*/
public static int getSecurity(WifiConfiguration config){
if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)){
return SecurityType.SECURITY_PSK;
}
if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)){
return SecurityType.SECURITY_EAP;
}
return (config.wepKeys[0] != null) ? SecurityType.SECURITY_WEP : SecurityType.SECURITY_NONE;
}
/**
* 获取扫描结果的wifi的加密方式
* @param capabilities scanResult的capabilities属性
*/
public static int getSecurity(String capabilities){
if (capabilities.contains("WEP")){
return SecurityType.SECURITY_WEP;
}
if (capabilities.contains("EAP")){
return SecurityType.SECURITY_EAP;
}
if (capabilities.contains("PSK")){
return SecurityType.SECURITY_PSK;
}
return SecurityType.SECURITY_NONE;
}
public static List<WifiBean> getWifiBean(List<ScanResult> list) {
List<WifiBean> wifiBeans = new ArrayList<>();
if (list == null || list.isEmpty()) {
return wifiBeans;
}
HashMap<String, WifiBean> map = new HashMap<>();
for (ScanResult scanResult : list) {
if (!map.containsKey(scanResult.SSID)) {
WifiBean wifiBean = new WifiBean();
wifiBean.setSsid(scanResult.SSID);
wifiBean.setScanResult(scanResult);
map.put(scanResult.SSID, wifiBean);
wifiBeans.add(wifiBean);
} else {
WifiBean wifiBean = map.get(scanResult.SSID);
if (wifiBean != null) {
wifiBean.setScanResult(scanResult);
}
}
}
return wifiBeans;
}
/**
* 连接 wifi
* @param wifiBean wifibean
* @param password 当为“”时,是表示已经连接过配置的wifi,当为null则表示不需要密码的wifi
*/
public static boolean connectWiFi(WifiManager wifiManager, WifiBean wifiBean, String password) {
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo != null && wifiInfo.getSSID() != null) {
String ssid = wifiInfo.getSSID();
ssid = ssid.replace("\"", "");
if (ssid.equals(wifiBean.getSsid())) {
return true;
}
}
WifiConfiguration wifiConfig = obtainWifiConfig(wifiBean, password);
Log.d(TAG, "--- wifiConfig: "+wifiConfig);
if (wifiConfig == null) {
return false;
}
wifiManager.updateNetwork(wifiConfig);
wifiManager.disconnect();
int netWorkId = wifiConfig.networkId;
if (netWorkId == -1) {
netWorkId = wifiManager.addNetwork(wifiConfig);
if (netWorkId != -1) {
Log.d(TAG, "--- obtain configuration's id : "+netWorkId);
wifiManager.connect(netWorkId, null);
wifiBean.setNetworkId(netWorkId);
}else {
//Failed to connect to network
Log.d(TAG, "wifiConfiguration config fail");
return false;
}
} else {
Log.d(TAG, "--- connect wifi wificonfiguration --- ");
wifiManager.connect(netWorkId, null);
}
return true;
}
/**
* 断开当前wifi连接
*/
public static void disConnectWifi(WifiManager wifiManager, WifiBean wifiBean){
if (wifiBean.getNetworkId() >= 0){
wifiManager.disableNetwork(wifiBean.getNetworkId());
wifiManager.disconnect();
}
}
/**
* 忘记网络
*/
public static void forgetNetwork(WifiManager wifiManager, int networkId, WifiManager.ActionListener actionListener){
wifiManager.forget(networkId, actionListener);
}
private static WifiConfiguration obtainWifiConfig(WifiBean wifiBean, String password){
WifiConfiguration configuration;
if (wifiBean.getData() instanceof WifiConfiguration){
Log.d(TAG, "current wifibean is wificonfiguration !");
configuration = (WifiConfiguration) wifiBean.getData();
configuration.BSSID = wifiBean.getBssid();
}else {
Log.d(TAG, "current wifibean is ScanResult, config wificonfiguration !");
configuration = new WifiConfiguration();
configuration.allowedAuthAlgorithms.clear();
configuration.allowedGroupCiphers.clear();
configuration.allowedKeyManagement.clear();
configuration.allowedPairwiseCiphers.clear();
configuration.allowedProtocols.clear();
configuration.SSID = "\"" + wifiBean.getSsid() +"\"";
configuration.BSSID = wifiBean.getBssid();
int sercurity = wifiBean.getSecurity();
switch (sercurity){
case SecurityType.SECURITY_WEP:
int i = password.length();
if (((i == 10 || (i == 26) || (i == 58))) && (password.matches("[0-9A-Fa-f]*"))) {
configuration.wepKeys[0] = password;
} else {
configuration.wepKeys[0] = "\"" + password + "\"";
}
configuration.allowedAuthAlgorithms
.set(WifiConfiguration.AuthAlgorithm.SHARED);
configuration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
configuration.wepTxKeyIndex = 0;
break;
case SecurityType.SECURITY_PSK:
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
configuration.preSharedKey = "\"" + password + "\"";
configuration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
configuration.status = WifiConfiguration.Status.ENABLED;
break;
default:
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
break;
}
}
return configuration;
}
/**
* 移除配置的wificonfiguration
*/
public static boolean removeNetwork(WifiManager wifiManager, String name){
boolean removeRst = false;
if (!wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(true);
}
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo != null && wifiInfo.getSSID() != null) {
String ssid = wifiInfo.getSSID();
ssid = ssid.replace("\"", "");
if (ssid.equals(name)) {
wifiManager.disconnect();
}
}
List<WifiConfiguration> configuredNetworks = wifiManager.getConfiguredNetworks();
if (configuredNetworks != null){
for (WifiConfiguration configuration : configuredNetworks) {
if (configuration.SSID.replace("\"", "").equals(name)){
boolean b = wifiManager.removeNetwork(wifiManager.addNetwork(configuration));
removeRst = b;
wifiManager.saveConfiguration();
break;
}
}
}
return removeRst;
}
public static boolean isValidSsid(String ssid) {
if (TextUtils.isEmpty(ssid)) {
return false;
}
return !ssid.equals("<unknown ssid>");
}
}
大意了,没封装好,有WifiBean的耦合。小问题小问题,专治强迫症,我就不改,哎,就是玩儿。
总结
以上就是Wifi开发基本上涵盖了所有功能,余下的UI什么的就全靠自己去把控了。毕竟这玩意儿就看产品经理心情,今天这个样明天那个样。所以,此处强力推荐采用MVVM架构模式,将UI和逻辑分离开来。任尔东南西北风,我自岿然不动。再见!!!