Penemuan layanan jaringan (Network service discovery/NSD) memberi aplikasi Anda akses ke layanan yang disediakan perangkat lain di jaringan lokal. Perangkat yang mendukung NSD meliputi printer, webcam, server HTTPS, dan perangkat seluler lainnya.
NSD menerapkan mekanisme Penemuan Layanan berbasis DNS (DNS-based Service Discovery/DNS-SD), yang memungkinkan aplikasi Anda untuk meminta layanan dengan menentukan jenis layanan dan nama instance perangkat yang menyediakan jenis layanan yang diinginkan. DNS-SD didukung di Android dan platform seluler lainnya.
Penambahan NSD ke aplikasi memungkinkan pengguna mengidentifikasi perangkat lain di jaringan lokal yang mendukung layanan yang diminta aplikasi Anda. Hal ini berguna untuk berbagai aplikasi peer-to-peer seperti berbagi file atau game multi-player. NSD Android API menyederhanakan upaya yang Anda butuhkan untuk menerapkan fitur tersebut.
Tutorial ini menunjukkan cara membuat aplikasi yang dapat menyiarkan nama dan informasi koneksinya ke jaringan lokal serta memindai informasi dari aplikasi lain yang melakukan hal yang sama. Terakhir, tutorial ini menunjukkan cara menghubungkan ke aplikasi yang sama, yang dijalankan di perangkat lain.
Mendaftarkan layanan di jaringan
Catatan: Langkah ini bersifat opsional. Jika Anda tidak ingin menyiarkan layanan aplikasi melalui jaringan lokal, Anda dapat langsung menuju ke bagian berikutnya, Menemukan Layanan pada Jaringan.
Untuk mendaftarkan layanan Anda di jaringan lokal, terlebih dahulu buat objek NsdServiceInfo
. Objek ini memberikan informasi yang digunakan oleh perangkat lain di jaringan saat memutuskan untuk tersambung ke layanan Anda.
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); ... }
Cuplikan kode tersebut menetapkan nama layanan ke "NsdChat". Nama layanan adalah nama instance: nama tersebut merupakan nama yang dapat dilihat oleh perangkat lain di jaringan. Nama ini dapat dilihat oleh perangkat mana pun di jaringan yang menggunakan NSD untuk mencari layanan lokal. Perlu diingat bahwa nama harus unik untuk setiap layanan di jaringan, dan Android secara otomatis menangani resolusi konflik. Jika dua perangkat di jaringan memiliki aplikasi NsdChat yang terinstal, salah satu di antaranya akan mengubah nama layanan secara otomatis, misalnya seperti "NsdChat (1)".
Parameter kedua menetapkan jenis layanan, menentukan protokol dan lapisan transport yang digunakan aplikasi. Sintaksnya adalah "_<protocol>._<transportlayer>". Di cuplikan kode, layanan menggunakan protokol HTTP yang berjalan di TCP. Aplikasi yang menawarkan layanan printer (misalnya, printer jaringan) akan menetapkan jenis layanan ke "_ipp._tcp".
Catatan: International Assigned Numbers Authority (IANA) mengelola daftar otoritatif yang terpusat untuk jenis layanan yang digunakan oleh protokol penemuan layanan seperti NSD dan Bonjour. Anda dapat mendownload daftar tersebut dari daftar nama layanan dan nomor port IANA. Jika ingin menggunakan jenis layanan baru, Anda harus memesannya dengan mengisi formulir pendaftaran Layanan dan Port IANA.
Saat menyetel port untuk layanan Anda, hindari melakukan hardcoding karena akan konflik dengan aplikasi lain. Misalnya, apabila aplikasi Anda selalu menggunakan port 1337, aplikasi mungkin akan mengalami konflik dengan aplikasi terinstal lainnya yang menggunakan port yang sama. Sebagai gantinya, gunakan port berikutnya dari perangkat yang tersedia. Karena informasi ini disampaikan ke aplikasi lain melalui siaran layanan, port tidak perlu digunakan oleh aplikasi Anda agar diketahui oleh aplikasi lain pada waktu kompilasi. Sebagai gantinya, aplikasi dapat memperoleh informasi ini dari siaran layanan Anda, tepat sebelum tersambung ke layanan.
Jika Anda bekerja dengan soket, berikut adalah cara untuk menginisialisasi soket ke port yang tersedia dengan menetapkannya ke 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(); ... }
Setelah menentukan objek NsdServiceInfo
, Anda harus mengimplementasikan antarmuka RegistrationListener
. Antarmuka ini berisi callback yang digunakan oleh Android untuk memberi tahu aplikasi Anda tentang keberhasilan atau kegagalan pendaftaran layanan dan pembatalan pendaftaran.
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. } }; }
Sekarang Anda memiliki semua komponen untuk mendaftarkan layanan Anda. Panggil metode registerService()
.
Perhatikan bahwa metode ini asinkron, sehingga kode apa pun yang perlu dijalankan setelah layanan didaftarkan harus menggunakan metode 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); }
Menemukan layanan pada jaringan
Jaringan ini penuh dengan kehidupan, mulai dari printer jaringan yang 'liar' hingga webcam jaringan yang 'jinak', hingga pertempuran seru dan brutal dari pemain tic-tac-toe di sekitar. Kunci agar aplikasi Anda melihat ekosistem fungsi yang dinamis ini adalah penemuan layanan. Aplikasi Anda perlu memproses siaran layanan pada jaringan untuk melihat layanan apa saja yang tersedia, dan memfilter apa pun yang tidak dapat ditangani aplikasi.
Penemuan layanan, seperti pendaftaran layanan, memiliki dua langkah: menyiapkan pemroses penemuan dengan callback yang relevan, dan membuat panggilan API asinkron tunggal untuk discoverServices()
.
Pertama, buat instance dari class anonim yang mengimplementasikan NsdManager.DiscoveryListener
. Cuplikan berikut menunjukkan contoh sederhana:
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 menggunakan metode dalam antarmuka ini untuk memberi tahu aplikasi Anda saat penemuan dimulai, saat gagal, serta saat layanan ditemukan dan hilang (hilang berarti "tidak lagi tersedia"). Perhatikan bahwa cuplikan ini melakukan beberapa pemeriksaan saat layanan ditemukan.
- Nama layanan dari layanan yang ditemukan dibandingkan dengan nama layanan dari layanan lokal untuk menentukan apakah perangkat baru saja mengambil siarannya sendiri (yang valid).
- Jenis layanan diperiksa, untuk memastikan bahwa ini adalah jenis layanan yang dapat disambungkan ke aplikasi Anda.
- Nama layanan diperiksa untuk memastikan koneksi ke aplikasi yang benar.
Memeriksa nama layanan tidak selalu diperlukan, dan hanya relevan jika Anda ingin terhubung ke aplikasi tertentu. Misalnya, aplikasi mungkin hanya perlu terhubung ke instance yang berjalan di perangkat lain. Namun, jika aplikasi perlu terhubung ke printer jaringan, cukup lihat bahwa jenis layanannya adalah _ _ex._tcp_.
Setelah menyiapkan pemroses, panggil discoverServices()
, yang meneruskan jenis layanan yang harus dicari aplikasi Anda, protokol penemuan yang akan digunakan, dan pemroses yang baru saja Anda buat.
Kotlin
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
Java
nsdManager.discoverServices( SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
Menghubungkan ke layanan pada jaringan
Jika aplikasi Anda menemukan layanan pada jaringan yang akan tersambung, aplikasi harus terlebih dahulu menentukan informasi koneksi untuk layanan tersebut, menggunakan metode resolveService()
.
Implementasikan NsdManager.ResolveListener
untuk meneruskan ke metode ini, dan gunakan untuk mendapatkan NsdServiceInfo
yang berisi informasi koneksi.
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(); } }; }
Setelah layanan terselesaikan, aplikasi Anda akan menerima informasi layanan lengkap termasuk alamat IP dan nomor port. Ini adalah segala yang Anda butuhkan untuk membuat koneksi jaringan sendiri ke layanan.
Membatalkan pendaftaran layanan dengan penutupan aplikasi
Penting untuk mengaktifkan dan menonaktifkan fungsi NSD yang sesuai selama siklus proses aplikasi. Membatalkan pendaftaran aplikasi saat menutup akan membantu mencegah aplikasi lain menganggapnya masih aktif dan mencoba menyambungkan ke aplikasi tersebut. Selain itu, penemuan layanan juga merupakan pengoperasian yang mahal, dan harus dihentikan saat Aktivitas induk dijeda, dan diaktifkan kembali saat Aktivitas dilanjutkan. Ganti metode siklus proses Aktivitas utama Anda dan sisipkan kode untuk memulai dan menghentikan penemuan dan siaran layanan yang sesuai.
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); }