万事须己运,他得非我贤。这篇文章主要讲述Android连接指定Wifi的方法相关的知识,希望能为你提供帮助。
本篇博客主要记录一下android中打开Wifi、获取Wifi接入点信息及连接指接入点的方法。
自己写的demo主要用于测试接口的基本功能,
因此界面及底层逻辑比较粗糙。
demo的整体界面如下所示:
文章图片
上图中的OPEN按键负责开启Wifi;
GET按键负责获取扫描到的接入点信息。
当获取到接入点信息后, 我选取了其中的名称及信号强度, 以列表的形式显示在主界面下方, 如下图:
文章图片
当点击列表中的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"
/>
同时, 在高版本中还需要主动获取运行时权限。
【Android连接指定Wifi的方法】权限的要求, 是由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的高版本上, 应用基本上是无法再操作热点了。
推荐阅读
- RxAndroid 教程
- 记录(AndroidContentProvider的使用)
- Android实际开发中的首页框架搭建(项目结构搭建)
- AndroidStudio 入门——002控件篇
- 安卓3月21日作业
- Android 夜间模式的实现
- Android中广播的简单使用
- Android做下拉刷新的时候,在做些什么
- android OTA升级包制作