获取WIFI列表及连接WIFI【适配6.0】 这一篇真的够了

来源:互联网 发布:淘宝客服发货话术 编辑:程序博客网 时间:2024/05/22 04:45

1.写在前面

项目中需要获取wifi列表同时连接wifi,本来这种需要以前做过的,感觉挺简单的,后面发现坑比较多,看了很多博客和资料总是有些问题解决不了,比如兼容android6.0版本,到底需要动态申请哪些权限,以什么样的方式友好提示用户,怎样监听wifi连接成功或者失败 或者wifi列表的变化等等,现在需求完成,总结下吧!
这里写图片描述
这里写图片描述
这里写图片描述

2.步骤分析

  1. 进入页面。检测wifi开关是否打开,权限是否已经获取,如果没,动态申请权限,如果是请求wifi列表。
  2. 获取扫描wifi列表,循环遍历转成自己的wifi对象和集合。更新适配器,刷新界面列表
  3. 书写适配器,Recyclerview设置适配器。
  4. 注册广播,监听wifi开关状态,监听wifi是否已经连接,监听wifi列表是否变化。
  5. 点击wifi列表item连接。具体情况具体分析,已连接–>wifi详情;未连接–>连接;

3.代码

3.1 android6.0系统权限申请

进入页面检测权限以及wifi开启状态

     //权限请求码    private static final int PERMISSION_REQUEST_CODE = 0;    //两个危险权限需要动态申请    private static final String[] NEEDED_PERMISSIONS = new String[]{            Manifest.permission.ACCESS_COARSE_LOCATION,            Manifest.permission.ACCESS_FINE_LOCATION    };    private boolean mHasPermission;   mHasPermission = checkPermission();        if (!mHasPermission) {            requestPermission();        }     /**     * 检查是否已经授予权限     * @return     */    private boolean checkPermission() {        for (String permission : NEEDED_PERMISSIONS) {            if (ActivityCompat.checkSelfPermission(this, permission)                    != PackageManager.PERMISSION_GRANTED) {                return false;            }        }        return true;    }    /**     * 申请权限     */    private void requestPermission() {        ActivityCompat.requestPermissions(this,                NEEDED_PERMISSIONS, PERMISSION_REQUEST_CODE);    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        super.onRequestPermissionsResult(requestCode, permissions, grantResults);        boolean hasAllPermission = true;        if (requestCode == PERMISSION_REQUEST_CODE) {            for (int i : grantResults) {                if (i != PackageManager.PERMISSION_GRANTED) {                    hasAllPermission = false;   //判断用户是否同意获取权限                    break;                }            }            //如果同意权限            if (hasAllPermission) {                mHasPermission = true;                if(WifiSupport.isOpenWifi(MainActivity.this) && mHasPermission){  //如果wifi开关是开 并且 已经获取权限                    sortScaResult();                }else{                    Toast.makeText(MainActivity.this,"WIFI处于关闭状态或权限获取失败",Toast.LENGTH_SHORT).show();                }            } else {  //用户不同意权限                mHasPermission = false;                Toast.makeText(MainActivity.this,"获取权限失败",Toast.LENGTH_SHORT).show();            }        }    }

对于如上的代码主要是针对android6.0系统,获取wifi信息。首先进入此页面就检测是否已经获取权限或者wifi已经打开,如果wifi打开并且权限已经获取,搜索周围wifi数据,如果没有获取权限则申请权限。这一部分有关权限,我找了很多资料,最后确定为这两个是正确的

       Manifest.permission.ACCESS_COARSE_LOCATION,       Manifest.permission.ACCESS_FINE_LOCATION

1.通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500米,这是第一个。
2.通过GPS芯片接收卫星的定位信息,定位精度达10米以内,这是第二个权限

3.2 获取wifi列表集合数据

这里特别要注意,对于wifi对象,我们不能使用原生的对象ScanResult,因为它的属性中不能确定wifi的状态,不能知道此wifi是否处于连接状态,所以我们需要新建bean对象作为wifi的信息,然后将ScanResult的集合循环遍历,转成自己bean的集合。
ScanResult的属性如下:

    public String SSID;    public String capabilities;    public int centerFreq0;    public int centerFreq1;    public int channelWidth;    public int frequency;    public int level;    public CharSequence operatorFriendlyName;    public long timestamp;    public CharSequence venueName;

自己定义的bean如下,并且为了保证集合中按照信号强度排序,需要实现Comparable接口,然后重写compareTo方法,代码如下:

public class WifiBean implements Comparable<WifiBean> {    private String wifiName;    private String level;    private String state;  //已连接  正在连接  未连接 三种状态    private String capabilities;//加密方式    .......    省略get set方法    .......    @Override    public int compareTo(WifiBean o) {        int level1 = Integer.parseInt(this.getLevel());        int level2 = Integer.parseInt(o.getLevel());        return level1 - level2;    }}

所以接下来的思路就简单了,两个bean,一个是原生的ScanResult,一个是自定义的WifiBean,首先获取
ScanResult的集合,去重,循环遍历获取WifiBean的集合。

    /**     * 去除同名WIFI     *     * @param oldSr 需要去除同名的列表     * @return 返回不包含同命的列表     */    public static List<ScanResult> noSameName(List<ScanResult> oldSr)    {        List<ScanResult> newSr = new ArrayList<ScanResult>();        for (ScanResult result : oldSr)        {            if (!TextUtils.isEmpty(result.SSID) && !containName(newSr, result.SSID))                newSr.add(result);        }        return newSr;    }    /**     * 判断一个扫描结果中,是否包含了某个名称的WIFI     * @param sr 扫描结果     * @param name 要查询的名称     * @return 返回true表示包含了该名称的WIFI,返回false表示不包含     */    public static boolean containName(List<ScanResult> sr, String name)    {        for (ScanResult result : sr)        {            if (!TextUtils.isEmpty(result.SSID) && result.SSID.equals(name))                return true;        }        return false;    }   List<ScanResult> scanResults = WifiSupport.noSameName(WifiSupport.getWifiScanResult(this));//List<WifiBean> realWifiList = new ArrayList<>();   /**     * 获取wifi列表然后将bean转成自己定义的WifiBean     */    public void sortScaResult(){        List<ScanResult> scanResults = WifiSupport.noSameName(WifiSupport.getWifiScanResult(this));        realWifiList.clear();        if(!CollectionUtils.isNullOrEmpty(scanResults)){            for(int i = 0;i < scanResults.size();i++){                WifiBean wifiBean = new WifiBean();                wifiBean.setWifiName(scanResults.get(i).SSID);                wifiBean.setState(AppContants.WIFI_STATE_UNCONNECT);   //只要获取都假设设置成未连接,真正的状态都通过广播来确定                wifiBean.setCapabilities(scanResults.get(i).capabilities);                wifiBean.setLevel(WifiSupport.getLevel(scanResults.get(i).level)+"");                realWifiList.add(wifiBean);                //排序                Collections.sort(realWifiList);                adapter.notifyDataSetChanged();            }        }    }

这个sortScaResult()使用的比较多,比较简单,就是循环遍历list,然后set,组成一个新的list,特别要注意的是

    public static final String WIFI_STATE_CONNECT = "已连接";    public static final String WIFI_STATE_ON_CONNECTING = "正在连接";    public static final String WIFI_STATE_UNCONNECT = "未连接";
wifiBean.setState(AppContants.WIFI_STATE_UNCONNECT);   //只要获取都假设设置成未连接,真正的状态都通过广播来确定

假设进入此页面,wifi列表中一个wifi已经处于连接状态,这里获取是我们还是假设它处于未连接状态,因为后面我们都是需要通过广播来确定网络连接状态,并且为了提高用户体验,还需要将已经连接的wifi放到列表首位。

3.3 Adapter适配器的书写

对于此列表的适配器比较简单,主要就是onBindViewHolder和点击事件,对于onBindViewHolder看效果图中,我们可以知道如果某一个wifi处于已连接或者正在连接状态,wifi名称的颜色需要改变。然后对于点击事件,这里也是采用常规的方法,在activity中回调,代码如下

    @Override    public void onBindViewHolder(MyViewHolder holder, final int position) {        final WifiBean bean = resultList.get(position);        holder.tvItemWifiName.setText(bean.getWifiName());        holder.tvItemWifiStatus.setText("("+bean.getState()+")");        //可以传递给adapter的数据都是经过处理的,已连接或者正在连接状态的wifi都是处于集合中的首位,所以可以写出如下判断        if(position == 0  && (AppContants.WIFI_STATE_ON_CONNECTING.equals(bean.getState()) || AppContants.WIFI_STATE_CONNECT.equals(bean.getState()))){            holder.tvItemWifiName.setTextColor(mContext.getResources().getColor(R.color.homecolor1));            holder.tvItemWifiStatus.setTextColor(mContext.getResources().getColor(R.color.homecolor1));        }else{            holder.tvItemWifiName.setTextColor(mContext.getResources().getColor(R.color.gray_home));            holder.tvItemWifiStatus.setTextColor(mContext.getResources().getColor(R.color.gray_home));        }        holder.itemview.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                onItemClickListener.onItemClick(view,position,bean);            }        });    }


3.4 注册监听广播

这里我把广播注册在onResume()中,因为有一些需求是点击wifi连接后可能会跳转 或者 点击wifi连接后有fragmentdialog或者activitydialog等等,万一需要回到这个页面,就不会在经历onCreate(),这样不太好,这里注册了三个广播

    @Override    protected void onResume() {        super.onResume();        //注册广播        wifiReceiver = new WifiBroadcastReceiver();        IntentFilter filter = new IntentFilter();        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);//监听wifi是开关变化的状态        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);//监听wifiwifi连接状态广播        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);//监听wifi列表变化(开启一个热点或者关闭一个热点)        this.registerReceiver(wifiReceiver, filter);    }    @Override    protected void onPause() {        super.onPause();        WifiListActivity.this.unregisterReceiver(wifiReceiver);    }

其实上面杀那个广播中最主要的是第二个,当然其他两个也需要,但是接收到广播后需要做的事情并不复杂,先看第一个简单的

   if(WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())){                int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0);                switch (state){                    /**                     * WIFI_STATE_DISABLED    WLAN已经关闭                     * WIFI_STATE_DISABLING   WLAN正在关闭                     * WIFI_STATE_ENABLED     WLAN已经打开                     * WIFI_STATE_ENABLING    WLAN正在打开                     * WIFI_STATE_UNKNOWN     未知                     */                    case WifiManager.WIFI_STATE_DISABLED:{                        Log.d(TAG,"已经关闭");                        break;                    }                    case WifiManager.WIFI_STATE_DISABLING:{                        Log.d(TAG,"正在关闭");                        break;                    }                    case WifiManager.WIFI_STATE_ENABLED:{                        Log.d(TAG,"已经打开");                        sortScaResult();                        break;                    }                    case WifiManager.WIFI_STATE_ENABLING:{                        Log.d(TAG,"正在打开");                        break;                    }                    case WifiManager.WIFI_STATE_UNKNOWN:{                        Log.d(TAG,"未知状态");                        break;                    }                }            }

这是在广播接收器中接收到第一个广播wifi开关状态的,并不需要做什么,如果wifi是关闭的 直接弹出toast,如果是打开 搜索下wifi列表集合,
然后看第二个广播,监听下wifi是否连接了一个有效的路由,比如wifi进入这个页面并有连接wifi,是处于4G或者既不是wifi也不是4G,那么将所有wifi设置成未连接状态

      for(int i = 0;i < realWifiList.size();i++){//没连接上将 所有的连接状态都置为“未连接”                        realWifiList.get(i).setState(AppContants.WIFI_STATE_UNCONNECT);                    }                    adapter.notifyDataSetChanged();

如果进入此页面本身就已经连接了一个wifi,那么获取到已经连接wifi的信息数据,并且将它放到集合中的首位,设置成已连接状态

     Log.d(TAG,"wifi连接上了");                    hidingProgressBar();                    WifiInfo connectedWifiInfo = WifiSupport.getConnectedWifiInfo(MainActivity.this);                    //连接成功 跳转界面 传递ip地址                    Toast.makeText(MainActivity.this,"wifi连接上了",Toast.LENGTH_SHORT).show();                    connectType = 1;                    wifiListSet(connectedWifiInfo.getSSID(),connectType);

正在连接状态也是如上,和已连接一样
第三个广播很简单,如果周围某一wifi刚好发生变化,比如关闭了,刷新下wifi列表数据
总的代码如下

  //监听wifi状态    public class WifiBroadcastReceiver extends BroadcastReceiver {        @Override        public void onReceive(Context context, Intent intent) {            if(WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())){                int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0);                switch (state){                    /**                     * WIFI_STATE_DISABLED    WLAN已经关闭                     * WIFI_STATE_DISABLING   WLAN正在关闭                     * WIFI_STATE_ENABLED     WLAN已经打开                     * WIFI_STATE_ENABLING    WLAN正在打开                     * WIFI_STATE_UNKNOWN     未知                     */                    case WifiManager.WIFI_STATE_DISABLED:{                        Log.d(TAG,"已经关闭");                        Toast.makeText(MainActivity.this,"WIFI处于关闭状态",Toast.LENGTH_SHORT).show();                        break;                    }                    case WifiManager.WIFI_STATE_DISABLING:{                        Log.d(TAG,"正在关闭");                        break;                    }                    case WifiManager.WIFI_STATE_ENABLED:{                        Log.d(TAG,"已经打开");                        sortScaResult();                        break;                    }                    case WifiManager.WIFI_STATE_ENABLING:{                        Log.d(TAG,"正在打开");                        break;                    }                    case WifiManager.WIFI_STATE_UNKNOWN:{                        Log.d(TAG,"未知状态");                        break;                    }                }            }else if(WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(intent.getAction())){                NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);                Log.d(TAG, "--NetworkInfo--" + info.toString());                if(NetworkInfo.State.DISCONNECTED == info.getState()){//wifi没连接上                    Log.d(TAG,"wifi没连接上");                    hidingProgressBar();                    for(int i = 0;i < realWifiList.size();i++){//没连接上将 所有的连接状态都置为“未连接”                        realWifiList.get(i).setState(AppContants.WIFI_STATE_UNCONNECT);                    }                    adapter.notifyDataSetChanged();                }else if(NetworkInfo.State.CONNECTED == info.getState()){//wifi连接上了                    Log.d(TAG,"wifi连接上了");                    hidingProgressBar();                    WifiInfo connectedWifiInfo = WifiSupport.getConnectedWifiInfo(MainActivity.this);                    //连接成功 跳转界面 传递ip地址                    Toast.makeText(MainActivity.this,"wifi连接上了",Toast.LENGTH_SHORT).show();                    connectType = 1;                    wifiListSet(connectedWifiInfo.getSSID(),connectType);                }else if(NetworkInfo.State.CONNECTING == info.getState()){//正在连接                    Log.d(TAG,"wifi正在连接");                    showProgressBar();                    WifiInfo connectedWifiInfo = WifiSupport.getConnectedWifiInfo(MainActivity.this);                    connectType = 2;                    wifiListSet(connectedWifiInfo.getSSID(),connectType );                }            }else if(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())){                Log.d(TAG,"网络列表变化了");                wifiListChange();            }        }    }

上面的将以连接或者正在连接放到首位去方法

    /**     * 将"已连接"或者"正在连接"的wifi热点放置在第一个位置     * @param wifiName     * @param type     */    public void wifiListSet(String wifiName , int type){        int index = -1;        WifiBean wifiInfo = new WifiBean();        if(CollectionUtils.isNullOrEmpty(realWifiList)){            return;        }        for(int i = 0;i < realWifiList.size();i++){            realWifiList.get(i).setState(AppContants.WIFI_STATE_UNCONNECT);        }        Collections.sort(realWifiList);//根据信号强度排序        for(int i = 0;i < realWifiList.size();i++){            WifiBean wifiBean = realWifiList.get(i);            if(index == -1 && ("\"" + wifiBean.getWifiName() + "\"").equals(wifiName)){                index = i;                wifiInfo.setLevel(wifiBean.getLevel());                wifiInfo.setWifiName(wifiBean.getWifiName());                wifiInfo.setCapabilities(wifiBean.getCapabilities());                if(type == 1){                    wifiInfo.setState(AppContants.WIFI_STATE_CONNECT);                }else{                    wifiInfo.setState(AppContants.WIFI_STATE_ON_CONNECTING);                }            }        }        if(index != -1){            realWifiList.remove(index);            realWifiList.add(0, wifiInfo);            adapter.notifyDataSetChanged();        }    }


3.5 点击wifi连接

点击事件这里比较复杂,需要根据自己的需求来,首先当然一个item处于“未连接”或者“已连接”才可以点击,然后如果“已连接”点击后出现什么情况,不同的需求有不同的代码,一般常规的可能就是弹出dialog显示下已经连接wifi的信息数据,这里我没考虑这种情况了,然后“未连接”分为很多情况,比如需不需要密码,不需要密码直接用方法连接,如果需要密码 又分为好几种情况,以前连接过没,有没有保存的密码,以前从没连接过,弹出dialog输入密码。
这里我没有分为这么多情况,就两种,有密码 和 没密码 ,只要是要输入密码的wifi全部重新输入密码

       adapter.setOnItemClickListener(new WifiListAdapter.onItemClickListener() {            @Override            public void onItemClick(View view, int postion, Object o) {                WifiBean wifiBean = realWifiList.get(postion);                if(wifiBean.getState().equals(AppContants.WIFI_STATE_UNCONNECT) || wifiBean.getState().equals(AppContants.WIFI_STATE_CONNECT)){                    String capabilities = realWifiList.get(postion).getCapabilities();                    if(WifiSupport.getWifiCipher(capabilities) == WifiSupport.WifiCipherType.WIFICIPHER_NOPASS){//无需密码                        WifiConfiguration exsits = WifiSupport.createWifiConfig(wifiBean.getWifiName(), null, WifiSupport.WifiCipherType.WIFICIPHER_NOPASS);                        WifiSupport.addNetWork(exsits, MainActivity.this);                    }else{   //需要密码,弹出输入密码dialog                        noConfigurationWifi(postion);                    }                }            }        });


4.感谢

权限列表
很厉害的人
源码,如果帮到你 Star 下

注意 :比较尴尬,本来以为没问题的了,我在华为荣耀4A android5.1上和小米 5S android7.0上测试,有无密码都可以连接,但是今天领导被用户投诉了一大堆,直接找到我说华为P系列全部连不上,然后测试了下,还真的是,他们的手机大部分都是6.0以上的,但是我觉得我能在7.0上没问题,应该不是版本的问题,难道只是华为荣耀这种情况,然后debug 打log对比华为荣耀4A android5.1 和 华为荣耀P android6.0招了下就是WifiConfiguration对象总是有点不一样,然后搜了下 还真的搜到相关内容,
十分感谢这位大佬
通过他的思路,修改了下代码,解决了,这博客代码没改过来,github上代码提交更新了,如果帮到你 给个star吧

原创粉丝点击