注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術(shù)一般,由于喜愛(ài)安卓而產(chǎn)生了翻譯的念頭,純屬個(gè)人興趣愛(ài)好。
原文鏈接: http://developer.android.com/training/connect-devices-wirelessly/nsd.html
將網(wǎng)絡(luò)服務(wù)搜索(NSD)添加至你的應(yīng)用允許你的用戶識(shí)別在本地網(wǎng)絡(luò)的其它設(shè)備,這些設(shè)備提供了你的應(yīng)用所需要的服務(wù)。這一特性對(duì)很多P2P類應(yīng)用非常有用,如:文件共享,多人游戲等。Android的NSD API簡(jiǎn)化了你實(shí)現(xiàn)該功能特性所需要做的事情。
這節(jié)課將展示如何構(gòu)建一個(gè)應(yīng)用,它可以將其名字和 連接 信息廣播至所在的本地網(wǎng)絡(luò),并搜索其它正在做同樣事情的應(yīng)用。最終,這節(jié)課將展示如何連接運(yùn)行在另一設(shè)備上的應(yīng)用。
一). 在網(wǎng)絡(luò)上注冊(cè)你的服務(wù)
Note:
這一步是可選的,如果你對(duì)于將你的應(yīng)用服務(wù)廣播至本地網(wǎng)絡(luò)并不關(guān)心,你可以跳至下一節(jié)。
為了在本地網(wǎng)絡(luò)注冊(cè)你的服務(wù),首先創(chuàng)建一個(gè) NsdServiceInfo 對(duì)象。這個(gè)對(duì)象提供了網(wǎng)絡(luò)上其它設(shè)備在決定是否要鏈接到你提供的服務(wù)時(shí),所要使用到的信息。
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ù)名設(shè)置為“NsdChat”。這個(gè)名字對(duì)于任何在網(wǎng)絡(luò)上,并正在使用NSD尋找本地服務(wù)的設(shè)備都是可見(jiàn)的。記住這個(gè)名字必須是在網(wǎng)絡(luò)上是唯一的,不過(guò)Android會(huì)自動(dòng)解決名字沖突的問(wèn)題。如果兩個(gè)網(wǎng)絡(luò)上的設(shè)備都安裝了NsdChat應(yīng)用,他們中的一個(gè)會(huì)自動(dòng)改變服務(wù)名稱,如:“ NsdChat(1) ”。
第二個(gè)變量設(shè)置了服務(wù)類型,指定了傳輸層和應(yīng)用層所使用的協(xié)議。語(yǔ)法的格式是:“_<應(yīng)用層協(xié)議>._<傳輸層協(xié)議>”,在代碼片段中,應(yīng)用使用了運(yùn)行于TCP之上的HTTP。如果一個(gè)應(yīng)用提供了打印服務(wù)(例如,一個(gè)網(wǎng)絡(luò)打印機(jī))那么會(huì)將服務(wù)類型設(shè)置為“_ipp.tcp”。
Note:
國(guó)際號(hào)碼分配機(jī)構(gòu)(IANA)管理了一個(gè)集中、權(quán)威的服務(wù)類型列表,這一列表被服務(wù)搜索協(xié)議,如NSD和Bonjour。你可以 下載 這個(gè)列表。如果你希望使用一個(gè)新的服務(wù)類型,你需要填寫 IANA端口和服務(wù)注冊(cè)申請(qǐng)表 中維護(hù)它。
當(dāng)配置了你的服務(wù)的端口,避免對(duì)它進(jìn)行硬編碼因?yàn)檫@可能會(huì)和其它應(yīng)用產(chǎn)生沖突。例如,假設(shè)你的應(yīng)用一直使用1337端口,那么將會(huì)有潛在的可能性與其它使用相同端口的已安裝應(yīng)用產(chǎn)生沖突。解決方案是使用設(shè)備的下一個(gè)可使用端口。因?yàn)檫@一信息會(huì)通過(guò)一個(gè)服務(wù)廣播提供給其它應(yīng)用,在編譯期間,你的應(yīng)用所使用的端口無(wú)需被其它應(yīng)用知道。應(yīng)用可以可以通過(guò)服務(wù)廣播在連接到你的服務(wù)之前來(lái)得到這一信息。
如果你使用套接字鏈接,下面展示了你應(yīng)該如何初始化一個(gè)套接字鏈接到任一可以獲得的端口,方法是簡(jiǎn)單地設(shè)置它為0。
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)定義了 NsdServiceInfo 對(duì)象,你需要實(shí)現(xiàn) RegistrationListener 接口。這個(gè)接口包括了Android系統(tǒng)所使用的回調(diào)函數(shù),以此告知你的應(yīng)用服務(wù)的注冊(cè)和注銷是否成功。
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. } }; }
現(xiàn)在你具有了注冊(cè)服務(wù)所需要的代碼了。調(diào)用方法: registerService() 。
注意到這個(gè)方法是異步的,所以任何需要運(yùn)行的代碼,必須在服務(wù)注冊(cè)后運(yùn)行。這些代碼寫在 onServiceRegistered() 方法中。
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ò)上發(fā)現(xiàn)服務(wù)
網(wǎng)絡(luò)讓生活變得豐富,從網(wǎng)絡(luò)打印機(jī)到網(wǎng)絡(luò)相機(jī)。讓你的應(yīng)用能夠看見(jiàn)這一充滿活力的生態(tài)系統(tǒng)的關(guān)鍵是服務(wù)搜索。你的應(yīng)用需要再網(wǎng)絡(luò)上監(jiān)聽服務(wù)來(lái)查詢當(dāng)前能夠獲得哪些服務(wù),并且排除任何應(yīng)用無(wú)法實(shí)現(xiàn)的服務(wù)。
服務(wù)搜索,和服務(wù)注冊(cè)類似,有兩步要做:為相關(guān)的回調(diào)函數(shù)配置一個(gè)搜索監(jiān)聽器,為 discoverServices() 執(zhí)行一次異步調(diào)用。
首先,實(shí)例化一個(gè)匿名類,它實(shí)現(xiàn)了 NsdManager.DiscoveryListener 。下面的代碼片段為一個(gè)例子:
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使用這個(gè)接口的方法來(lái)通知你的應(yīng)用何時(shí)開始搜索,何時(shí)失敗,或者何時(shí)服務(wù)發(fā)現(xiàn)或丟失了(丟失即無(wú)法獲取服務(wù))。注意上述代碼樣例在一個(gè)服務(wù)搜索到了以后執(zhí)行了一些檢查。
- 找到的服務(wù)的服務(wù)名字,會(huì)和本地服務(wù)的服務(wù)名字進(jìn)行匹配,來(lái)確定設(shè)備是否接受到了它自己發(fā)出去的廣播。
- 檢查服務(wù)類型,來(lái)檢查你的應(yīng)用是否能夠連接此類型的服務(wù)。
- 檢查服務(wù)名字來(lái)確認(rèn)是否連接到了正確的應(yīng)用。
檢查服務(wù)名字并不總是必須的,如果你希望連接到某一個(gè)確切的應(yīng)用的話才會(huì)需要。例如,這個(gè)應(yīng)用可能可能只希望連接到在其他設(shè)備上運(yùn)行的其自身的實(shí)例。然而,如果應(yīng)用希望連接到一個(gè)網(wǎng)絡(luò)打印機(jī),那么吧服務(wù)類型設(shè)置為“ _ipp._tcp ”就夠了。
在設(shè)置了監(jiān)聽器之后,調(diào)用 discoverServices() ,將你的應(yīng)用要查找的服務(wù)類型,使用的搜索協(xié)議,和你剛創(chuàng)建過(guò)的監(jiān)聽器作為參數(shù)傳入。
mNsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
三). 連接網(wǎng)絡(luò)上的服務(wù)
當(dāng)你的應(yīng)用發(fā)現(xiàn)了一個(gè)網(wǎng)絡(luò)上要連接的服務(wù),它必須首先使用 resolveService() 方法來(lái)確定該服務(wù)的連接信息。實(shí)現(xiàn)一個(gè) NsdManager.ResolveListener ,并傳遞給該方法,并用它來(lái)獲得一個(gè)包含連接信息的 NsdServiceInfo 。
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(); } }; }
一旦服務(wù)解析完成了,你的應(yīng)用會(huì)收到詳細(xì)的服務(wù)信息,包括一個(gè)IP地址和一個(gè)端口號(hào)。這些就是用來(lái)和服務(wù)創(chuàng)建連接所需要的所有東西。
四). 應(yīng)用關(guān)閉時(shí)注銷你的服務(wù)
在應(yīng)用的生命周期恰當(dāng)?shù)貑⒂没蚪肗SD功能是很重要的。在應(yīng)用關(guān)閉時(shí)注銷服務(wù)可以阻止其它應(yīng)用誤認(rèn)為你的服務(wù)仍然是啟用狀態(tài)并嘗試連接。同時(shí),服務(wù)搜索是一個(gè)很消耗資源的操作,應(yīng)該在父Activity被暫停時(shí)停止搜索,而在父Activity恢復(fù)時(shí)重新啟用。覆寫你的Activity生命周期函數(shù),并插入代碼來(lái)啟動(dòng)或停止服務(wù)廣播,并在恰當(dāng)?shù)貢r(shí)機(jī)執(zhí)行搜索。
// 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); }
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
