欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android连接指定Wifi的方法实例代码

程序员文章站 2024-02-09 22:01:46
本篇文章主要记录一下android中打开wifi、获取wifi接入点信息及连接指接入点的方法。 自己写的demo主要用于测试接口的基本功能,因此界面及底层逻辑比较粗糙。...

本篇文章主要记录一下android中打开wifi、获取wifi接入点信息及连接指接入点的方法。

自己写的demo主要用于测试接口的基本功能,因此界面及底层逻辑比较粗糙。

demo的整体界面如下所示:

Android连接指定Wifi的方法实例代码

上图中的open按键负责开启wifi;

get按键负责获取扫描到的接入点信息。

当获取到接入点信息后,我选取了其中的名称及信号强度,以列表的形式显示在主界面下方,如下图:

Android连接指定Wifi的方法实例代码

当点击列表中的item时,就会去连接对应的接入点。
自己的逻辑比较简单,测试时的代码,假定连接的是不许要密码或密码已知的接入点。

demo的布局文件就不介绍了,就是button和recyclerview。
主要记录一下,使用到的核心代码。

 ....................
  //open按键点击后的逻辑
  mopenwifibutton.setonclicklistener(new view.onclicklistener() {
   @override
   public void onclick(view v) {
    //wifimanager的iswifienabled接口,用于判断wifi开关是否已经开启
    if (!mwifimanager.iswifienabled()) {
     //setwifienabled接口用于开启wifi
     mwifimanager.setwifienabled(true);
     mmainhandler.post(mmainrunnable);
    }
   }
  });
  ....................

mmainrunnable的代码如下,主要用于判断wifi是否开启成功。

................
 private runnable mmainrunnable = new runnable() {
  @override
  public void run() {
   if (mwifimanager.iswifienabled()) {
    //开启成功后,使能get按键
    mgetwifiinfobutton.setenabled(true);
   } else {
    mmainhandler.postdelayed(mmainrunnable, 1000);
   }
  }
 };
 ..............

这部分代码,主要使用了wifimanager的公有接口,开启wifi开关及判断开启状态。
这部分操作需要的权限是:

<uses-permission android:name="android.permission.access_wifi_state"/>
 <uses-permission android:name="android.permission.change_wifi_state"/>

get按键被点击后,对应的代码如下:

.................
  mgetwifiinfobutton.setonclicklistener(new view.onclicklistener() {
   @override
   public void onclick(view v) {
    if (mwifimanager.iswifienabled()) {
     //getscanresults接口将返回list<scanresult>
     //scanresult中保留了每个接入点的基本信息
     mscanresultlist = mwifimanager.getscanresults();
     //多个接入点可能携带相同的信息,形成一个整体的wifi覆盖网络
     //因此,筛除一些冗余信息
     sortlist(mscanresultlist);
     //我使用的是recyclerview,得到数据后,刷新界面进行显示
     mwifiinforecyclerview.getadapter().notifydatasetchanged();
    }
   }
  });
  .................

上面这部分代码也比较简单,主要利用wifimanager的getscanresults接口,获取终端探索到的接入点信息。
其中,sortlist的代码如下:

 ..............
 private void sortlist(list<scanresult> list) {
  treemap<string, scanresult> map = new treemap<>();
  //demo中仅按照ssid进行筛选
  //实际使用时,还可以参考信号强度等条件
  for (scanresult scanresult : list) {
   map.put(scanresult.ssid, scanresult);
  }
  list.clear();
  list.addall(map.values());
 }
 .............

这部分代码唯一需要注意的地方是,需要申明权限:

<uses-permission android:name="android.permission.access_coarse_location"/>
 <uses-permission android:name="android.permission.access_fine_location"/>

同时,在高版本中还需要主动获取运行时权限。

权限的要求,是由wifiserviceimpl的实现决定的,我们以android 7.0为例,看看对应的代码:

public list<scanresult> getscanresults(string callingpackage) {
 //这里要求的是access_wifi_state
 enforceaccesspermission();
 ............
 try {
  ...........
  if (!canreadpeermacaddresses && !isactivenetworkscorer
    //在checkcallercanaccessscanresults中检查了access_fine_location和access_coarse_location
    //如果没有这两个权限,就会返回一个empty list
    && !checkcallercanaccessscanresults(callingpackage, uid)) {
   return new arraylist<scanresult>();
  }
  ...........
 } fianlly {
  ..........
 }
}

获取到信息后,就可以显示和点击列表中的item了。

由于自己使用的是recyclerview,因此这部分工作全部交给了对应viewholder:

 ...............
 private class scanresultviewholder extends recyclerview.viewholder {
  private view mview;
  private textview mwifiname;
  private textview mwifilevel;
  scanresultviewholder(view itemview) {
   super(itemview);
   mview = itemview;
   mwifiname = (textview) itemview.findviewbyid(r.id.ssid);
   mwifilevel = (textview) itemview.findviewbyid(r.id.level);
  }
  void bindscanresult(final scanresult scanresult) {
   //将接入点的名称和强度显示到界面上
   mwifiname.settext(
     getstring(r.string.scan_wifi_name, "" + scanresult.ssid));
   mwifilevel.settext(
     getstring(r.string.scan_wifi_level, "" + scanresult.level));
   //点击item后,就连接对应的接入点
   mview.setonclicklistener(new view.onclicklistener() {
    @override
    public void onclick(view v) {
     //createwificonfig主要用于构建一个wificonfiguration,代码中的例子主要用于连接不需要密码的wifi
     //wifimanager的addnetwork接口,传入wificonfiguration后,得到对应的networkid
     int netid = mwifimanager.addnetwork(createwificonfig(scanresult.ssid, "", wificipher_nopass));
     //wifimanager的enablenetwork接口,就可以连接到netid对应的wifi了
     //其中boolean参数,主要用于指定是否需要断开其它wifi网络
     boolean enable = mwifimanager.enablenetwork(netid, true);
     log.d("zjtest", "enable: " + enable);
     //可选操作,让wifi重新连接最近使用过的接入点
     //如果上文的enablenetwork成功,那么reconnect同样连接netid对应的网络
     //若失败,则连接之前成功过的网络
     boolean reconnect = mwifimanager.reconnect();
     log.d("zjtest", "reconnect: " + reconnect);
    }
   });
  }
 }
 .................

以上就是连接指定wifi的基本套路,从代码中容易看出,关键问题是如何创建出有效的wificonfiguration。
自己测试时,初始创建wificonfiguration失败,手机怎么都没法连接到热点上,后来修改后,基本功能终于能够实现:

 ....................
 private static final int wificipher_nopass = 0;
 private static final int wificipher_wep = 1;
 private static final int wificipher_wpa = 2;
 private wificonfiguration createwificonfig(string ssid, string password, int type) {
  //初始化wificonfiguration
  wificonfiguration config = new wificonfiguration();
  config.allowedauthalgorithms.clear();
  config.allowedgroupciphers.clear();
  config.allowedkeymanagement.clear();
  config.allowedpairwiseciphers.clear();
  config.allowedprotocols.clear();
  //指定对应的ssid
  config.ssid = "\"" + ssid + "\"";
  //如果之前有类似的配置
  wificonfiguration tempconfig = isexist(ssid);
  if(tempconfig != null) {
   //则清除旧有配置
   mwifimanager.removenetwork(tempconfig.networkid);
  }
  //不需要密码的场景
  if(type == wificipher_nopass) {
   config.allowedkeymanagement.set(wificonfiguration.keymgmt.none);
  //以wep加密的场景
  } else if(type == wificipher_wep) {
   config.hiddenssid = true;
   config.wepkeys[0]= "\""+password+"\"";
   config.allowedauthalgorithms.set(wificonfiguration.authalgorithm.open);
   config.allowedauthalgorithms.set(wificonfiguration.authalgorithm.shared);
   config.allowedkeymanagement.set(wificonfiguration.keymgmt.none);
   config.weptxkeyindex = 0;
  //以wpa加密的场景,自己测试时,发现热点以wpa2建立时,同样可以用这种配置连接
  } else if(type == wificipher_wpa) {
   config.presharedkey = "\""+password+"\"";
   config.hiddenssid = true;
   config.allowedauthalgorithms.set(wificonfiguration.authalgorithm.open);
   config.allowedgroupciphers.set(wificonfiguration.groupcipher.tkip);
   config.allowedkeymanagement.set(wificonfiguration.keymgmt.wpa_psk);
   config.allowedpairwiseciphers.set(wificonfiguration.pairwisecipher.tkip);
   config.allowedgroupciphers.set(wificonfiguration.groupcipher.ccmp);
   config.allowedpairwiseciphers.set(wificonfiguration.pairwisecipher.ccmp);
   config.status = wificonfiguration.status.enabled;
  }
  return config;
 }
 .................
 private wificonfiguration isexist(string ssid) {
  list<wificonfiguration> configs = mwifimanager.getconfigurednetworks();
  for (wificonfiguration config : configs) {
   if (config.ssid.equals("\""+ssid+"\"")) {
    return config;
   }
  }
  return null;
 }
 .................

自己写完demo后,以一个手机建立热点,分别测试了有密码和无密码的场景(对应的,需要修改createwificonfig的传入参数)。

发现demo运行的手机在两种场景下,均能够连接到指定热点。

demo地址如下:

https://github.com/zhangjianisastark/demos/tree/master/wifitest

在本文的最后,补充一下终端作为热点时的接口。

public boolean iswifiapenabled()

具有@systemapi、@hide注解的公有接口,判断手机的热点是否开启。

在android 5.1之前,这个接口没有@systemapi注解,

于是有很多代码会利用java发射机制,获取该方法并判断手机热点是否开启。

现在那些老代码已经没法使用了。

现在的做法(以5.1以上为例),应该利用广播接收器监听wifimanager中定义的wifi_ap_state_changed_action。

注意到该action也有@systemapi注解,所以要直接监听对应的字符串,示例如下(上面链接中的demo也有涉及):

...................
 private broadcastreceiver mbroadcastreceiver;
 private void registerbroadcastreceiver() {
  mbroadcastreceiver = new broadcastreceiver() {
   @override
   public void onreceive(context context, intent intent) {
    //收到广播后,利用"wifi_state"的字段,得到ap的状态
    int state = intent.getintextra("wifi_state", 11);
    log.d("zjtest", "ap state: " + state);
   }
  };
  intentfilter intentfilter = new intentfilter();
  //添加action对应的字符信息
  intentfilter.addaction("android.net.wifi.wifi_ap_state_changed");
  this.registerreceiver(mbroadcastreceiver, intentfilter);
 }
 .........
 private void unregisterbroadcastreceiver() {
  this.unregisterreceiver(mbroadcastreceiver);
 }
 ..........

我暂时没有深究wifi模块开启ap的流程。

不过从自己的测试结果来看,wifi开启或关闭ap时,推测发送的应该是sticky类型的广播。

于是,只要apk注册了广播监听器,立马就会得到回复,明白当前ap的状态。

例如,我在开启ap后,再打开自己的测试demo,立马会收到如下信息:

//对应wifi_ap_state_enabled,定义于wifimanager中,@systemapi
02-20 17:48:52.470 12773-12773/? d/zjtest: ap state: 13

手动关闭ap后可以得到如下结果:

//wifi_ap_state_disabling
02-20 17:49:35.803 12773-12773/stark.a.is.zhang.wifitest d/zjtest: ap state: 10
//wifi_ap_state_disabled
02-20 17:49:36.960 12773-12773/stark.a.is.zhang.wifitest d/zjtest: ap state: 11
public boolean setwifiapconfiguration(wificonfiguration wificonfig)
public wificonfiguration getwifiapconfiguration()

@systemapi,设置和获取wifi-ap的配置信息。

可以看出不论手机作为ap还是sta,在framework中均利用wificonfiguration抽象对应的配置信息,包括鉴权算法、密码、ssid、协议等。

这种设计是符合802.11协议精神的,毕竟在物理设备的角度上,ap和sta是完全对等的。只不过在实际情况中,根据各自的需求,特质化了一些组件。

实际上从底层协议来看,仅在传输这个角度上,ap和sta的主要区别仅在于收到数据帧后的处理流程不同。ap收到数据帧后,发现目的地址不是自己,就会进入转发流程;而sta可能就直接丢弃该数据帧了。当然如果从控制的角度来看,即考虑通信信令,ap和sta还是主从的关系。

public boolean setwifiapenabled(wificonfiguration wificonfig, boolean enabled)

@systemapi,改变wifi-ap的开关状态。开启的ap,将使用参数定义的wificonfiguration信息。

可以看出,手机热点对应接口全部变成了systemapi,因此在android的高版本上,应用基本上是无法再操作热点了。

以上所述是小编给大家介绍的android连接指定wifi的方法实例代码,希望对大家有所帮助