標籤:
原文地址:http://android.xsoftlab.net/training/connect-devices-wirelessly/index.html
引言
Android裝置除了可以與伺服器建立串連之外,Android無線API還允許處於同一網段下的兩台裝置建立串連,或者是物理距離相近的兩台裝置建立串連。Network Service Discovery (NSD)允許應用程式通過掃描來搜尋附近可串連的裝置。當然被掃描的裝置也同樣需要開啟該服務。利用這項特性可以使應用程式擁有更強大的功能,比如和同伴在一個房間內玩遊戲,或者從有NSD服務的網路攝影機上下載照片,或者遠程操控屋內的其它裝置(智能家電的基礎)。
這節課將學習如何搜尋其它裝置並與之建立串連。需要特別說明的是,這些關鍵的API描述了如何使用NSD API來搜尋可用的服務及如何利用Wi-Fi Peer-to-Peer (P2P) API來建立P2P方式的無線串連。這節課還會學習如何使用NSD與WIFI P2P結合的方式來搜尋服務提供者。
使用網路搜尋服務
使用Network Service Discovery (NSD)服務可以使應用程式搜尋處於同一本網下的其它支援服務需求的裝置。這對多樣的P2P應用程式極為有用,比如可以利用這項技術共用檔案,或者玩多人遊戲。Android的NSD API可以使應用程式快速實現這種功能。
這節課會學習如何構建一個應用程式,這個應用程式需要可以廣播程式的名稱及串連到本網的串連資訊及掃描上述資訊的功能。最後,這節課會學習如何去其它裝置建立串連。
將服務註冊到網路
Note: 這節課是可選的,如果不需要將應用的服務廣播到本網,則可以直接跳到下一段。
為了將服務註冊到本網上,首先需要建立一個NsdServiceInfo對象,這個對象提供了使其它裝置決定是否要串連的資訊。
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); ....}
上面的代碼將服務的名稱設定為”NsdChat”。這個名稱對所有使用了USD服務的裝置可見。要記住一點就是這個名稱在這個網路環境中必須是唯一的,Android會自動處理這種名稱衝突問題。如果在同一環境下有兩個名稱為”NsdChat”的裝置,那麼Android會自動將其中一個命名為”NsdChat (1)”.
第二個參數設定了該服務的類型,指定了所使用的傳輸協議與運輸層。這個定義文法是”.”.在上面的代碼中,該服務使用的是運行在TCP上的HTTP協議。如果一個程式提供的是列印服務,那麼它的服務類型應該是”_ipp._tcp”.
Note: 這些搜尋服務合約比如NSD、Bonjour由International Assigned Numbers Authority (IANA)互連網地址編碼分配機構統一管理。你可以通過the IANA list of service names and port numbers下載服務類型列表。如果計算使用一種新的服務類型,應當在IANA Ports and Service registration form上進行備案。
當為服務設定連接埠時,應避免將連接埠寫死,因為這個連接埠可能會與其他連接埠衝突。假設,應用程式一直在使用連接埠1337,則會與其它也使用了相同連接埠的應用程式造成連接埠衝突,使用裝置的下一個可用連接埠則可以避免這種衝突。因為這個資訊是由其它應用通過廣播提供的,所以在編譯時間不需要知道其它應用使用的是哪個介面。相反的,應用程式會在串連到裝置之前通過廣播獲得連接埠的資訊。
如果使用Socket進行通訊,下面的代碼展示了如何初始化一個Socket,並將其連接埠設定為萬能的連接埠0.
public void initializeServerSocket() { // Initialize a server socket on the next available port. mServerSocket = new ServerSocket(0); // Store the chosen port. mLocalPort = mServerSocket.getLocalPort(); ...}
那麼現在已經定義了NsdServiceInfo對象,接下來還需實現RegistrationListener介面。這個介面用於通知應用程式服務的註冊於解注是成功還是失敗。
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. } };}
那麼現在已經完成了所有的前期準備工作,調用registerService()方法完成服務註冊。
注意這個方法是非同步方法呼叫,所以任何在服務完成註冊之後啟動並執行代碼必須放在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);}
搜尋網路上的服務
網路的世界充滿了各種生命,從狂野的網路印表機到溫柔的網路攝影機,再到激烈運動的體感遊戲機,無不是一種生命。可以使應用程式能夠看到這些各式各樣的生命體的核心是服務搜尋功能。應用程式只需要監聽網路上的服務廣播就可以判斷哪些服務是可用的,哪些是不需要的。
服務搜尋功能與服務的註冊很類似,也有兩個步驟:設定相應的搜尋監聽器,調用discoverServices()方法。
首先,執行個體化一個實現了NsdManager.DiscoveryListener介面的匿名類:
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會在這些情況下回調這些方法:搜尋服務啟動時,搜尋服務失敗時,服務未找到時,服務丟失時。注意在服務找到時會有以下若干項檢查:
- 1.會對找到的服務名稱與本身服務的名稱相比較,來判斷是否是自己發出的廣播。
- 2.服務類型檢查,驗證找到的服務是否可以串連。
- 3.服務名稱檢查,驗證是否串連到了正確的程式上。
服務名稱的檢查並不是必須的,只有在串連到指定的程式上才會這一步。比如,應用程式只是希望串連其它裝置上的相同應用程式。無論如何,如果程式希望串連到網路印表機上時,只需要判斷其服務類型是否是”_ipp._tcp”就足夠了。
在設定完監聽器之後,調用discoverServices(),然後傳入程式要監聽的服務類型,要使用的搜尋協議,及剛剛建立的監聽器。
mNsdManager.discoverServices( SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
串連到網路服務
當應用程式找到網路上的服務並試圖與之建立串連時,必須先通過resolveService()方法檢查該服務的串連資訊。實現NsdManager.ResolveListener介面,然後將其傳入方法resolveService()中,然後通過這個介面的回調方法獲得一個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(); } };}
一旦解析完成,那麼程式就會獲得包括IP地址與連接埠號碼在內的詳細服務資訊。這為串連到該服務的網路提供了協助。
登出服務
在程式的生命週期內可以適當關閉NSD功能是很重要的。在程式關閉時登出服務有助於防止其它程式認為該程式還處於存活狀態,並試圖與其建立串連。另外,服務搜尋功能的代價很高,所以在Activity停止時應當關閉服務,在Activity重新啟動時再次開啟。應用程式應當重寫對應的Activity的生命週期回調方法,並在這些方法中插入適當的代碼來啟動或者關閉服務廣播與服務搜尋功能。
//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); }
Android官方開發文檔Training系列課程中文版:串連無線裝置之網路服務搜尋功能