網路服務探索 (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 會在探索時,使用這個介面中的方法通知您的應用程式 失敗、發生錯誤及找到服務並遺失時 (遺失表示 )。請注意,這個程式碼片段會執行多項檢查 找到服務後
- 系統會將找到的服務名稱與服務進行比較 本機服務的名稱,判斷裝置是否剛拿到手 廣播 (有效的)。
- 已勾選服務類型,以確認這是服務類型 應用程式之間的連線
- 系統會檢查服務名稱,確認與正確連線 應用程式。
您不一定要檢查服務名稱,只有在您使用了 來連線至特定應用程式舉例來說,應用程式 只想連線至其他裝置上執行的執行個體。不過, 應用程式想要連線至網路印表機,您可以看到服務類型 為「_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); }