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

1.写在前面

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

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吧

怎么说呢?不管有用没,还是想加上这句
老郭种树原创,转载请加上【搬家】获取WIFI列表及连接WIFI【适配6.0】 这一篇真的够了
点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注