您的位置:首页 > 其它

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

2017-11-27 22:12 651 查看

1.写在前面

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







2.步骤分析

进入页面。检测wifi开关是否打开,权限是否已经获取,如果没,动态申请权限,如果是请求wifi列表。

获取扫描wifi列表,循环遍历转成自己的wifi对象和集合。更新适配器,刷新界面列表

书写适配器,Recyclerview设置适配器。

注册广播,监听wifi开关状态,监听wifi是否已经连接,监听wifi列表是否变化。

点击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吧
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: