随着无线局域网(Wi-Fi)的普及和发展,使用Wi-Fi直连技术(P2P)在没有中间接入点的情况下实现设备间直接互联成为可能。通过Wi-Fi直连,具备相应硬件的Android 4.0及更高版本设备可以实现高速连接通信,比传统蓝牙连接具有更远的传输距离。这项技术对于多人游戏、照片共享等需要设备间数据共享的应用非常有用。
Wi-Fi直连的核心API是WifiP2pManager
类。
private WifiP2pManager.Channel mChannel; // app与framework联系的纽带
private WifiP2pManager mManager;
...
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
只有服务端设备需要执行此步骤,客户端设备可以跳过。即使不创建群组,设备仍然可以进行后续的设备发现和连接操作。一旦连接成功,这两台设备将自动形成一个群组,并在其中选择一台设备作为群主。创建群组后,会在Wi-Fi列表中出现一个名为DIRECT_**_DeviceName
的网络,这个网络是为没有Wi-Fi直连功能的设备准备的,它们可以通过连接此网络来加入群组。但是使用此方式加入的设备不会收到WIFI_P2P_CONNECTION_CHANGED_ACTION
广播,不过在调用mManager.requestGroupInfo
时,可以获取到这些设备的信息。
mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
appendLog("创建群组成功");
// 在这里可以创建ServerSocket并等待客户端接入
...
}
@Override
public void onFailure(int reason) {
appendLog("创建群组失败");
...
}
});
很多地方都会使用到WifiP2pManager.ActionListener
回调,下面列出了一些可能的错误原因:
/**
* Wi-Fi直连操作失败
*/
public static final int ERROR = 0;
/**
* 设备不支持Wi-Fi直连功能
*/
public static final int P2P_UNSUPPORTED = 1;
/**
* 操作失败,框架忙于处理其他请求
*/
public static final int BUSY = 2;
/**
* 操作失败,未添加任何服务请求
*/
public static final int NO_SERVICE_REQUESTS = 3;
mManager.discoverPeers(mChannel, mActionListener);
客户端设备直接执行此操作,不需要创建群组。两台设备必须同时执行设备发现操作,才能相互发现对方的存在。
mManager.stopPeerDiscovery(mChannel, mActionListener);
连接设备的操作可以由服务端或客户端发起。device
是通过设备发现获得的对方设备信息,后续会介绍广播的相关内容。
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
mManager.connect(mChannel, config, mActionListener);
连接后也可以取消连接。注意:取消操作将取消所有正在发起连接邀请的设备,不能针对单个设备进行操作。
mManager.cancelConnect(mChannel, mActionListener);
当需要取消某个单一连接时,只能从客户端取消对服务端的连接。如果需要取消所有连接,只能通过解散群组来实现。
mManager.removeGroup(mChannel, mActionListener);
可以通过以下代码获取群组中的所有设备信息:
mManager.requestGroupInfo(mChannel, new WifiP2pManager.GroupInfoListener() {
@Override
public void onGroupInfoAvailable(WifiP2pGroup group) {
appendLog("已连接的设备:");
Collection<WifiP2pDevice> devices = group.getClientList();
int i=1;
for(WifiP2pDevice d: devices) {
appendLog((i++) +
": ip:" +
d.deviceAddress+
", name:" +
d.deviceName +
", isGroupOwner:" +
d.isGroupOwner() );
}
}
});
使用广播来通知应用层自身设备、发现设备和连接设备的状态变化。
Intent | 说明 |
---|---|
WIFI_P2P_CONNECTION_CHANGED_ACTION |
当设备的 WLAN 连接状态更改时广播。 |
WIFI_P2P_PEERS_CHANGED_ACTION |
当您调用 discoverPeers() 时广播。如果您在应用中处理此 Intent,则通常需要调用 requestPeers() 以获取对等设备的更新列表。 |
WIFI_P2P_STATE_CHANGED_ACTION |
当 WLAN P2P 在设备上启用或停用时广播。 |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION |
当设备的详细信息(例如设备名称)更改时广播。 |
WIFI_P2P_DISCOVERY_CHANGED_ACTION |
当设备开始或停止发现设备时广播 |
下面是一个实现广播接收器的示例:
public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {
public WiFiDirectBroadcastReceiver() {
super();
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
// 检查 Wi-Fi 是否启用并通知相应的活动
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, WifiP2pManager.WIFI_P2P_STATE_DISABLED);
// wifi p2p 能否使用取决于你的 Wi-Fi 是否已打开。
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
// wifi p2p 可用
} else if (state == WifiP2pManager.WIFI_P2P_STATE_DISABLED) {
// wifi p2p 不可用
}
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// 调用 WifiP2pManager.requestPeers() 获取当前对等设备列表
// 获取所有扫描到的设备。
WifiP2pDeviceList mPeers = intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST);
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
// 处理新连接或断开连接的情况
// wifi p2p 连接状态发生变化,在创建组成功时也会触发该广播。
// 获取 Wi-Fi P2P 网络状态
NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
// 关于该群组的连接信息。有三个属性:
// 1. groupFormed:群组是否已形成,作为群主,创建群组后获得的该属性值为 true
// 2. isGroupOwner:本设备是否为群主
// 3. groupOwnerAddress:群主的 IP 地址
WifiP2pInfo p2pInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO);
// 群组的相关信息,如网络名称、密码和所有已连接设备等。
WifiP2pGroup p2pGroup = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP);
if (networkInfo != null && networkInfo.isConnected()) {
// 表明已连接上,创建组成功也会进入该判断
if (p2pInfo.groupFormed && p2pInfo.isGroupOwner) {
// 群组已形成,且本设备为群主
} else if (p2pInfo.groupFormed) {
// 群组已形成,但本设备非群主
// 获取群主 IP
String groupOwnerAddress = p2pInfo.groupOwnerAddress.getHostAddress();
// 建立 Socket 连接
}
}
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
// 处理本设备的 Wi-Fi 状态变化
// 本设备信息发生变化
WifiP2pDevice wifiP2pDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
} else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
// 处理发现设备操作状态变化
// WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED:停止状态
// WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED:开始状态
int discoveryState = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE,
WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
}
}
}
上述代码片段展示了如何使用WifiP2pManager来获取连接信息。通过调用requestConnectionInfo()
方法,并传入一个ConnectionInfoListener
,我们可以在连接信息可用时得到回调。
requestGroupInfo()
方法获取群组信息,其中包括WiFi热点密码。对于客户端设备,可以根据需要执行相应的操作。
除了普通的搜索和连接方式外,还可以使用WifiP2pManager.discoverServices()
方法进行针对性的WiFi P2P连接。这种方式会根据服务提供的字段和协议来判断是否符合连接要求。
Bonjour
使用Bonjour服务时,可以通过创建txt记录来指定相关信息。
示例代码如下:
Map<String, String> txtRecord = new HashMap<String, String>();
txtRecord.put("txtvers", "1");
txtRecord.put("pdl", "application/postscript");
mServiceInfo = WifiP2pDnsSdServiceInfo.newInstance(instanceName,
serviceType, txtRecord);
其中,serviceType
参数指定了应用程序使用的协议和传输层。
UPnP
使用UPnP服务时,可以指定服务类型和相关信息。
示例代码如下:
List<String> services = new ArrayList<String>();
services.add("urn:schemas-upnp-org:service:AVTransport:1");
services.add("urn:schemas-upnp-org:service:ConnectionManager:1");
mServiceInfo = WifiP2pUpnpServiceInfo.newInstance(
"6859dede-8574-59ab-9332-123456789011",
"urn:schemas-upnp-org:device:MediaRenderer:1",
services);
创建完服务信息后,可以使用addLocalService()
方法将服务添加到本地。
示例代码如下:
mManager.addLocalService(mChannel, mServiceInfo, mActionListener);
与之前的示例相同,可以使用相同的方法来创建WiFi P2P群组。
针对Bonjour和UPnP服务,可以分别创建相应的监听器。当搜索到设备服务时,这些监听器会得到回调。
示例代码如下:
// Bonjour服务监听器
WifiP2pManager.DnsSdServiceResponseListener ptrListener = new WifiP2pManager.DnsSdServiceResponseListener() {
@Override
public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice srcDevice) {
}
}
WifiP2pManager.DnsSdTxtRecordListener txtListener = new WifiP2pManager.DnsSdTxtRecordListener() {
@Override
public void onDnsSdTxtRecordAvailable(String fullDomainName, Map<String, String> txtRecordMap, WifiP2pDevice srcDevice) {
}
}
mManager.setDnsSdResponseListeners(mChannel, ptrListener, txtListener);
// UPnP服务监听器
private WifiP2pManager.UpnpServiceResponseListener upnpListener = new WifiP2pManager.UpnpServiceResponseListener() {
@Override
public void onUpnpServiceAvailable(List<String> uniqueServiceNames, WifiP2pDevice srcDevice) {
}
}
mManager.setUpnpServiceResponseListener(mChannel, upnpListener);
这些监听器会在搜索到设备服务时得到回调。根据搜索时传入的服务request,决定回调哪个监听器。
通过创建相应的服务请求,可以发现Bonjour和UPnP服务。
示例代码如下:
// Bonjour类型的TXT服务,对应上面 txtListener
WifiP2pDnsSdServiceRequest txtRequest = WifiP2pDnsSdServiceRequest.newInstance(INSTANCE_NAME, SERVICE_TYPE);
// Bonjour类型的PTR服务,对应上面 ptrListener
WifiP2pDnsSdServiceRequest ptrRequest = WifiP2pDnsSdServiceRequest.newInstance(SERVICE_TYPE);
// UPnP服务,对应上面的upnpListener
WifiP2pUpnpServiceRequest upnpRequest = WifiP2pUpnpServiceRequest.newInstance(searchTarget);
mManager.addServiceRequest(mChannel, txtRequest, mActionListener);
mManager.addServiceRequest(mChannel, ptrRequest, mActionListener);
mManager.addServiceRequest(mChannel, upnpRequest, mActionListener);
其余步骤与之前的示例相同。
以上是针对性WiFi P2P连接的示例代码和步骤。你可以根据自己的需求来选择合适的服务类型和设置相应的监听器来处理搜索到的设备服务。
参考文档:https://developer.android.google.cn/guide/topics/connectivity/wifip2p?hl=zh-cn