Android Wifi开发记录
文章目录
记录Wifi开发过程中的一些要点和坑。
前置了解
需要先了解一些基础概念。
相关类
- WifiManager,核心入口类
- WifiConfiguration,配置信息
- WifiInfo,当前Wifi连接信息
- ScanResult,扫描的热点信息
相关属性
- networkId
- 连接到wifi时,将生成networkId;
- 不同热点,networkId不同且唯一;
- 通常为大于0的整数,出厂后第一个连接上的wifi的networkId为0;
- 小于0的networkId没有意义,连不上;
- SSID
- 热点名称,可重复
- WifiInfo中的SSID包含了双引号,匹配时需要注意;
- ScanResult中的SSID不包含双引号;
- BSSID
- 一个wifi热点可能有多个AP点,如一个wifi有4个AP点,将有4个ScanResult;
- 类似MAC地址,与SSID一起作为唯一标示;
加密类型
- OPEN,开放网络,无加密;
- WEP,已不推荐;
- WPA/WPA2,目前使用最广泛;
- EAP,企业常用;
权限需求
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Android 6.0以上需要位置权限,才能获取Wifi列表 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- Android 6.0以上是系统权限,可用来修改配置 -->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
- 位置权限需求,参见Android7.0的WifiServiceImpl实现
public List<ScanResult> getScanResults(String callingPackage) {
......
try {
......
// checkCallerCanAccessScanResults, check ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions
if (!canReadPeerMacAddresses && !isActiveNetworkScorer
&& !checkCallerCanAccessScanResults(callingPackage, uid)) {
return new ArrayList<ScanResult>();
}
......
}
相关广播
- WifiManager.WIFI_STATE_CHANGED_ACTION
- wifi开关变化广播
- WifiManager.SCAN_RESULTS_AVAILABLE_ACTION
- 热点扫描结果通知广播
- WifiManager.SUPPLICANT_STATE_CHANGED_ACTION
- 热点连接结果通知广播
- WifiManager.NETWORK_STATE_CHANGED_ACTION
- 网络状态变化广播(与上一广播协同完成连接过程通知)
具体细节
获取实例
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Wifi开关
// 判断wifi是否可用(似乎有时候不太准,特别是刚连上一个wifi的时候)
boolean isWifiEnabled = mWifiManager.isWifiEnabled();
// 控制打开/关闭wifi
boolean result = mWifiManager.setWifiEnabled(true);
// 监听wifi状态
// 广播: WifiManager.WIFI_STATE_CHANGED_ACTION
int wifistate = intent.getIntExtra(
WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_DISABLED
);
switch (wifistate) {
case WifiManager.WIFI_STATE_DISABLED: //wifi已关闭
break;
case WifiManager.WIFI_STATE_ENABLED: //wifi已打开
break;
case WifiManager.WIFI_STATE_ENABLING: //wifi正在打开
break;
}
扫描热点
mWifiManager.startScan();
// 大概2~3秒之后,收到广播WifiManager.SCAN_RESULTS_AVAILABLE_ACTION
boolean isScanned = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true);
// 获取扫描到的wifi列表
List<ScanResult> mScanResultList = mWifiManager.getScanResults();
getScanResults有时获取不到的问题
- SCAN_RESULTS_AVAILABLE_ACTION,监听该广播,收到后再获取扫描列表
- NETWORK_STATE_CHANGED_ACTION,监听该广播,更快确定扫描状态
过滤扫描列表
TreeMap<String, ScanResult> map = new TreeMap<>();
for (ScanResult scanResult : mScanResultList) {
map.put(scanResult.SSID, scanResult);
}
mScanResultList.clear();
mScanResultList.addAll(map.values());
wifi连接历史
// 有SSID和networkId,可以直接连接
// 没有BSSID、密钥等信息
List<WifiConfiguration> configurations = mWifiManager.getConfiguredNetworks();
当前wifi连接
List<WifiConfiguration> configurations = mWifiManager.getConfiguredNetworks();
连接制定wifi
- 创建配置信息
public WifiConfiguration createWifiInfo(String SSID, String pwd, String userName, WifiCipherType type) {
WifiConfiguration config = new WifiConfiguration();
config.allowedAuthAlgorithms.clear();
config.allowedGroupCiphers.clear();
config.allowedKeyManagement.clear();
config.allowedPairwiseCiphers.clear();
config.allowedProtocols.clear();
if(type == WifiCipherType.WIFICIPHER_EAP){
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
config.enterpriseConfig = new WifiEnterpriseConfig();
config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
config.enterpriseConfig.setIdentity(userName);
config.enterpriseConfig.setAnonymousIdentity(userName);
config.enterpriseConfig.setPassword(pwd);
return saveEapConfig(config, SSID, pwd, userName);
}
config.SSID = "\"" + SSID + "\"";
if (type == WifiCipherType.WIFICIPHER_NOPASS) {
config.wepKeys[0] = "";
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
config.wepTxKeyIndex = 0;
}
if (type == WifiCipherType.WIFICIPHER_WEP) {
config.preSharedKey = "\"" + pwd + "\"";
config.hiddenSSID = true;
config.allowedAuthAlgorithms
.set(WifiConfiguration.AuthAlgorithm.SHARED);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
config.allowedGroupCiphers
.set(WifiConfiguration.GroupCipher.WEP104);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
config.wepTxKeyIndex = 0;
}
if (type == WifiCipherType.WIFICIPHER_WPA) {
// 修改之后配置
config.preSharedKey = "\"" + pwd + "\"";
config.hiddenSSID = true;
config.allowedAuthAlgorithms
.set(WifiConfiguration.AuthAlgorithm.OPEN);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
config.allowedPairwiseCiphers
.set(WifiConfiguration.PairwiseCipher.TKIP);
// config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedPairwiseCiphers
.set(WifiConfiguration.PairwiseCipher.CCMP);
} else {
return null;
}
return config;
}
private WifiConfiguration saveEapConfig(WifiConfiguration selectedConfig,String SSID_Name,String passString, String userName) {
/********************************Configuration Strings****************************************************/
final String ENTERPRISE_EAP = "PEAP";
//final String ENTERPRISE_CLIENT_CERT = "keystore://USRCERT_CertificateName";
//final String ENTERPRISE_PRIV_KEY = "USRPKEY_CertificateName";
//CertificateName = Name given to the certificate while installing it
/*Optional Params- My wireless Doesn't use these*/
final String ENTERPRISE_PHASE2 = "";
final String ENTERPRISE_ANON_IDENT = "ABC";
final String ENTERPRISE_CA_CERT = ""; // If required: "keystore://CACERT_CaCertificateName"
/********************************Configuration Strings****************************************************/
/*Create a WifiConfig*/
/*AP Name*/
selectedConfig.SSID = ""+SSID_Name;
/*Priority*/
selectedConfig.priority = 40;
/*Enable Hidden SSID*/
selectedConfig.hiddenSSID = true;
/*Key Mgmnt*/
selectedConfig.allowedKeyManagement.clear();
selectedConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
selectedConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
/*Group Ciphers*/
selectedConfig.allowedGroupCiphers.clear();
selectedConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
selectedConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
selectedConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
selectedConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
/*Pairwise ciphers*/
selectedConfig.allowedPairwiseCiphers.clear();
selectedConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
selectedConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
/*Protocols*/
selectedConfig.allowedProtocols.clear();
selectedConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
selectedConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
selectedConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
selectedConfig.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
return selectedConfig;
}
- 生成networkId
int networkId = mWifiManager.addNetwork(config);
建立连接
- 对于已连接过的wifi,可以跳过生成配置和networkId,直接建立连接;
- 当使用了错误密码时,需要先移除掉错误配置;
// boolean, 是否需要断开其它Wifi网络 boolean enable = mWifiManager.enableNetwork(netId, true); // 可选操作,若enableNetwork失败,则连之前成功过的网络 boolean reconnect = mWifiManager.reconnect();
连接结果
- 密码错误,通过广播WifiManager.SUPPLICANT_STATE_CHANGED_ACTION
int error = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0); if (WifiManager.ERROR_AUTHENTICATING == error) { //密码错误,认证失败 }
- 其他,通过广播WifiManager.NETWORK_STATE_CHANGED_ACTION
if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); if (null != info) { NetworkInfo.DetailedState state = info.getDetailedState(); } }
断开连接
mWifiManager.disconnect();
- 结果在多次广播中得到反馈
- WifiManager.SUPPLICANT_STATE_CHANGED_ACTION
- WifiManager.NETWORK_STATE_CHANGED_ACTION
忘记网络
- 6.0以下系统
boolean isRemoved = wifiManager.removeNetwork(networkId);
- 6.0以上系统
int networkId = mWifiManager.getConnectionInfo().getNetworkId();
Class wifiManagerClass = WifiManager.class;
if(networkId < 0){
return false;
}
Class<?> actionListenerClass = null;
try {
actionListenerClass = Class.forName("android.net.wifi.WifiManager$ActionListener");
} catch (ClassNotFoundException e) {
LogKit.e(e.getMessage());
}
if(actionListenerClass == null){
return false;
}
Method forgetMethod = null;
try{
forgetMethod = wifiManagerClass.getMethod("forget", int.class, actionListenerClass);
} catch (NoSuchMethodException e){
}
if(forgetMethod == null){
return false;
}
try {
forgetMethod.invoke(mWifiManager, networkId, null);
} catch (IllegalAccessException | InvocationTargetException e) {
}
- 6.0后系统限制
- 系统不允许修改或删除由用户或其他应用创建的配置;
- 限制解除:
- frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiConfigManager.java
- (3144):canModifyNetwork(int uid, int networkId, boolean onlyAnnotate)
Captive Portal Check (营销验证)
- 商场/部分企业存在wifi营销验证,通过验证后才能够正常上网,否则流量都被劫持到某个固定页面,显示营销内容;
- 检测是否处于未经过营销验证的状态,通常访问某个固定地址,根据返回结果是否204确定;