Mit der Netzwerkdiensterkennung (Network Service Discovery, NSD) erhält Ihre Anwendung Zugriff auf Dienste, die andere Geräte in einem lokalen Netzwerk bereitstellen. Zu den Geräten, die NSD unterstützen, gehören Drucker, Webcams, HTTPS-Server und andere Mobilgeräte.
NSD implementiert den DNS-basierten Service Discovery-Mechanismus (DNS-SD), mit dem deine Anwendung Dienste anfordern kann. Dazu wird ein Diensttyp und der Name einer Geräteinstanz angegeben, die den gewünschten Diensttyp bereitstellt. DNS-SD wird sowohl unter Android als auch auf anderen mobilen Plattformen unterstützt.
Wenn Sie Ihrer Anwendung NSD hinzufügen, können Ihre Nutzer andere Geräte im lokalen Netzwerk identifizieren, die die Dienste unterstützen, die von Ihrer Anwendung angefordert werden. Dies ist nützlich für eine Vielzahl von Peer-to-Peer-Anwendungen wie die Dateifreigabe oder Multiplayer-Spiele. Die NSD APIs von Android vereinfachen die Implementierung solcher Funktionen.
In dieser Lektion erfahren Sie, wie Sie eine Anwendung erstellen, die Name- und Verbindungsinformationen an das lokale Netzwerk übertragen und nach Informationen von anderen Anwendungen sucht, die dies ebenfalls tun. In dieser Lektion erfahren Sie, wie Sie eine Verbindung zur selben Anwendung herstellen, die auf einem anderen Gerät ausgeführt wird.
Dienst im Netzwerk registrieren
Hinweis : Dieser Schritt ist optional. Wenn Sie die Dienste Ihrer Anwendung nicht über das lokale Netzwerk übertragen möchten, können Sie mit dem nächsten Abschnitt Dienste im Netzwerk finden fortfahren.
Erstellen Sie zuerst ein NsdServiceInfo
-Objekt, um Ihren Dienst im lokalen Netzwerk zu registrieren. Dieses Objekt stellt die Informationen bereit, die andere Geräte im Netzwerk verwenden, wenn sie entscheiden, ob sie eine Verbindung zu Ihrem Dienst herstellen möchten.
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); ... }
Mit diesem Code-Snippet wird der Dienstname auf „NsdChat“ festgelegt. Der Dienstname ist der Instanzname. Er ist für andere Geräte im Netzwerk sichtbar. Der Name ist für jedes Gerät im Netzwerk sichtbar, das NSD für die Suche nach lokalen Diensten verwendet. Beachten Sie, dass der Name für jeden Dienst im Netzwerk eindeutig sein muss und dass die Konfliktlösung automatisch von Android behoben wird. Wenn auf zwei Geräten im Netzwerk auf beiden Geräten die NsdChat-Anwendung installiert ist, ändert eines davon den Dienstnamen automatisch in "NsdChat (1)".
Der zweite Parameter legt den Diensttyp fest und gibt an, welche Protokoll- und Transportschicht die Anwendung verwendet. Die Syntax lautet "_<protocol>._<transportlayer>". Im Code-Snippet verwendet der Dienst das HTTP-Protokoll, das über TCP ausgeführt wird. Eine Anwendung, die einen Druckerdienst anbietet (z. B. einen Netzwerkdrucker), würde den Diensttyp auf "_ipp._tcp" setzen.
Hinweis : Die International Assigned Numbers Authority (IANA) verwaltet eine zentrale, verbindliche Liste von Diensttypen, die von Diensterkennungsprotokollen wie NSD und Bonjour verwendet werden. Sie können die Liste aus der IANA-Liste der Dienstnamen und Portnummern herunterladen. Wenn Sie einen neuen Diensttyp verwenden möchten, sollten Sie ihn reservieren. Dazu füllen Sie das Anmeldeformular für die IANA-Ports und Dienste aus.
Vermeiden Sie beim Festlegen des Ports für Ihren Dienst eine Hartcodierung, da dies zu Konflikten mit anderen Anwendungen führt. Wenn Sie zum Beispiel davon ausgehen, dass Ihre Anwendung immer Port 1337 verwendet, kann dies zu einem Konflikt mit anderen installierten Anwendungen führen, die denselben Port verwenden. Verwenden Sie stattdessen den nächsten verfügbaren Port des Geräts. Da diese Informationen über einen Dienst-Broadcast anderen Anwendungen bereitgestellt werden, muss der von Ihrer Anwendung verwendete Port bei der Kompilierung den anderen Anwendungen nicht bekannt sein. Stattdessen können die Anwendungen diese Informationen vom Dienst-Broadcast abrufen, bevor sie eine Verbindung zu Ihrem Dienst herstellen.
Wenn Sie mit Sockets arbeiten, können Sie einen Socket für jeden verfügbaren Port initialisieren, indem Sie ihn einfach auf 0 setzen.
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(); ... }
Nachdem Sie das NsdServiceInfo
-Objekt definiert haben, müssen Sie die RegistrationListener
-Schnittstelle implementieren. Diese Schnittstelle enthält Callbacks, die von Android verwendet werden, um Ihre App über Erfolg oder Fehler bei der Registrierung oder Aufhebung der Registrierung zu informieren.
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. } }; }
Jetzt haben Sie alle Bestandteile für die Registrierung Ihres Dienstes. Rufen Sie die Methode registerService()
auf.
Diese Methode ist asynchron. Code, der nach der Registrierung des Dienstes ausgeführt werden muss, muss also in die onServiceRegistered()
-Methode eingefügt werden.
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); }
Dienste im Netzwerk finden
Das Netzwerk wimmelt von Leben, von den faszinierenden Netzwerkdruckern über die sanftmütigen Netzwerk-Webcams bis zu den brutalen, heftigen Schlachten von Tic-Tac-Toe-Spielern in der Nähe. Service Discovery ist entscheidend, damit Ihre Anwendung diese vielseitigen Funktionen nutzen kann. Ihre Anwendung muss auf Dienstübertragungen im Netzwerk warten, um zu sehen, welche Dienste verfügbar sind, und alles herauszufiltern, mit dem die Anwendung nicht kompatibel ist.
Die Diensterkennung umfasst wie die Dienstregistrierung zwei Schritte: Einrichtung eines Erkennungs-Listeners mit den relevanten Callbacks und Ausführung eines einzelnen asynchronen API-Aufrufs an discoverServices()
.
Instanziieren Sie zuerst eine anonyme Klasse, die NsdManager.DiscoveryListener
implementiert. Das folgende Snippet zeigt ein einfaches Beispiel:
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); } }; }
Die NSD API verwendet die Methoden in dieser Schnittstelle, um Ihre Anwendung darüber zu informieren, wann die Erkennung gestartet wird, wann sie fehlschlägt und wann Dienste gefunden und verloren werden (verloren bedeutet, dass sie nicht mehr verfügbar ist). Dieses Snippet führt mehrere Prüfungen durch, wenn ein Dienst gefunden wird.
- Der Dienstname des gefundenen Dienstes wird mit dem Dienstnamen des lokalen Dienstes verglichen, um festzustellen, ob das Gerät gerade eine eigene Broadcasting-Übertragung empfangen hat (die gültig ist).
- Der Diensttyp ist markiert, um zu bestätigen, dass es sich um einen Diensttyp handelt, zu dem Ihre Anwendung eine Verbindung herstellen kann.
- Der Dienstname wird überprüft, um die Verbindung zur richtigen Anwendung zu verifizieren.
Das Prüfen des Dienstnamens ist nicht immer erforderlich und nur relevant, wenn Sie eine Verbindung zu einer bestimmten Anwendung herstellen möchten. Beispielsweise möchte die Anwendung möglicherweise nur eine Verbindung zu eigenen Instanzen herstellen, die auf anderen Geräten ausgeführt werden. Wenn die Anwendung jedoch eine Verbindung zu einem Netzwerkdrucker herstellen möchte, reicht es aus, zu sehen, dass der Diensttyp „_ipp._tcp“ ist.
Rufen Sie nach dem Einrichten des Listeners discoverServices()
auf und übergeben Sie den Diensttyp, nach dem Ihre Anwendung suchen soll, das zu verwendende Erkennungsprotokoll und den soeben erstellten Listener.
Kotlin
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
Java
nsdManager.discoverServices( SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
Verbindung zu Diensten im Netzwerk herstellen
Wenn die Anwendung im Netzwerk einen Dienst findet, zu dem sie eine Verbindung herstellen kann, muss sie zuerst mit der Methode resolveService()
die Verbindungsinformationen für diesen Dienst ermitteln.
Implementieren Sie einen NsdManager.ResolveListener
, der an diese Methode übergeben wird, und rufen Sie damit ein NsdServiceInfo
mit den Verbindungsinformationen ab.
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(); } }; }
Sobald der Dienst aufgelöst ist, erhält Ihre Anwendung detaillierte Dienstinformationen, einschließlich einer IP-Adresse und Portnummer. Dies ist alles, was Sie benötigen, um Ihre eigene Netzwerkverbindung zum Dienst herzustellen.
Dienstregistrierung beim Schließen der Anwendung aufheben
Es ist wichtig, die NSD-Funktionalität während des Lebenszyklus der Anwendung entsprechend zu aktivieren und zu deaktivieren. Wenn Sie die Registrierung Ihrer Anwendung beim Beenden aufheben, verhindern Sie, dass andere Anwendungen sie als noch aktiv wahrnehmen und versuchen, eine Verbindung zu ihr herzustellen. Außerdem ist die Diensterkennung ein teurer Vorgang und sollte beendet werden, wenn die übergeordnete Aktivität pausiert, und wieder aktiviert werden, wenn die Aktivität fortgesetzt wird. Überschreiben Sie die Lebenszyklusmethoden Ihrer Hauptaktivität und fügen Sie Code ein, um die Dienstübertragung und -erkennung entsprechend zu starten und zu stoppen.
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); }