使用网络服务发现

网络服务发现 (NSD) 可让您的应用访问 设备通过本地网络提供。支持 NSD 的设备包括打印机 摄像头、HTTPS 服务器和其他移动设备。

NSD 实现了基于 DNS 的服务发现 (DNS-SD) 机制, 允许您的应用通过指定服务类型和名称来请求服务 提供所需服务类型的设备实例的角色。DNS-SD 现为 同时支持 Android 和其他移动平台。

向应用添加 NSD 后,您的用户可以识别设备上的其他设备 支持您的应用请求的服务。这对于 各种点对点应用,例如文件共享或多人游戏 游戏。Android 的 NSD API 简化了实现 API 所需的工作量 此类功能

本课将介绍如何构建可以广播 网络名称和连接信息,并扫描查找信息, 其他同类应用的影响最后,本课将介绍 即可连接到另一台设备上运行的同一应用

在网络上注册您的服务

注意:此步骤是可选的。如果 您不需要在意通过本地网络广播应用的服务 则可以跳至 下一部分:发现网络上的服务

如需在本地网络上注册服务,请先创建一个 NsdServiceInfo 对象。此对象可提供 网络中的其他设备在决定是否连接到您的 服务。

fun registerService(port: Int) {
   
// Create the NsdServiceInfo object, and populate it.
   
val serviceInfo = NsdServiceInfo().apply {
       
// The name is subject to change based on conflicts
       
// with other services advertised on the same network.
        serviceName
= "NsdChat"
        serviceType
= "_nsdchat._tcp"
        setPort
(port)
       
...
   
}
}
public void registerService(int port) {
   
// Create the NsdServiceInfo object, and populate it.
   
NsdServiceInfo serviceInfo = new NsdServiceInfo();

   
// The name is subject to change based on conflicts
   
// with other services advertised on the same network.
    serviceInfo
.setServiceName("NsdChat");
    serviceInfo
.setServiceType("_nsdchat._tcp");
    serviceInfo
.setPort(port);
   
...
}

此代码段将服务名称设置为“NsdChat”。服务名称 是实例名称:它是对网络上的其他设备可见的名称。 网络上使用 NSD 查找 本地服务请记住,该名称必须是唯一的 而 Android 会自动处理冲突解决。如果 网络中的两台设备都安装了 NsdChat 应用,其中一台 他们会自动将服务名称更改为“NsdChat”之类的 (1)”。

第二个参数用于设置服务类型,用于指定具体的协议和传输方式。 应用使用的层。语法如下: “_<protocol>._<transportlayer>”。在 则该服务使用通过 TCP 运行的 HTTP 协议。应用 提供打印机服务(例如网络打印机)时,会将 服务类型更改为“_ipp._tcp”。

注意 :国际分配号码 授权中心 (IANA) 管理着 服务发现协议(如 NSD 和 Bonjour)使用的服务类型的权威列表。 您可以从 IANA 服务名称和端口号列表。 如果您打算使用新的服务类型,则应填写 IANA 端口和服务 注册表单

为服务设置端口时,请避免按如下方式对其进行硬编码 与其他应用冲突问题例如,假设 您的应用始终使用端口 1337 可能会导致它与 使用同一端口的其他已安装应用。而应使用设备的 下一个可用端口。因为此类信息由 因此无需将应用使用的端口 在编译时为其他应用所知。相反,应用可以获取 此信息,就在连接您的 服务。

如果您使用套接字,可以按照以下方法将套接字初始化为任意 只需将其设为 0 即可。

fun initializeServerSocket() {
   
// Initialize a server socket on the next available port.
    serverSocket
= ServerSocket(0).also { socket ->
       
// Store the chosen port.
        mLocalPort
= socket.localPort
       
...
   
}
}
public void initializeServerSocket() {
   
// Initialize a server socket on the next available port.
    serverSocket
= new ServerSocket(0);

   
// Store the chosen port.
    localPort
= serverSocket.getLocalPort();
   
...
}

您已定义 NsdServiceInfo 对象,现在需要实现 RegistrationListener 接口。这个 接口包含 Android 用于提醒您的应用 服务注册和取消注册是否成功。

private val registrationListener = object : NsdManager.RegistrationListener {

   
override fun onServiceRegistered(NsdServiceInfo: NsdServiceInfo) {
       
// Save the service name. Android may have changed it in order to
       
// resolve a conflict, so update the name you initially requested
       
// with the name Android actually used.
        mServiceName
= NsdServiceInfo.serviceName
   
}

   
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
       
// Registration failed! Put debugging code here to determine why.
   
}

   
override fun onServiceUnregistered(arg0: NsdServiceInfo) {
       
// Service has been unregistered. This only happens when you call
       
// NsdManager.unregisterService() and pass in this listener.
   
}

   
override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
       
// Unregistration failed. Put debugging code here to determine why.
   
}
}
public void initializeRegistrationListener() {
    registrationListener
= new NsdManager.RegistrationListener() {

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

       
@Override
       
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
           
// Registration failed! Put debugging code here to determine why.
       
}

       
@Override
       
public void onServiceUnregistered(NsdServiceInfo arg0) {
           
// Service has been unregistered. This only happens when you call
           
// NsdManager.unregisterService() and pass in this listener.
       
}

       
@Override
       
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
           
// Unregistration failed. Put debugging code here to determine why.
       
}
   
};
}

现在,您已具备注册服务的所有条件。调用 方法 registerService()

请注意,此方法是异步的,因此任何需要运行的代码 必须在 onServiceRegistered() 方法中使用。

fun registerService(port: Int) {
   
// Create the NsdServiceInfo object, and populate it.
   
val serviceInfo = NsdServiceInfo().apply {
       
// The name is subject to change based on conflicts
       
// with other services advertised on the same network.
        serviceName
= "NsdChat"
        serviceType
= "_nsdchat._tcp"
        setPort
(port)
   
}

    nsdManager
= (getSystemService(Context.NSD_SERVICE) as NsdManager).apply {
        registerService
(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
   
}
}
public void registerService(int port) {
   
NsdServiceInfo serviceInfo = new NsdServiceInfo();
    serviceInfo
.setServiceName("NsdChat");
    serviceInfo
.setServiceType("_http._tcp.");
    serviceInfo
.setPort(port);

    nsdManager
= Context.getSystemService(Context.NSD_SERVICE);

    nsdManager
.registerService(
            serviceInfo
, NsdManager.PROTOCOL_DNS_SD, registrationListener);
}

发现网络上的服务

网络生机勃勃,从凶猛的网络打印机到 温顺的网络摄像头,以及附近井字棋游戏的残酷激烈战斗 玩家。想让您的应用看到这个活力四射的生态系统的关键 就是服务发现您的应用需要监听服务 看看有哪些服务可用,并过滤掉 应用无法使用的任何资源

与服务注册一样,服务发现包含两个步骤: 设置具有相关回调的发现监听器,并将单个异步事件 对 discoverServices() 的 API 调用。

首先,实例化一个实现 NsdManager.DiscoveryListener 的匿名类。以下代码段展示了一个 简单示例:

// Instantiate a new DiscoveryListener
private val discoveryListener = object : NsdManager.DiscoveryListener {

   
// Called as soon as service discovery begins.
   
override fun onDiscoveryStarted(regType: String) {
       
Log.d(TAG, "Service discovery started")
   
}

   
override fun onServiceFound(service: NsdServiceInfo) {
       
// A service was found! Do something with it.
       
Log.d(TAG, "Service discovery success$service")
       
when {
            service
.serviceType != SERVICE_TYPE -> // Service type is the string containing the protocol and
               
// transport layer for this service.
               
Log.d(TAG, "Unknown Service Type: ${service.serviceType}")
            service
.serviceName == 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")
            service
.serviceName.contains("NsdChat") -> nsdManager.resolveService(service, resolveListener)
       
}
   
}

   
override fun onServiceLost(service: NsdServiceInfo) {
       
// When the network service is no longer available.
       
// Internal bookkeeping code goes here.
       
Log.e(TAG, "service lost: $service")
   
}

   
override fun onDiscoveryStopped(serviceType: String) {
       
Log.i(TAG, "Discovery stopped: $serviceType")
   
}

   
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
       
Log.e(TAG, "Discovery failed: Error code:$errorCode")
        nsdManager
.stopServiceDiscovery(this)
   
}

   
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
       
Log.e(TAG, "Discovery failed: Error code:$errorCode")
        nsdManager
.stopServiceDiscovery(this)
   
}
}
public void initializeDiscoveryListener() {

   
// Instantiate a new DiscoveryListener
    discoveryListener
= 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(serviceName)) {
               
// 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: " + serviceName);
           
} else if (service.getServiceName().contains("NsdChat")){
                nsdManager
.resolveService(service, resolveListener);
           
}
       
}

       
@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 void onDiscoveryStopped(String serviceType) {
           
Log.i(TAG, "Discovery stopped: " + serviceType);
       
}

       
@Override
       
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
           
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
            nsdManager
.stopServiceDiscovery(this);
       
}

       
@Override
       
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
           
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
            nsdManager
.stopServiceDiscovery(this);
       
}
   
};
}

NSD API 使用此接口中的方法在发现时通知您的应用 何时启动、何时发生故障,以及何时发现和丢失服务(丢失意味着“ 不再可用”)。请注意,此代码段会 。

  1. 将所找到服务的服务名称与该服务进行比较 本地服务的名称,以确定设备是否刚刚拾取了自己的服务 广播(有效)。
  2. 已检查服务类型,以确认相应服务属于 应用可连接到哪些资源
  3. 检查服务名称以确认连接到正确的 应用。

您不一定需要检查服务名称, 希望连接到特定应用例如,应用可能 只希望连接到在其他设备上运行的其自身实例。但是,如果 应用想要连接到网络打印机,则可以看到该服务类型 为“_ipp._tcp”。

设置监听器后,调用 discoverServices() 并传入服务类型 您的应用应查找的、要使用的发现协议,以及 刚才创建的监听器。

nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
nsdManager.discoverServices(
        SERVICE_TYPE
, NsdManager.PROTOCOL_DNS_SD, discoveryListener);

连接到网络上的服务

当应用在网络上找到要连接的服务时, 必须先使用 resolveService() 方法结合使用。 实现 NsdManager.ResolveListener 以传递到 方法,并使用它获取包含以下内容的 NsdServiceInfo: 以及连接信息

private val resolveListener = object : NsdManager.ResolveListener {

   
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
       
// Called when the resolve fails. Use the error code to debug.
       
Log.e(TAG, "Resolve failed: $errorCode")
   
}

   
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
       
Log.e(TAG, "Resolve Succeeded. $serviceInfo")

       
if (serviceInfo.serviceName == mServiceName) {
           
Log.d(TAG, "Same IP.")
           
return
       
}
        mService
= serviceInfo
       
val port: Int = serviceInfo.port
       
val host: InetAddress = serviceInfo.host
   
}
}
public void initializeResolveListener() {
    resolveListener
= new NsdManager.ResolveListener() {

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

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

           
if (serviceInfo.getServiceName().equals(serviceName)) {
               
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 fun onPause() {
        nsdHelper
?.tearDown()
       
super.onPause()
   
}

   
override fun onResume() {
       
super.onResume()
        nsdHelper
?.apply {
            registerService
(connection.localPort)
            discoverServices
()
       
}
   
}

   
override fun onDestroy() {
        nsdHelper
?.tearDown()
        connection
.tearDown()
       
super.onDestroy()
   
}

   
// NsdHelper's tearDown method
   
fun tearDown() {
        nsdManager
.apply {
            unregisterService
(registrationListener)
            stopServiceDiscovery
(discoveryListener)
       
}
   
}

    // In your application's Activity

   
@Override
   
protected void onPause() {
       
if (nsdHelper != null) {
            nsdHelper
.tearDown();
       
}
       
super.onPause();
   
}

   
@Override
   
protected void onResume() {
       
super.onResume();
       
if (nsdHelper != null) {
            nsdHelper
.registerService(connection.getLocalPort());
            nsdHelper
.discoverServices();
       
}
   
}

   
@Override
   
protected void onDestroy() {
        nsdHelper
.tearDown();
        connection
.tearDown();
       
super.onDestroy();
   
}

   
// NsdHelper's tearDown method
   
public void tearDown() {
        nsdManager
.unregisterService(registrationListener);
        nsdManager
.stopServiceDiscovery(discoveryListener);
   
}