小言_互联网的博客

Android wifi列表扫描 密码连接 多个wifi切换登录 广播状态等都在这里

385人阅读  评论(0)

前言

app里有个需求就是在应用内部开发一个wifi设置的功能,避免用户跳到手机wifi设置界面操作,之前没开发过这样的需求,只是简单的判断网络状态,不过心想应该不难,都是挺成熟的东西,其实做了后才知道还是有些坑的,同时也看了网络上的一些文章,只是大都是讲一些小的方面或者讲的很简陋,于是就自己重新整理了下wifi完整操作的内容;背景是app的wifi设置需求

其demo效果如图

需求

  1. 手机没有打开wifi,需要自动打开wifi
  2. 获取可用的wifi列表,如果某个wifi是连接状态,那就用(已连接)标注
  3. 时刻刷新wifi列表,也就是当有可用的wifi热点出现时,要在列表上体现,wifi列表按信号强度倒序排列
  4. 输入密码连接wifi,如果之前已经连接了某个wifi,那就断开那个连接,但是不能清除它的信息,否则下次连接那个wifi时还得重新输入密码
  5. 时刻监听wifi按钮的打开、关闭状态
  6. 获取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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场