網路服務探索 (NSD) 可讓您的應用程式存取其他服務 使用區域網路提供的裝置支援 NSD 的裝置包括印表機 網路攝影機、HTTPS 伺服器和其他行動裝置
NSD 採用 DNS 型服務探索 (DNS-SD) 機制, 可讓應用程式指定服務類型和名稱,藉此要求服務 提供所需服務類型的裝置執行個體DNS-SD 為 Android 和其他行動平台都支援這項功能
只要在應用程式中加入 NSD,使用者就能識別 Google Play 上的其他裝置 支援應用程式要求服務的本機網路。這對使用者來說相當實用 提供多種點對點應用程式,例如檔案共用或多人 遊戲。Android 的 NSD API 可簡化實作程序 這類功能
本課程將說明如何建構可廣播自家應用程式 連線至區域網路,並掃描是否有資訊 其他應用程式也能以相同方式運作最後,本課程將說明 這些連線會連線至在另一部裝置上執行的同一個應用程式
在網路上註冊服務
注意事項 :此為選用步驟。如果 不在乎透過區域網路廣播應用程式服務 可以直接跳到 下一節「探索網路上的服務」。
如要在區域網路上註冊服務,請先建立 NsdServiceInfo
物件。這個物件會提供
網路上的其他裝置在考慮是否要連上您的
課程中也會快速介紹 Memorystore
這是 Google Cloud 的全代管 Redis 服務
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 即可
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 用來向應用程式發出快訊的回呼
或服務註冊/註冊失敗或失敗。
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()
方法。
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
的匿名類別例項化。以下程式碼片段顯示
簡單的範例:
// 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 會在探索時,使用這個介面中的方法通知您的應用程式 失敗、發生錯誤及找到服務並遺失時 (遺失表示 )。請注意,這個程式碼片段會執行多項檢查 找到服務後
- 系統會將找到的服務名稱與服務進行比較 本機服務的名稱,判斷裝置是否剛拿到手 廣播 (有效的)。
- 已勾選服務類型,以確認這是服務類型 應用程式之間的連線
- 系統會檢查服務名稱,確認與正確連線 應用程式。
您不一定要檢查服務名稱,只有在您使用了 來連線至特定應用程式舉例來說,應用程式 只想連線至其他裝置上執行的執行個體。不過, 應用程式想要連線至網路印表機,您可以看到服務類型 為「_ipp._tcp」。
設定事件監聽器後,請呼叫 discoverServices()
並傳入服務類型
應用程式應尋找要使用的探索通訊協定
即可。
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
nsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
連線至網路上的服務
當應用程式在網路上找到可連線的服務時,
必須先使用
resolveService()
方法。
實作 NsdManager.ResolveListener
並傳入此項目
方法,並使用此方法取得 NsdServiceInfo
以及連線資訊
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 以提供合適的功能 生命週期在應用程式關閉時取消註冊, 以免其他應用程式認為裝置仍然有效,並嘗試 基礎架構此外,服務探索是一項耗費高成本的作業,應該停止 而在活動暫停時重新啟用 已恢復計時覆寫主要活動的生命週期方法並插入程式碼 以便開始及停止服務廣播和探索作業。
// 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);
}