如何使用Network Service Discovery和WIFI Direct找到并连接本地设备,来创建peer-to-peer连接。
同与云进行通信一样,Android无线APIs也能启用同同一本地网的其它的设备的通信,甚至不在网络但物理上接近的设备。NSD(Network Service Discovery)通过允许应用查找附近运行能与之通信的服务的设备来提供这个功能。将这个功能集成到你的应用中能帮你提供更加丰富的功能,例如,在同一间屋子中打游戏,从已启用NSD的webcam上拉取图片,或远程登录同一网络的其它的机器。
添加NSD到你的应用会允许用户标识本地网络的其它设备,这个本地网络应支持你应用请求的服务。这对一些peer-to-peer应用例如文件共享或多人游戏是相当有用的。Android的NSD API为你实现这个功能而简化了必要的步骤。
这节课教会你如何构建一个应用,这个应用能广播它的名字和连接信息到本地网上,并且也能扫描其它应用提供的相同的信息。最后,这节课会为你展示如何链接到运行在其它设备的同一应用上。
注意:这步是可选的。如果你不想广播你应用的服务到本地网上,你可以向前跳到下一节,在网络上搜索服务。
为了在本地网上注册你的服务,首先要创建NsdServiceInfo对象。当网络上其它设备决定连接到你的服务时,这个对象提供了它们使用的信息。
public void registerService(intport) {
// Create the NsdServiceInfo object, and populate it.
NsdServiceInfo serviceInfo = newNsdServiceInfo();
// The name is subject to change based on conflicts
// with other services advertised on the same network.
serviceInfo.setServiceName("NsdChat");
serviceInfo.setServiceType("_http._tcp.");
serviceInfo.setPort(port);
....
}
这个代码段设置服务的名称为“NsdChat”。这个名称对网络上任何使用Nsd搜索本地服务设备都是可见的。对于网络上的任何服务来说,这个名称必须是唯一的,并且Android会自动处理这个冲突。如果网络上的两个设备都有NsdChat应用被安装,它们当中的一个服务名称会自动被修改,例如“NsdChat(1)”。
第二个参数设置服务类型,指定了应用使用协议和传输层。语法是“_<protocol>._<transportlayer>”。在这个代码段中,这个服务使用HTTP协议运行在TCP上。一个提供打印的服务(例如,网络打印机)应将网络类型设置为“_ipp._tcp”。
注意:互联网编号分配机构(IANA)管理统一的、权威的由服务搜索协议如NSD和Bonjour使用的服务类型清单。你能从IANAlist of service names and port numbers下载这些清单。如果你想使用新的服务类型,你应当通过填写IANA Ports and Services registrationform来保存它。
当为你的服务设置端口号时,避免对它硬编码以便同其它应用产生冲突。例如,猜想你的应用总是使用端口1337,那么它就有可能同其它使用同一端口的其它应用产生冲突。反之,使用设备的下一个可用端口。由于这个信息由服务广播提供给其它应用,所以你应用使用的这个端口没有必要让其它应用在编译时知道。在连接到你的应用之前,这个应用能从你应用的服务广播中获取这个信息。
如果你正用sockets工作,下方是如何你能通过设置它为0来简单地为获取一个可用端口而初始化socket。
public void initializeServerSocket(){
// Initialize a server socket on the next available port.
mServerSocket = new ServerSocket(0);
// Store the chosen port.
mLocalPort = mServerSocket.getLocalPort();
...
}
在你定义了NsdServiceInfo对象后,你需要实现RegistrationListener接口。这个接口包含了Android针对服务的注册和取消注册的成功或失败进行提醒的回调方法。
public voidinitializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
@Override
public void onServiceRegistered(NsdServiceInfoNsdServiceInfo) {
// Save the service name. Android may have changed it in order to
// resolve a conflict, so updatethe name you initially requested
// with the name Android actuallyused.
mServiceName =NsdServiceInfo.getServiceName();
}
@Override
public voidonRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Registration failed! Put debugging code here to determine why.
}
@Override
public voidonServiceUnregistered(NsdServiceInfo arg0) {
// Service has beenunregistered. This only happens when youcall
// NsdManager.unregisterService()and pass in this listener.
}
@Override
public voidonUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Unregistration failed. Put debugging code here to determine why.
}
};
}
现在你有了注册你服务的所有部件。调用这个方法registerService()。
注意这个方法是异步的,因此需要在这个服务已经注册后需要运行的任何代码都必须放在onServiceRegistered()方法中。
public void registerService(intport) {
NsdServiceInfo serviceInfo = newNsdServiceInfo();
serviceInfo.setServiceName("NsdChat");
serviceInfo.setServiceType("_http._tcp.");
serviceInfo.setPort(port);
mNsdManager = Context.getSystemService(Context.NSD_SERVICE);
mNsdManager.registerService(
serviceInfo,NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}
网络是丰富多彩的,从野蛮的网络打印机到温驯的网络摄像头,再到附近热火朝天的一字棋游戏。让你的应用找到这些丰富多彩的功能的关键是服务搜索。你的应用需要在网络上监听服务广播来找到那些服务是可用的,同时过滤掉任何不能用的应用。
服务搜索,如服务注册,有两个步骤:使用相关的回调安装搜索监听器,同时构造简单的异步API调用到discoverServices()方法。
首先,构造实现NsdManager.DiscoveryListener的匿名类。下面的代码段显示了简单的例子:
public voidinitializeDiscoveryListener() {
// Instantiate a new DiscoveryListener
mDiscoveryListener = new NsdManager.DiscoveryListener() {
// Called as soon as service discovery begins.
@Override
public void onDiscoveryStarted(StringregType) {
Log.d(TAG, "Service discoverystarted");
}
@Override
public void onServiceFound(NsdServiceInfoservice) {
// A service was found! Do something with it.
Log.d(TAG, "Service discoverysuccess" + service);
if(!service.getServiceType().equals(SERVICE_TYPE)) {
// Service type is the stringcontaining the protocol and
// transport layer for thisservice.
Log.d(TAG, "UnknownService Type: " + service.getServiceType());
} else if(service.getServiceName().equals(mServiceName)) {
// The name of the servicetells the user what they'd be
// connecting to. It could be"Bob's Chat App".
Log.d(TAG, "Same machine:" + mServiceName);
} else if(service.getServiceName().contains("NsdChat")){
mNsdManager.resolveService(service,mResolveListener);
}
}
@Override
public voidonServiceLost(NsdServiceInfo service) {
// When the network service is nolonger available.
// Internal bookkeeping code goeshere.
Log.e(TAG, "service lost"+ service);
}
@Override
public void onDiscoveryStopped(StringserviceType) {
Log.i(TAG, "Discovery stopped:" + serviceType);
}
@Override
public voidonStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed:Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
@Override
public voidonStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed:Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
}
当搜索启动时,失败时,以及当服务找到或丢失时(丢失意味着不再可用),NSD API使用这个接口中的方法通知你的应用。注意当服务找到时,这个代码段未作检测。
1. 这个找到的服务的服务名称将同本地服务的服务名称进行比较来决定是否这个设备正好获得它自己的广播(如果可用的话)。
2. 检查服务类型,校验你应用能连接到的服务的类型。
3. 检查服务名用来校验连接到正确的应用。
服务名检测并非总是必须的,它只被用在你想连接指定应用的情况下。例如,某个应用只想连接到运行在其它设备上的它自己的实例。然而,如果这个应用需要连接到网络打印机,就足以看出服务类型是“_ipp._tcp”。
在安装了监听器后,调用discoverService(),为它传递你应用能找到的服务类型,使用的搜索协议,以及你刚刚创建的监听器。
mNsdManager.discoverServices(SERVICE_TYPE,NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
当你的应用在网络上找到能连接到的服务时,首先它必须使用resolveService()方法为这个服务确定连接信息。实现NsdManager.ResolveListener并传递到这个方法,用它来获取包含这个连接信息的NsdServiceInfo。
public voidinitializeResolveListener() {
mResolveListener = new NsdManager.ResolveListener() {
@Override
public voidonResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Called when the resolvefails. Use the error code to debug.
Log.e(TAG, "Resolvefailed" + errorCode);
}
@Override
public voidonServiceResolved(NsdServiceInfo serviceInfo) {
Log.e(TAG, "Resolve Succeeded." + serviceInfo);
if(serviceInfo.getServiceName().equals(mServiceName)) {
Log.d(TAG, "SameIP.");
return;
}
mService = serviceInfo;
int port = mService.getPort();
InetAddress host =mService.getHost();
}
};
}
一旦这个服务被确定,你的应用会收到包含IP地址和端口号的服务的详细信息。这就是你需要创建你自己的网络连接到这个服务上的一切。
在应用的生命周期中酌情启用和禁用NSD功能是相当重要的。当应用关闭时取消注册会帮助阻止其它认为它还可用的其它应用对它进行访问。也就是说,服务搜索是耗资源的操作,我们应当在它所在的Activity暂停时停止它,同时当这个Activity再到前台时重新启用它。在你的主Activity中视情况而定重写生命周期方法同时插入启动和停止服务广播和搜索的代码。
//In your application's Activity
@Override
protected void onPause() {
if (mNsdHelper != null) {
mNsdHelper.tearDown();
}
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (mNsdHelper != null) {
mNsdHelper.registerService(mConnection.getLocalPort());
mNsdHelper.discoverServices();
}
}
@Override
protected void onDestroy() {
mNsdHelper.tearDown();
mConnection.tearDown();
super.onDestroy();
}
// NsdHelper's tearDown method
public void tearDown() {
mNsdManager.unregisterService(mRegistrationListener);
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}