Menggunakan penemuan layanan jaringan

Penemuan layanan jaringan (NSD) memberi aplikasi Anda akses ke layanan yang yang disediakan perangkat di jaringan lokal. Perangkat yang mendukung NSD termasuk {i>printer<i}, webcam, server HTTPS, dan perangkat seluler lainnya.

NSD menerapkan mekanisme Penemuan Layanan berbasis DNS (DNS-SD), yang memungkinkan aplikasi Anda meminta layanan dengan menentukan jenis layanan dan namanya dari {i>instance<i} perangkat yang menyediakan jenis layanan yang diinginkan. DNS-SD adalah 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 multi-player bermain game. NSD API Android menyederhanakan upaya yang diperlukan untuk menerapkan fitur-fitur tersebut.

Pelajaran ini menunjukkan cara membangun aplikasi yang dapat menyiarkan nama dan informasi koneksi ke jaringan lokal dan memindai informasi dari aplikasi lain melakukan hal yang sama. Akhirnya, pelajaran ini menunjukkan cara menghubungkan ke aplikasi yang sama yang berjalan di perangkat lain.

Mendaftarkan layanan di jaringan

Catatan: Langkah ini bersifat opsional. Jika Anda tidak perlu menyiarkan layanan aplikasi Anda melalui jaringan lokal, Anda dapat langsung menuju 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 oleh perangkat lain di jaringan tersebut ketika mereka memutuskan apakah ingin terhubung ke layanan.

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 dapat dilihat oleh perangkat lain di jaringan. Nama terlihat oleh perangkat apa pun di jaringan yang menggunakan NSD untuk mencari dan layanan lokal. Perhatikan bahwa nama harus unik untuk setiap layanan pada jaringan, dan Android akan secara otomatis menangani resolusi konflik. Jika dua perangkat di jaringan keduanya telah menginstal aplikasi NsdChat, salah satu mereka mengubah nama layanan secara otomatis, menjadi sesuatu seperti "NsdChat (1)".

Parameter kedua menetapkan jenis layanan, menentukan protokol dan transport yang digunakan aplikasi. {i>Syntax<i}-nya adalah "_<protokol>._<transportlayer>". Di kolom cuplikan kode, layanan ini menggunakan protokol HTTP yang berjalan melalui TCP. Aplikasi menawarkan layanan {i>printer <i}(misalnya, {i>printer<i} jaringan) akan mengatur ke "_ipp._tcp".

Catatan: International Assigned Numbers Otoritas (IANA) mengelola daftar otoritatif 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 Anda ingin menggunakan jenis layanan baru, Anda harus memesannya dengan mengisi Porta dan Layanan IANA formulir pendaftaran.

Ketika menyetel porta untuk layanan Anda, hindari melakukan hardcode konflik dengan aplikasi lain. Misalnya, dengan asumsi aplikasi Anda selalu menggunakan porta 1337 sehingga memiliki potensi konflik dengan aplikasi terinstal lainnya yang menggunakan porta yang sama. Sebagai gantinya, gunakan porta berikutnya yang tersedia. Karena informasi ini diberikan ke aplikasi lain oleh siaran layanan, tidak perlu porta yang digunakan aplikasi Anda untuk dikenal oleh aplikasi lain pada waktu kompilasi. Sebagai gantinya, aplikasi bisa mendapatkan informasi ini dari siaran layanan Anda, tepat sebelum menyambungkan ke layanan.

Jika Anda bekerja dengan soket, berikut ini cara Anda menginisialisasi soket ke porta yang tersedia hanya 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. Ini berisi callback yang digunakan oleh Android untuk memperingatkan aplikasi Anda tentang keberhasilan atau kegagalan pendaftaran dan pembatalan pendaftaran layanan.

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. Memanggil metode registerService().

Perhatikan, metode ini asinkron, jadi setiap kode 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, dari {i> printer<i} jaringan yang mengerikan hingga webcam jaringan yang berbahaya, ke pertempuran brutal dan sengit dari aksi tic-tac-toe terdekat pemain. Kunci untuk membuat aplikasi Anda melihat ekosistem yang dinamis fungsionalitasnya adalah menemukan layanan. Aplikasi Anda perlu memproses layanan {i>broadcast <i}di jaringan untuk melihat layanan apa yang tersedia, dan menyaring apa pun yang tidak dapat dikerjakan aplikasi.

Penemuan layanan, seperti pendaftaran layanan, memiliki dua langkah: menyiapkan pemroses penemuan dengan callback yang relevan, dan membuat Panggilan API 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 layanan gagal, dan 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 layanan tersebut nama layanan lokal untuk menentukan apakah perangkat baru saja mengambilnya sendiri {i>broadcast<i} (yang valid).
  2. Jenis layanan diperiksa, untuk memverifikasi bahwa itu adalah jenis layanan terhubung dengan aplikasi Anda.
  3. Nama layanan diperiksa untuk memverifikasi koneksi ke alamat aplikasi.

Memeriksa nama layanan tidak selalu diperlukan, dan hanya relevan jika Anda ingin terhubung ke aplikasi tertentu. Misalnya, aplikasi mungkin hanya ingin terhubung ke {i>instance<i} yang berjalan di perangkat lain. Namun, jika aplikasi ingin terhubung ke {i>printer<i} jaringan, cukup untuk melihat bahwa jenis layanan adalah "_ipp._tcp".

Setelah menyiapkan pemroses, panggil discoverServices() dengan meneruskan jenis layanan yang harus dicari aplikasi Anda, protokol penemuan yang 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

Bila aplikasi Anda menemukan layanan pada jaringan yang akan disambungkan, harus terlebih dahulu menentukan informasi koneksi untuk layanan tersebut, dengan menggunakan Metode resolveService(). Implementasikan NsdManager.ResolveListener untuk diteruskan ke class 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 Anda, termasuk alamat IP dan nomor porta. Ini semua Anda harus membuat koneksi jaringan Anda sendiri ke layanan.

Membatalkan pendaftaran layanan dengan penutupan aplikasi

NSD harus mengaktifkan dan menonaktifkan NSD fungsionalitasnya yang sesuai selama siklus proses. Membatalkan pendaftaran aplikasi Anda saat ditutup membantu mencegah aplikasi lain agar komputer masih aktif dan mencoba untuk terhubung ke anotasi. Selain itu, penemuan layanan merupakan 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 siaran dan penemuan 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);
    }