編寫(xiě):naizhengtan - 原文:http://developer.android.com/training/connect-devices-wirelessly/nsd.html
添加網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)(Network Service Discovery)到我們的 app 中,可以使我們的用戶(hù)辨識(shí)在局域網(wǎng)內(nèi)支持我們的 app 所請(qǐng)求的服務(wù)的設(shè)備。這種技術(shù)在點(diǎn)對(duì)點(diǎn)應(yīng)用中能夠提供大量幫助,例如文件共享、聯(lián)機(jī)游戲等。Android 的網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)(NSD)API 大大降低實(shí)現(xiàn)上述功能的難度。
本講將簡(jiǎn)要介紹如何創(chuàng)建 NSD 應(yīng)用,使其能夠在本地網(wǎng)絡(luò)內(nèi)廣播自己的名稱(chēng)和連接信息,并且掃描其它正在做同樣事情的應(yīng)用信息。最后,將介紹如何連接運(yùn)行著同樣應(yīng)用的另一臺(tái)設(shè)備。
Note: 這一步驟是選做的。如果我們并不關(guān)心在本地網(wǎng)絡(luò)上廣播 app 服務(wù),那么我們可以跳過(guò)這一步,直接嘗試發(fā)現(xiàn)網(wǎng)絡(luò)中的服務(wù)。
在局域網(wǎng)內(nèi)注冊(cè)自己服務(wù)的第一步是創(chuàng)建 NsdServiceInfo 對(duì)象。此對(duì)象包含的信息能夠幫助網(wǎng)絡(luò)中的其他設(shè)備決定是否要連接到我們所提供的服務(wù)。
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("_http._tcp.");
serviceInfo.setPort(port);
....
}
這段代碼將服務(wù)命名為“NsdChat”。該名稱(chēng)將對(duì)所有局域網(wǎng)絡(luò)中使用 NSD 查找本地服務(wù)的設(shè)備可見(jiàn)。需要注意的是,在網(wǎng)絡(luò)內(nèi)該名稱(chēng)必須是獨(dú)一無(wú)二的。Android 系統(tǒng)會(huì)自動(dòng)處理沖突的服務(wù)名稱(chēng)。如果同時(shí)有兩個(gè)名為“NsdChat”的應(yīng)用,其中一個(gè)會(huì)被自動(dòng)轉(zhuǎn)換為類(lèi)似“NsdChat(1)”這樣的名稱(chēng)。
第二個(gè)參數(shù)設(shè)置了服務(wù)類(lèi)型,即指定應(yīng)用使用的協(xié)議和傳輸層。語(yǔ)法是“_< protocol >._< transportlayer >”。在上面的代碼中,服務(wù)使用了TCP協(xié)議上的HTTP協(xié)議。想要提供打印服務(wù)(例如,一臺(tái)網(wǎng)絡(luò)打印機(jī))的應(yīng)用應(yīng)該將服務(wù)的類(lèi)型設(shè)置為“_ipp._tcp”。
Note: 互聯(lián)網(wǎng)編號(hào)分配機(jī)構(gòu)(International Assigned Numbers Authority,簡(jiǎn)稱(chēng) IANA)提供用于服務(wù)發(fā)現(xiàn)協(xié)議(例如 NSD 和 Bonjour)的官方服務(wù)種類(lèi)列表。我們可以下載該列表了解相應(yīng)的服務(wù)名稱(chēng)和端口號(hào)碼。如果我們想起用新的服務(wù)種類(lèi),應(yīng)該向 IANA 官方提交申請(qǐng)。
當(dāng)為我們的服務(wù)設(shè)置端口號(hào)時(shí),應(yīng)該盡量避免將其硬編碼在代碼中,以防止與其他應(yīng)用產(chǎn)生沖突。例如,如果我們的應(yīng)用僅僅使用端口1337,就可能與其他使用1337端口的應(yīng)用發(fā)生沖突。解決方法是,不要硬編碼,使用下一個(gè)可用的端口。不必?fù)?dān)心其他應(yīng)用無(wú)法知曉服務(wù)的端口號(hào),因?yàn)樵撔畔诜?wù)的廣播包中。接收到廣播后,其他應(yīng)用將從廣播包中得知服務(wù)端口號(hào),并通過(guò)端口連接到我們的服務(wù)上。
如果使用的是 socket,那么我們可以將端口設(shè)置為 0,來(lái)初始化 socket 到任意可用的端口。
public void initializeServerSocket() {
// Initialize a server socket on the next available port.
mServerSocket = new ServerSocket(0);
// Store the chosen port.
mLocalPort = mServerSocket.getLocalPort();
...
}
現(xiàn)在,我們已經(jīng)成功的創(chuàng)建了 NsdServiceInfo 對(duì)象,接下來(lái)要做的是實(shí)現(xiàn) RegistrationListener 接口。該接口包含了注冊(cè)在 Android 系統(tǒng)中的回調(diào)函數(shù),作用是通知應(yīng)用程序服務(wù)注冊(cè)和注銷(xiāo)的成功或者失敗。
public void initializeRegistrationListener() {
mRegistrationListener = 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.
mServiceName = 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.
}
};
}
萬(wàn)事俱備只欠東風(fēng),調(diào)用 registerService() 方法,真正注冊(cè)服務(wù)。
因?yàn)樵摲椒ㄊ钱惒降模栽诜?wù)注冊(cè)之后的操作都需要在 onServiceRegistered() 方法中進(jìn)行。
public void registerService(int port) {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setServiceName("NsdChat");
serviceInfo.setServiceType("_http._tcp.");
serviceInfo.setPort(port);
mNsdManager = Context.getSystemService(Context.NSD_SERVICE);
mNsdManager.registerService(
serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}
網(wǎng)絡(luò)充斥著我們的生活,從網(wǎng)絡(luò)打印機(jī)到網(wǎng)絡(luò)攝像頭,再到聯(lián)網(wǎng)井字棋。網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)是能讓我們的應(yīng)用融入這一切功能的關(guān)鍵。我們的應(yīng)用需要偵聽(tīng)網(wǎng)絡(luò)內(nèi)服務(wù)的廣播,發(fā)現(xiàn)可用的服務(wù),過(guò)濾無(wú)效的信息。
與注冊(cè)網(wǎng)絡(luò)服務(wù)類(lèi)似,服務(wù)發(fā)現(xiàn)需要兩步驟:用相應(yīng)的回調(diào)函數(shù)設(shè)置發(fā)現(xiàn)監(jiān)聽(tīng)器(Discover Listener),以及調(diào)用 discoverServices() 這個(gè)異步API。
首先,實(shí)例化一個(gè)實(shí)現(xiàn) NsdManager.DiscoveryListener 接口的匿名類(lèi)。下列代碼是一個(gè)簡(jiǎn)單的范例:
public void initializeDiscoveryListener() {
// Instantiate a new DiscoveryListener
mDiscoveryListener = 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(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);
} else if (service.getServiceName().contains("NsdChat")){
mNsdManager.resolveService(service, mResolveListener);
}
}
@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);
mNsdManager.stopServiceDiscovery(this);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
}
NSD API 通過(guò)使用該接口中的方法通知用戶(hù)程序發(fā)現(xiàn)何時(shí)開(kāi)始、何時(shí)失敗以及何時(shí)找到可用服務(wù)和何時(shí)服務(wù)丟失(丟失意味著“不再可用”)。在上述代碼中,當(dāng)發(fā)現(xiàn)了可用的服務(wù)時(shí),程序做了幾次檢查。
我們并不需要每次都檢查服務(wù)名稱(chēng),僅當(dāng)我們想要接入特定的應(yīng)用時(shí)需要檢查。例如,應(yīng)用只想與運(yùn)行在其他設(shè)備上的相同應(yīng)用通信。然而,如果應(yīng)用僅僅想接入到一臺(tái)網(wǎng)絡(luò)打印機(jī),那么看到服務(wù)類(lèi)型是“_ipp._tcp”的服務(wù)就足夠了。
當(dāng)配置好監(jiān)聽(tīng)器后,調(diào)用 discoverService() 函數(shù),其參數(shù)包括試圖發(fā)現(xiàn)的服務(wù)種類(lèi)、發(fā)現(xiàn)使用的協(xié)議、以及上一步創(chuàng)建的監(jiān)聽(tīng)器。
mNsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
當(dāng)我們的應(yīng)用發(fā)現(xiàn)了網(wǎng)上可接入的服務(wù),首先需要調(diào)用 resolveService() 方法,以確定服務(wù)的連接信息。實(shí)現(xiàn) NsdManager.ResolveListener 對(duì)象并將其傳入 resolveService()
方法,并使用這個(gè) NsdManager.ResolveListener
對(duì)象獲得包含連接信息的 NsdSerServiceInfo。
public void initializeResolveListener() {
mResolveListener = 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(mServiceName)) {
Log.d(TAG, "Same IP.");
return;
}
mService = serviceInfo;
int port = mService.getPort();
InetAddress host = mService.getHost();
}
};
}
當(dāng)服務(wù)解析完成后,我們將獲得服務(wù)的詳細(xì)資料,包括其 IP 地址和端口號(hào)。此時(shí),我們就可以創(chuàng)建自己網(wǎng)絡(luò)連接與服務(wù)進(jìn)行通訊。
在應(yīng)用的生命周期中正確的開(kāi)啟和關(guān)閉 NSD 服務(wù)是十分關(guān)鍵的。在程序退出時(shí)注銷(xiāo)服務(wù)可以防止其他程序因?yàn)椴恢婪?wù)退出而反復(fù)嘗試連接的行為。另外,服務(wù)發(fā)現(xiàn)是一種開(kāi)銷(xiāo)很大的操作,應(yīng)該隨著父 Activity 的暫停而停止,當(dāng)用戶(hù)返回該界面時(shí)再開(kāi)啟。因此,開(kāi)發(fā)者應(yīng)該重寫(xiě) Activity 的生命周期函數(shù),并添加按照需要開(kāi)啟和停止服務(wù)廣播和發(fā)現(xiàn)的代碼。
//In your application's Activity
@Override
protected void onPause() {
if (mNsdHelper != null) {
mNsdHelper.tearDown();
}
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (mNsdHelper != null) {
mNsdHelper.registerService(mConnection.getLocalPort());
mNsdHelper.discoverServices();
}
}
@Override
protected void onDestroy() {
mNsdHelper.tearDown();
mConnection.tearDown();
super.onDestroy();
}
// NsdHelper's tearDown method
public void tearDown() {
mNsdManager.unregisterService(mRegistrationListener);
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}