使用網路服務探索功能

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

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

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

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

在網路上註冊服務

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

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

KotlinJava
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)
       
...
   
}
}
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 即可

KotlinJava
fun initializeServerSocket() {
   
// Initialize a server socket on the next available port.
    serverSocket
= ServerSocket(0).also { socket ->
       
// Store the chosen port.
        mLocalPort
= socket.localPort
       
...
   
}
}
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 用來向應用程式發出快訊的回呼 或服務註冊/註冊失敗或失敗。

KotlinJava
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.
   
}
}
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() 方法。

KotlinJava
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)
   
}
}
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 的匿名類別例項化。以下程式碼片段顯示 簡單的範例:

KotlinJava
// 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)
   
}
}
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() 並傳入服務類型 應用程式應尋找要使用的探索通訊協定 即可。

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

連線至網路上的服務

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

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

KotlinJava
    // 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)
       
}
   
}

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