1.> 转载请标明出处
本文出自[HCY的微博]
一、概述
相信不少人有接触过《闪传》、《快牙》这两款传输分享工具,它们无需WIFI,3G和SIM卡,不费任何流量,极速传输内容,每秒大约2M的速率。方便了多台手机之间的数据传输,直接秒蓝牙好几条街。今天我就来剖析一下他们的核心技术实现。
二、实现思路
接收方:
- 建立WIFI热点,如SSID为QuickShareMI3。
- 通过ServerSocket在指定端口建立Socket服务端。
- 通过UDP广播,将第2步中建立的服务端的ip地址和端口广播出去。
发送方:
- 开启WIFI扫描功能,当扫描到SSID为QuickShareMI3的热点时,连接至该热点。
- 监听UDP广播,获取接收方广播的ip地址和端口号。
- 获取到ip地址和端口号后,连接到服务端,至此,传输链路建立成功,可以开始收发文件了。
三、WIFI模块的功能实现
创建WIFI热点
创建WIFI热点时需要配置热点信息,如下代码实现了无密码、WPA_PSK、WPA2_PSK三种类型热点的信息配置:
/**
* 创建wifi热点配置信息,详见系统Setting App的WifiApDialog.java源码,当wifiApType为
* {@link WifiApType#TYPE_NONE}时,ssid和psw不起作用。另外在SDK_INT<14的系统版本下,使用
* {@link WifiApType#TYPE_WPA2_PSK}无效
*
* @param wifiApType
* 热点类型,{eg:{@link WifiApType#TYPE_NONE}
* @param ssid
*
* @param password
* 热点密码 ,密码可以为8-63个ASCII码或8-64个十六进制的字符
* @return
*/
public WifiConfiguration createWifiApConfiguration(int wifiApType,
String ssid, String password) {
if (TextUtils.isEmpty(ssid)) {
throw new RuntimeException("ssid should not be null");
}
WifiConfiguration config = new WifiConfiguration();
config.SSID = ssid;
switch (wifiApType) {
case WifiApType.TYPE_NONE:
config.allowedKeyManagement.set(KeyMgmt.NONE);
break;
case WifiApType.TYPE_WPA_PSK:
case WifiApType.TYPE_WPA2_PSK:
if (TextUtils.isEmpty(password) || password.length() < 8) {
throw new RuntimeException("密码至少为8个字符");
}
config.preSharedKey = password;
final int WPA2_PSK = 4;
config.allowedKeyManagement
.set(wifiApType == WifiApType.TYPE_WPA_PSK ? KeyMgmt.WPA_PSK
: WPA2_PSK);
config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
break;
}
return config;
}
配置完上述热点信息之后,建立热点
/**
* 打开wifi热点
*
* @param wifiConfig
* @return
*/
public boolean openWifiAp(WifiConfiguration wifiConfig) {
// 热点与wifi不能共存,开启热点需要关闭wifi
wifiManager.setWifiEnabled(false);
return setWifiApEnabled(wifiConfig, true);
}
/**
* 设置wifi热点的激活状态,反射实现
*
* @param wifiConfig
* 配置信息
* @param enabled
* 是否激活
*/
public boolean setWifiApEnabled(WifiConfiguration wifiConfig,
boolean enabled) {
try {
Method setWifiApEnabled = WifiManager.class.getMethod(
"setWifiApEnabled", WifiConfiguration.class, Boolean.TYPE);
setWifiApEnabled.setAccessible(true);
return (Boolean) setWifiApEnabled.invoke(wifiManager, wifiConfig,
enabled);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
扫描WIFI热点
开始扫描热点
wifiManager.startScan()
扫描结果会在BroadcastReceiver且意图action为WifiManager.SCAN_RESULTS_AVAILABLE_ACTION时被接收
- 连接到指定的WIFI热点
遍历扫描到的热点列表,当发现满足要求的热点时,创建连接到该热点的配置信息(密码、加密类型要正确)
/**
* 创建链接网络的wifi配置信息,详见详见系统Setting
* App的(低版本看WifiDialog.java,高版本看WifiConfigController.java)
*
* @param ssid
* 热点名称
* @param password
* 密码可以为8-63个ASCII码或8-64个十六进制的字符
* @return
*/
public WifiConfiguration createWifiConnectConfiguration(int wifiApType,
String ssid, String password) {
if (TextUtils.isEmpty(ssid)) {
throw new RuntimeException("ssid should not be null");
}
WifiConfiguration config = new WifiConfiguration();
config.SSID = WifiUtils.convertToQuotedString(ssid);
switch (wifiApType) {
case WifiApType.TYPE_NONE:
config.allowedKeyManagement.set(KeyMgmt.NONE);
break;
case WifiApType.TYPE_WPA_PSK:
case WifiApType.TYPE_WPA2_PSK:
if (TextUtils.isEmpty(password) || password.length() < 8) {
throw new RuntimeException("密码至少为8个字符");
}
config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
if (password.matches("[0-9A-Fa-f]{64}")) {
config.preSharedKey = password;
} else {
config.preSharedKey = WifiUtils.convertToQuotedString(password);
}
break;
}
return config;
}
连接到指定热点
/**
* 连接到指定热点
*
* @param accessPoint
* 热点信息
* @param password
* 密码,热点安全类型为WPA_PSK或者WPA2_PSK,密码必填
* @return
*/
public boolean connectToAccessPoint(AccessPoint accessPoint, String password) {
if (accessPoint == null) {
return false;
}
int apType = WifiApManager.WifiApType.TYPE_NONE;
switch (accessPoint.getSecurity()) {
case AccessPoint.SECURITY_NONE:
break;
case AccessPoint.SECURITY_WPA_PSK:
apType = WifiApManager.WifiApType.TYPE_WPA_PSK;
if (TextUtils.isEmpty(password)) {
throw new IllegalArgumentException(
"when accessPoint security is WPA_PSK,the password should not be null");
}
break;
case AccessPoint.SECURITY_WPA2_PSK:
apType = WifiApManager.WifiApType.TYPE_WPA2_PSK;
if (TextUtils.isEmpty(password)) {
throw new IllegalArgumentException(
"when accessPoint security is WPA2_PSK,the password should not be null");
}
break;
}
// 如果已经保存的wifi,里面有networkId,可以直接连接
int networkId = accessPoint.getNetworkId();
if (networkId == -1) {
WifiConfiguration config = wifiApManager
.createWifiConnectConfiguration(apType,
accessPoint.getSsid(), password);
networkId = wifiApManager.wifiManager.addNetwork(config);
}
return wifiManager.enableNetwork(networkId, true);
}
四、总结
通过第三部分的实现,WIFI通信模块的功能差不多已经实现完毕,上面所用到代码都在WifiLibrary[https://github.com/Money888/WifiLibrary]中,欢迎大家下载阅读。
在后面的两篇实现文章中,会介绍UDP和TCP的通信实现,敬请期待。