Menggunakan penemuan layanan jaringan

Penemuan layanan jaringan (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 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.

Menambahkan 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 API Android menyederhanakan upaya yang diperlukan untuk mengimplementasikan fitur tersebut.

Tutorial ini menunjukkan cara membuat aplikasi yang dapat menyiarkan nama dan informasi koneksinya ke jaringan lokal dan memindai informasi dari aplikasi lain yang melakukan hal yang sama. Terakhir, pelajaran ini menunjukkan cara terhubung ke aplikasi yang sama yang berjalan di perangkat lain.

Mendaftarkan layanan di jaringan

Catatan: Langkah ini bersifat opsional. Jika tidak ingin menyiarkan layanan aplikasi melalui jaringan lokal, Anda dapat langsung melanjutkan ke bagian berikutnya, Menemukan Layanan di Jaringan.

Untuk mendaftarkan layanan Anda di jaringan lokal, terlebih dahulu buat objek NsdServiceInfo. Objek ini memberikan informasi yang digunakan perangkat lain di jaringan saat memutuskan apakah akan terhubung ke layanan Anda atau tidak.

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: ini adalah nama yang dapat dilihat oleh perangkat lain di jaringan. Nama ini dapat dilihat oleh perangkat apa pun di jaringan yang menggunakan NSD untuk mencari layanan lokal. Perlu diingat bahwa nama untuk layanan apa pun di jaringan harus unik, dan Android akan otomatis menangani resolusi konflik. Jika dua perangkat di jaringan telah menginstal aplikasi NsdChat, salah satunya akan mengubah nama layanan secara otomatis, seperti "NsdChat (1)".

Parameter kedua menetapkan jenis layanan, menentukan protokol dan lapisan transport yang digunakan aplikasi. Sintaksisnya adalah "_<protocol>._<transportlayer>". Dalam cuplikan kode, layanan menggunakan protokol HTTP yang berjalan melalui TCP. Aplikasi yang menawarkan layanan printer (misalnya, printer jaringan) akan menetapkan jenis layanan ke "_ipp._tcp".

Catatan: International Assigned Numbers Authority (IANA) mengelola daftar jenis layanan yang terpusat dan otoritatif yang digunakan oleh protokol penemuan layanan seperti NSD dan Bonjour. Anda dapat mendownload daftar ini 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 hardcode karena hal ini bentrok dengan aplikasi lain. Misalnya, mengasumsikan bahwa aplikasi Anda selalu menggunakan port 1337 akan mengakibatkan potensi konflik dengan aplikasi terinstal lainnya yang menggunakan port yang sama. Sebagai gantinya, gunakan port perangkat berikutnya yang tersedia. Karena informasi ini diberikan ke aplikasi lain melalui siaran layanan, port yang digunakan aplikasi Anda tidak perlu diketahui oleh aplikasi lain pada waktu kompilasi. Sebagai gantinya, aplikasi bisa mendapatkan informasi ini dari siaran layanan Anda, tepat sebelum terhubung ke layanan.

Jika Anda menangani soket, berikut cara menginisialisasi soket ke port yang tersedia, cukup dengan menyetelnya 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 brutal, webcam jaringan jinak, hingga pertempuran brutal dan sengit dari pemain tic-tac-toe di sekitar. Kunci agar aplikasi Anda melihat ekosistem fungsi yang dinamis ini adalah penemuan layanan. Aplikasi Anda perlu mendengarkan siaran layanan di jaringan untuk melihat layanan apa 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 ke 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 layanan lokal untuk menentukan apakah perangkat baru saja mengambil siarannya sendiri (yang valid).
  2. Jenis layanan diperiksa, untuk memverifikasi bahwa ini adalah jenis layanan yang dapat dihubungkan oleh aplikasi Anda.
  3. Nama layanan diperiksa untuk memverifikasi 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 ingin terhubung ke instance yang berjalan di perangkat lain. Namun, jika aplikasi ingin terhubung ke printer jaringan, cukup lihat bahwa jenis layanannya adalah "_ipp._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 dihubungkan, aplikasi harus terlebih dahulu menentukan informasi koneksi untuk layanan tersebut, menggunakan metode resolveService(). Implementasikan NsdManager.ResolveListener untuk diteruskan 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 diselesaikan, aplikasi Anda akan menerima informasi layanan mendetail termasuk alamat IP dan nomor port. Ini adalah semua yang Anda butuhkan untuk membuat koneksi jaringan sendiri ke layanan.

Membatalkan pendaftaran layanan dengan penutupan aplikasi

Anda harus mengaktifkan dan menonaktifkan fungsi NSD yang sesuai selama siklus proses aplikasi. Membatalkan pendaftaran aplikasi saat tertutup akan membantu mencegah aplikasi lain menganggapnya masih aktif dan mencoba terhubung ke aplikasi tersebut. Selain itu, penemuan layanan adalah operasi 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);
    }