Android WiFiDirect 学习(二)——Service Discovery

时间:2023-11-09 22:14:26

Service Discovery 简介

在Android WifiDirect学习(一 )中,简单介绍了如何使用WifiDirect进行搜索——连接——传输。

这样会有一个问题,那就是你会搜索到到附近所有处于WifiDirect搜索状态的网络设备,而这些设备中不一定都是你想进行连接的。

Android WifiDirect Api提供了一个仅搜索特定网络设备的搜索方式,叫做Service Discovery,它是Wi-Fi Direct API在Android 4.1中被增强以支持在WifiP2pManager中的预先关联服务发现。这允许在连接之前使用Wi-Fi Direct通过服务(Service)发现和筛选周围的设备。

常见Service:

Android已经提供了两种Service,一种是 Bonjour Service,一种是Upnp Service,开发者也可以自己设计一套Service,一般情况下,这两种Service已经足够用了。

Service基本使用方法

为了广播你的应用作为一个Wi-Fi上的服务,以便于其他设备能够发现并连接你的应用,需要调用addLocalService()方法并传输一个WifiP2pServiceInfo对象。这个对象描述了你的应用服务。

为了通过Wi-Fi开始发现附近的设备,首先你应当决定使用Bonjour Service还是Upnp Service实现通信。如果使用Bonjour,首先要用setDnsSdResponseListeners()设置好一些回调监听器。这个方法需要WifiP2pManager.DnsSdServiceResponseListenerDnsSdTxtRecordListener两个作参数。如果使用Upnp,则要调用setUpnpServiceResponseListener()。这个方法需要UpnpServiceResponseListener作为参数。

在你开始发现本地设备上的服务之前,你也需要调用addServiceRequest()方法。当你传递给这个方法的接口ActionListener收到一个成功的回调,接着你可以通过调用discoverServices()开始探索在本地设备上的服务。

当本地服务被发现,你会收到一个回调,来自WifiP2pManager.DnsSdServiceResponseListener或者WifiP2pManager.UpnpServiceResponseListener,这个取决于你注册使用的是Bonjour还是Upnp。收到的回调都会包含一个WifiP2pDevice对象,代表了对应的设备。

Service使用案例(以Bonjour Service为例)

设置Manifest

为了使用Wi-Fi P2P,要在你的androidmanifest中添加CHANGE_WIFI_STATEACCESS_WIFI_STATE、和INTERNET权限。即使Wi-FiP2P不要求互联网连接,但它要使用标准的Java socket,并且Android在使用这些套接字时要求申请这些权限,因此要在清单中申请INTERNET权限。

 <manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.nsdchat"
...
<uses-permission
android:required="true"
android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission
android:required="true"
android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission
android:required="true"
android:name="android.permission.INTERNET"/>
...

添加Local Service

如果你要提供一个Local Service,那么就需要把这个服务注册为可发现的。本地服务被注册后,框架就会自动的响应来自对等点的服务发现请求。

以下是创建本地服务的步骤:

1. 创建一个WifiP2pServiceInfo对象;

2. 填入你的服务相关的信息;

3. 调用addLocalService()方法来注册本地服务,让其可发现。

 private void startRegistration(){
// Create a string map containing information about your service.
Map record = new HashMap();
record.put("listenport", String.valueOf(SERVER_PORT));
record.put("buddyname", "John Doe" + (int) (Math.random() * 1000));
record.put("available", "visible");
// Serviceinformation. Pass it an instance name, service type
//_protocol._transportlayer , and the map containing
//information other devices will want once they connect to this one.
WifiP2pDnsSdServiceInfo serviceInfo =
WifiP2pDnsSdServiceInfo.newInstance("_test", "_presence._tcp", record);
// Add thelocal service, sending the service info, network channel,
// andlistener that will be used to indicate success or failure of
// therequest.
mManager.addLocalService(channel, serviceInfo, new ActionListener() {
@Override
public void onSuccess() {
// Command successful! Code isn't necessarily needed here,
// Unless you want to update the UI or add logging statements.
}
@Override
public void onFailure(int arg0) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
}
});
}

发现附近的服务

Android使用回调方法来通知你的应用程序可用的服务,因此首先要做的就是要建立回调方法。创建一个WifiP2pManager.DnsSdTxtRecordListener对象来监听传入的记录。这个记录可以是其他设备的任意广播。当一个记录进入时,你可以把设备地址和其他你想要的其他相关信息复制到当前方法外部的数据结构中,以便后续可以访问它。下面的例子假设记录中包含一个“buddyname”字段,它带有用户的标识。

 finalHashMap<String,String> buddies =newHashMap<String,String>();
...
private void discoverService() {
DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() {
@Override
/* Callbackincludes:
* fullDomain: full domainname: e.g "printer._ipp._tcp.local."
* record: TXT record dta as amap of key/value pairs.
* device: The device runningthe advertised service.
*/
public voidonDnsSdTxtRecordAvailable(
String fullDomain, Map record, WifiP2pDevice device) {
Log.d(TAG, "DnsSdTxtRecord available -" + record.toString());
buddies.put(device.deviceAddress, record.get("buddyname"));
}
};
...
}

实现一个WifiP2pManager.DnsSdServiceResponseListener接口,来获取服务信息。这个接口会接收实际的描述和连接信息。上面的代码中使用了Map对象把设备地址和用户标识组成一对。服务响应监听器使用这个接口把DNS记录和对应的服务信息连接到一起。实现上述两个监听器后,使用setDnsSdResponseListener()方法把它们添加给WifiP2pManager对象。

 private void discoverService(){
...
DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() {
@Override
public voidonDnsSdServiceAvailable(String instanceName, String registrationType,
WifiP2pDevice resourceType) {
// Update the device name with the human-friendly version from
// the DnsTxtRecord, assuming one arrived.
resourceType.deviceName = buddies
.containsKey(resourceType.deviceAddress) ? buddies
.get(resourceType.deviceAddress) : resourceType.deviceName;
// Add to the custom adapter defined specifically for showing
// wifi devices.
WiFiDirectServicesList fragment = (WiFiDirectServicesList)getFragmentManager()
.findFragmentById(R.id.frag_peerlist);
WiFiDevicesAdapter adapter = ((WiFiDevicesAdapter)fragment
.getListAdapter());
adapter.add(resourceType);
adapter.notifyDataSetChanged();
Log.d(TAG, "onBonjourServiceAvailable " + instanceName);
}
};
mManager.setDnsSdResponseListeners(channel, servListener, txtListener);
...
}

现在创建一个服务请求并调用addServiceRequest()方法,这个方法也需要一个监听器来包括成功或失败。

 serviceRequest =WifiP2pDnsSdServiceRequest.newInstance();
mManager.addServiceRequest(channel,
serviceRequest,
new ActionListener() {
@Override
public void onSuccess() {
// Success!
}
@Override
public void onFailure(int code) {
// Command failed. Check forP2P_UNSUPPORTED, ERROR, or BUSY
}
});

最后,调用的discoverServices()方法。

 mManager.discoverServices(channel,newActionListener(){
@Override
public void onSuccess() {
// Success!
}
@Override
public void onFailure(int code) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
if (code == WifiP2pManager.P2P_UNSUPPORTED) {
Log.d(TAG, "P2P isn'tsupported on this device.");
else if(...)
...
}
});

如果一切顺利,恭喜你大功告成。如果遇到问题,前面异步调用的参数WifiP2pManager.ActionListener参数,它提供了指示成功或失败的回调方法。把调试断点设置在onFailure()方法中来诊断问题。这个方法提供了错误代码,以下是可能发生的错误:

P2P_UNSUPPORTED

运行app的设备上不支持Wi-Fi P2P

BUSY

系统忙于处理请求

ERROR

由于内部错误导致操作失败

参考文章

Android 4.1 API中文详解-Android 4.1 APIs  http://wiki.eoeandroid.com/Android_4.1_APIs

http://developer.android.com/