Android开发wifi功能竟会如此简单?天啊,怎么可能!!

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

咳咳,首先可能进来的各位心里在骂娘,这一看就是标题狗,哈哈哈哈哈哈。各位,来了就别走,进来坐坐。且听小生细细道来。

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和逻辑分离开来。任尔东南西北风,我自岿然不动。再见!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值