使用網路服務探索功能

網路服務探索 (NSD) 可讓您的應用程式存取其他服務 使用區域網路提供的裝置支援 NSD 的裝置包括印表機 網路攝影機、HTTPS 伺服器和其他行動裝置

NSD 採用 DNS 型服務探索 (DNS-SD) 機制, 可讓應用程式指定服務類型和名稱,藉此要求服務 提供所需服務類型的裝置執行個體DNS-SD 為 Android 和其他行動平台都支援這項功能

只要在應用程式中加入 NSD,使用者就能識別 Google Play 上的其他裝置 支援應用程式要求服務的本機網路。這對使用者來說相當實用 提供多種點對點應用程式,例如檔案共用或多人 遊戲。Android 的 NSD API 可簡化實作程序 這類功能

本課程將說明如何建構可廣播自家應用程式 連線至區域網路,並掃描是否有資訊 其他應用程式也能以相同方式運作最後,本課程將說明 這些連線會連線至在另一部裝置上執行的同一個應用程式

在網路上註冊服務

注意事項 :此為選用步驟。如果 不在乎透過區域網路廣播應用程式服務 可以直接跳到 下一節「探索網路上的服務」。

如要在區域網路上註冊服務,請先建立 NsdServiceInfo 物件。這個物件會提供 網路上的其他裝置在考慮是否要連上您的 課程中也會快速介紹 Memorystore 這是 Google Cloud 的全代管 Redis 服務

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);
    ...
}

這段程式碼會將服務名稱設為「NsdChat」。服務名稱 是執行個體名稱,這會是網路中其他裝置看到的名稱。 網路中任何使用 NSD 查詢網路的裝置都能看到這個名稱 本地服務。請記住,對於 連線時,Android 會自動處理衝突解決。如果 網路中的兩部裝置已安裝 NsdChat 應用程式, 他們會自動將服務名稱變更為「NsdChat」 (1)」。

第二個參數會設定服務類型,指明要採用何種通訊協定和傳輸方式 是在應用程式使用的資料層語法為 「_<protocol>._<transportlayer>」。在 程式碼片段,表示服務使用透過 TCP 執行的 HTTP 通訊協定。應用程式 提供印表機服務 (例如網路印表機) 時,應設定 設為「_ipp._tcp」。

注意: 國際指派的號碼 管理局 (IANA) 會集中管理 服務探索通訊協定 (如 NSD 和 Bonjour) 使用的服務類型官方清單。 您可以前往 IANA 服務名稱和通訊埠編號清單。 如果您打算使用新的服務類型,請填妥並填寫 IANA 連接埠與服務 註冊表單

設定服務的通訊埠時,請避免以硬式編碼的方式寫入服務 與其他應用程式發生衝突舉例來說,假設 如果應用程式一律使用通訊埠 1337,就會引發 使用相同通訊埠的其他已安裝應用程式。請改為使用裝置的 就會看到下一個可用通訊埠因為這項資訊是由 也沒有應用程式使用的通訊埠 也不必擔心不過,應用程式可能會 系統會從服務廣播訊息擷取這項資訊,然後再連線至 課程中也會快速介紹 Memorystore 這是 Google Cloud 的全代管 Redis 服務

如果您使用通訊端,以下是將通訊端初始化為 只需將其設為 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();
    ...
}

現在您已定義 NsdServiceInfo 物件,因此您需要實作 RegistrationListener 介面。這個 介麵包含 Android 用來向應用程式發出快訊的回呼 或服務註冊/註冊失敗或失敗。

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.
        }
    };
}

現在,您已完成註冊服務的所有步驟。呼叫下列方法 registerService()

請注意,此方法屬於非同步性質,因此任何需要執行 註冊服務後,請務必加入 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);
}

探索網路上的服務

網路與生活密不可分,從神奇的網路印表機到 利用角色網路網路攝影機,加入附近西洋棋盤的激烈激烈戰鬥 廣告。如此一來,您的應用程式就能看見這個蓬勃發展的 就是服務探索您的應用程式需要監聽服務 透過網路廣播以查看哪些服務可用 任何應用程式無法使用的功能

服務探索 (例如服務註冊) 有兩個步驟: 使用相關回呼設定探索事件監聽器,並單一非同步 對 discoverServices() 發出的 API 呼叫。

首先,將實作 NsdManager.DiscoveryListener 的匿名類別例項化。以下程式碼片段顯示 簡單的範例:

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 會在探索時,使用這個介面中的方法通知您的應用程式 失敗、發生錯誤及找到服務並遺失時 (遺失表示 )。請注意,這個程式碼片段會執行多項檢查 找到服務後

  1. 系統會將找到的服務名稱與服務進行比較 本機服務的名稱,判斷裝置是否剛拿到手 廣播 (有效的)。
  2. 已勾選服務類型,以確認這是服務類型 應用程式之間的連線
  3. 系統會檢查服務名稱,確認與正確連線 應用程式。

您不一定要檢查服務名稱,只有在您使用了 來連線至特定應用程式舉例來說,應用程式 只想連線至其他裝置上執行的執行個體。不過, 應用程式想要連線至網路印表機,您可以看到服務類型 為「_ipp._tcp」。

設定事件監聽器後,請呼叫 discoverServices() 並傳入服務類型 應用程式應尋找要使用的探索通訊協定 即可。

Kotlin

nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)

Java

nsdManager.discoverServices(
        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);

連線至網路上的服務

當應用程式在網路上找到可連線的服務時, 必須先使用 resolveService() 方法。 實作 NsdManager.ResolveListener 並傳入此項目 方法,並使用此方法取得 NsdServiceInfo 以及連線資訊

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();
        }
    };
}

服務解決後,應用程式就會收到 包括 IP 位址和通訊埠編號一切都完成了 您必須自行建立該服務的網路連線。

在應用程式關閉時取消註冊服務

請務必啟用及停用 NSD 以提供合適的功能 生命週期在應用程式關閉時取消註冊, 以免其他應用程式認為裝置仍然有效,並嘗試 基礎架構此外,服務探索是一項耗費高成本的作業,應該停止 而在活動暫停時重新啟用 已恢復計時覆寫主要活動的生命週期方法並插入程式碼 以便開始及停止服務廣播和探索作業。

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);
    }