网络服务发现 (NSD) 可让您的应用访问 设备通过本地网络提供。支持 NSD 的设备包括打印机 摄像头、HTTPS 服务器和其他移动设备。
NSD 实现了基于 DNS 的服务发现 (DNS-SD) 机制, 允许您的应用通过指定服务类型和名称来请求服务 提供所需服务类型的设备实例的角色。DNS-SD 现为 同时支持 Android 和其他移动平台。
向应用添加 NSD 后,您的用户可以识别设备上的其他设备 支持您的应用请求的服务。这对于 各种点对点应用,例如文件共享或多人游戏 游戏。Android 的 NSD API 简化了实现 API 所需的工作量 此类功能
本课将介绍如何构建可以广播 网络名称和连接信息,并扫描查找信息, 其他同类应用的影响最后,本课将介绍 即可连接到另一台设备上运行的同一应用
在网络上注册您的服务
注意:此步骤是可选的。如果 您不需要在意通过本地网络广播应用的服务 则可以跳至 下一部分:发现网络上的服务。
如需在本地网络上注册服务,请先创建一个 NsdServiceInfo
对象。此对象可提供
网络中的其他设备在决定是否连接到您的
服务。
Kotlin
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) ... } }
Java
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 即可。
Kotlin
fun initializeServerSocket() { // Initialize a server socket on the next available port. serverSocket = ServerSocket(0).also { socket -> // Store the chosen port. mLocalPort = socket.localPort ... } }
Java
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 用于提醒您的应用
服务注册和取消注册是否成功。
Kotlin
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. } }
Java
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()
方法中使用。
Kotlin
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) } }
Java
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
的匿名类。以下代码段展示了一个
简单示例:
Kotlin
// 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) } }
Java
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 使用此接口中的方法在发现时通知您的应用 何时启动、何时发生故障,以及何时发现和丢失服务(丢失意味着“ 不再可用”)。请注意,此代码段会 。
- 将所找到服务的服务名称与该服务进行比较 本地服务的名称,以确定设备是否刚刚拾取了自己的服务 广播(有效)。
- 已检查服务类型,以确认相应服务属于 应用可连接到哪些资源
- 检查服务名称以确认连接到正确的 应用。
您不一定需要检查服务名称, 希望连接到特定应用例如,应用可能 只希望连接到在其他设备上运行的其自身实例。但是,如果 应用想要连接到网络打印机,则可以看到该服务类型 为“_ipp._tcp”。
设置监听器后,调用 discoverServices()
并传入服务类型
您的应用应查找的、要使用的发现协议,以及
刚才创建的监听器。
Kotlin
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
Java
nsdManager.discoverServices( SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
连接到网络上的服务
当应用在网络上找到要连接的服务时,
必须先使用
resolveService()
方法结合使用。
实现 NsdManager.ResolveListener
以传递到
方法,并使用它获取包含以下内容的 NsdServiceInfo
:
以及连接信息
Kotlin
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 } }
Java
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 的生命周期方法并插入代码 根据需要启动和停止服务广播和发现。
Kotlin
// 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) } }
Java
// 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); }