本文译自:http://developer.android.com/training/connect-devices-wirelessly/nsd.html

给应用程序添加网络服务发现(NSD)功能,以便用户能够识别局域网内支持该应用所请求的服务的其他设备。主要用于诸如文件共享或多人游戏等各种对等应用程序。Android的NSD API简化了实现这些功能所需要的工作。

本文介绍如何构建一个能够把自己的名称和连接信息广播给本地网络,并扫描来自其他应用程序所做的同样的信息。最后介绍如何连接到运行在其他设备上的同样的应用程序。

在网络上注册你的服务

注意:这一步骤是可选的。如果你不想把你的应用程序广播到本地网络上,你可以跳过本段内容,直接进入下一节:发现网络上的服务。
要把你的服务注册到本地网络上,首先要创建一个NsdServiceInfo对象。这个对象提供了网络上其他设备的信息,以便判断它们是否连接到了你的服务。

publicvoidregisterService(int port){
    // Create theNsdServiceInfo object, and populate it.
     NsdServiceInfo serviceInfo  = new NsdServiceInfo();

     // The name is subject tochange based on conflicts
     // with other servicesadvertised on the same network.
     serviceInfo.setServiceName("NsdChat");
     serviceInfo.setServiceType("_http._tcp.");
     serviceInfo.setPort(port);
     ....
 }

这段代码吧服务的名称设置为“NsdChat”。这个名称对于任意使用NSD来搜寻本地服务的网络设备都是可见的。要注意,这个名称在网络上必须是唯一的,并且Android会自动的处理名称冲突。如果网络上的两个设备都装有NsdChat应用程序,那么其中一个服务的名称会自动的改变成“NsdChat(1)”。

第二个参数设置了服务的类型,它指定了应用程序所使用的协议和传输层。语法是“_<protocol>._<transportlayer>”。在这段代码中,服务使用的是运行在TCP之上的HTTP协议。对于提供了打印机服务(例如,网络打印机)的应用程序,应该把服务类型设置为“_ipp._tcp”。

注意:国际号码分配组织(IANA)集中管理着诸如NSD和Bonjour所使用的、权威的发现协议的服务类型列表。你可以从IANA上下载服务名称和端口号列表。如果你打算使用你的服务类型,你应该通过填写IANA的端口和服务注册表单来保留它。

在给服务设置端口时,为了防止跟其他应用程序发生冲突,要避免硬编码。例如,假设你的应用程序总是使用端口1337,就有可能跟其他的已经安装的使用相同端口的应用程序发生冲突。这时,就要使用设备的下一个有效的端口。因为这个信息是由其他的应用程序通过服务广播的方式来提供的,你的应用程序在编译时,不需要知道由其他应用程序所提供的端口号。相反,应用程序在正确的连接到你的服务之前,可以从你的服务广播中获取这个信息。

如果你在使用套接字来工作,那么通过简单的把套接字设置为0的方法,把套接字设置为对任意端口都有效,设置方法如下:

publicvoidinitializeServerSocket(){
    // Initialize a serversocket on the next available port.
     mServerSocket = new ServerSocket(0);

     // Store the chosen port.
     mLocalPort =  mServerSocket.getLocalPort();
     ...
 }

现在,就可以定义NsdServiceInfo对象了,你需要实现RegistrationListener接口。这个接口包含了由Android系统所使用的回调方法,这些方法会通知你的应用程序,服务注册和解除注册是成功还是失败。

publicvoidinitializeRegistrationListener(){
    mRegistrationListener = new NsdManager.RegistrationListener() {

         @Override
         public voidonServiceRegistered(NsdServiceInfo NsdServiceInfo) {
             // Save the service name.  Android may have changed it in orderto
             // resolve a conflict, so update the name you initially requested
             // with the name Android actually used.
             mServiceName = NsdServiceInfo.getServiceName();
         }

         @Override
         public voidonRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
             // Registration failed!  Put debugging code here to determinewhy.
         }

         @Override
         public voidonServiceUnregistered(NsdServiceInfo arg0) {
             // Service has been unregistered.  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 determinewhy.
         }
     };
 }

以上就是注册服务的所有代码。然后调用下面的registerService()方法。

注意,这个方法是异步的,因此任何需要再服务被注册完成之后执行的代码,都要放到onServiceRegistered()方法中。

publicvoidregisterService(int port){
    NsdServiceInfo serviceInfo  = new NsdServiceInfo();
     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接口的匿名类。以下是一个简单的示例:

publicvoidinitializeDiscoveryListener(){

    // Instantiate a newDiscoveryListener
     mDiscoveryListener = new NsdManager.DiscoveryListener() {

         // Called as soon as service discovery begins.
         @Override
         public void onDiscoveryStarted(String regType) {
             Log.d(TAG, "Service discovery started");
         }

         @Override
         public void onServiceFound(NsdServiceInfo service) {
             // A service was found!  Do something with it.
             Log.d(TAG, "Service discovery success" + service);
             if (!service.getServiceType().equals(SERVICE_TYPE)) {
                 // Service type is the string containing the protocol and
                 // transport layer for this service.
                 Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
             } else if (service.getServiceName().equals(mServiceName)) {
                 // The name of the service tells 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 void onServiceLost(NsdServiceInfo service) {
             // When the network service is no longer available.
             // Internal bookkeeping code goes here.
             Log.e(TAG, "service lost" + service);
         }

         @Override
         public voidonDiscoveryStopped(String serviceType) {
             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”就足够了。

监听器建立完之后,就要调用discoverServices()方法,把你的应用程序所要查找的服务类型、使用的发现协议、以及刚刚创建的监听器传递给NSD管理器。

mNsdManager.discoverServices(
        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,mDiscoveryListener);

连接到网络上的服务

当应用程序找到一个要连接的网络上的服务时,首先要使用resolveService()方法来判断服务的连接信息。把NsdManager.ResolveListener的接口实现传递到这个方法中,并使用该接口方法来获得NsdServiceInfo对象中所包含的连接信息。

publicvoidinitializeResolveListener(){
    mResolveListener = new NsdManager.ResolveListener() {

         @Override
         public void onResolveFailed(NsdServiceInfoserviceInfo, int errorCode) {
             // Called when the resolve fails.  Use the error code to debug.
             Log.e(TAG, "Resolve failed" + errorCode);
         }

         @Override
         public void onServiceResolved(NsdServiceInfoserviceInfo) {
             Log.e(TAG, "Resolve Succeeded. " + serviceInfo);

             if (serviceInfo.getServiceName().equals(mServiceName)) {
                 Log.d(TAG, "Same IP.");
                 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 tearDownmethod
         public void tearDown() {
         mNsdManager.unregisterService(mRegistrationListener);
         mNsdManager.stopServiceDiscovery(mDiscoveryListener);
     }