记录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 (营销验证)

参考