Menggunakan penemuan layanan jaringan

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.

  1. Nama layanan dari layanan yang ditemukan dibandingkan dengan nama layanan dari layanan lokal untuk menentukan apakah perangkat baru saja mengambil siarannya sendiri (yang valid).
  2. Jenis layanan diperiksa, untuk memastikan bahwa ini adalah jenis layanan yang dapat disambungkan ke aplikasi Anda.
  3. 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);
        }