W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
編寫:naizhengtan - 原文:http://developer.android.com/training/connect-devices-wirelessly/nsd.html
添加網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)(Network Service Discovery)到我們的 app 中,可以使我們的用戶辨識在局域網(wǎng)內(nèi)支持我們的 app 所請求的服務(wù)的設(shè)備。這種技術(shù)在點對點應(yīng)用中能夠提供大量幫助,例如文件共享、聯(lián)機游戲等。Android 的網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)(NSD)API 大大降低實現(xiàn)上述功能的難度。
本講將簡要介紹如何創(chuàng)建 NSD 應(yīng)用,使其能夠在本地網(wǎng)絡(luò)內(nèi)廣播自己的名稱和連接信息,并且掃描其它正在做同樣事情的應(yīng)用信息。最后,將介紹如何連接運行著同樣應(yīng)用的另一臺設(shè)備。
Note: 這一步驟是選做的。如果我們并不關(guān)心在本地網(wǎng)絡(luò)上廣播 app 服務(wù),那么我們可以跳過這一步,直接嘗試發(fā)現(xiàn)網(wǎng)絡(luò)中的服務(wù)。
在局域網(wǎng)內(nèi)注冊自己服務(wù)的第一步是創(chuàng)建 NsdServiceInfo 對象。此對象包含的信息能夠幫助網(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”。該名稱將對所有局域網(wǎng)絡(luò)中使用 NSD 查找本地服務(wù)的設(shè)備可見。需要注意的是,在網(wǎng)絡(luò)內(nèi)該名稱必須是獨一無二的。Android 系統(tǒng)會自動處理沖突的服務(wù)名稱。如果同時有兩個名為“NsdChat”的應(yīng)用,其中一個會被自動轉(zhuǎn)換為類似“NsdChat(1)”這樣的名稱。
第二個參數(shù)設(shè)置了服務(wù)類型,即指定應(yīng)用使用的協(xié)議和傳輸層。語法是“_< protocol >._< transportlayer >”。在上面的代碼中,服務(wù)使用了TCP協(xié)議上的HTTP協(xié)議。想要提供打印服務(wù)(例如,一臺網(wǎng)絡(luò)打印機)的應(yīng)用應(yīng)該將服務(wù)的類型設(shè)置為“_ipp._tcp”。
Note: 互聯(lián)網(wǎng)編號分配機構(gòu)(International Assigned Numbers Authority,簡稱 IANA)提供用于服務(wù)發(fā)現(xiàn)協(xié)議(例如 NSD 和 Bonjour)的官方服務(wù)種類列表。我們可以下載該列表了解相應(yīng)的服務(wù)名稱和端口號碼。如果我們想起用新的服務(wù)種類,應(yīng)該向 IANA 官方提交申請。
當(dāng)為我們的服務(wù)設(shè)置端口號時,應(yīng)該盡量避免將其硬編碼在代碼中,以防止與其他應(yīng)用產(chǎn)生沖突。例如,如果我們的應(yīng)用僅僅使用端口1337,就可能與其他使用1337端口的應(yīng)用發(fā)生沖突。解決方法是,不要硬編碼,使用下一個可用的端口。不必?fù)?dān)心其他應(yīng)用無法知曉服務(wù)的端口號,因為該信息將包含在服務(wù)的廣播包中。接收到廣播后,其他應(yīng)用將從廣播包中得知服務(wù)端口號,并通過端口連接到我們的服務(wù)上。
如果使用的是 socket,那么我們可以將端口設(shè)置為 0,來初始化 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 對象,接下來要做的是實現(xiàn) RegistrationListener 接口。該接口包含了注冊在 Android 系統(tǒng)中的回調(diào)函數(shù),作用是通知應(yīng)用程序服務(wù)注冊和注銷的成功或者失敗。
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.
}
};
}
萬事俱備只欠東風(fēng),調(diào)用 registerService() 方法,真正注冊服務(wù)。
因為該方法是異步的,所以在服務(wù)注冊之后的操作都需要在 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ò)打印機到網(wǎng)絡(luò)攝像頭,再到聯(lián)網(wǎng)井字棋。網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)是能讓我們的應(yīng)用融入這一切功能的關(guān)鍵。我們的應(yīng)用需要偵聽網(wǎng)絡(luò)內(nèi)服務(wù)的廣播,發(fā)現(xiàn)可用的服務(wù),過濾無效的信息。
與注冊網(wǎng)絡(luò)服務(wù)類似,服務(wù)發(fā)現(xiàn)需要兩步驟:用相應(yīng)的回調(diào)函數(shù)設(shè)置發(fā)現(xiàn)監(jiān)聽器(Discover Listener),以及調(diào)用 discoverServices() 這個異步API。
首先,實例化一個實現(xiàn) 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 通過使用該接口中的方法通知用戶程序發(fā)現(xiàn)何時開始、何時失敗以及何時找到可用服務(wù)和何時服務(wù)丟失(丟失意味著“不再可用”)。在上述代碼中,當(dāng)發(fā)現(xiàn)了可用的服務(wù)時,程序做了幾次檢查。
我們并不需要每次都檢查服務(wù)名稱,僅當(dāng)我們想要接入特定的應(yīng)用時需要檢查。例如,應(yīng)用只想與運行在其他設(shè)備上的相同應(yīng)用通信。然而,如果應(yīng)用僅僅想接入到一臺網(wǎng)絡(luò)打印機,那么看到服務(wù)類型是“_ipp._tcp”的服務(wù)就足夠了。
當(dāng)配置好監(jiān)聽器后,調(diào)用 discoverService() 函數(shù),其參數(shù)包括試圖發(fā)現(xiàn)的服務(wù)種類、發(fā)現(xiàn)使用的協(xié)議、以及上一步創(chuàng)建的監(jiān)聽器。
mNsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
當(dāng)我們的應(yīng)用發(fā)現(xiàn)了網(wǎng)上可接入的服務(wù),首先需要調(diào)用 resolveService() 方法,以確定服務(wù)的連接信息。實現(xiàn) NsdManager.ResolveListener 對象并將其傳入 resolveService()
方法,并使用這個 NsdManager.ResolveListener
對象獲得包含連接信息的 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 地址和端口號。此時,我們就可以創(chuàng)建自己網(wǎng)絡(luò)連接與服務(wù)進(jìn)行通訊。
在應(yīng)用的生命周期中正確的開啟和關(guān)閉 NSD 服務(wù)是十分關(guān)鍵的。在程序退出時注銷服務(wù)可以防止其他程序因為不知道服務(wù)退出而反復(fù)嘗試連接的行為。另外,服務(wù)發(fā)現(xiàn)是一種開銷很大的操作,應(yīng)該隨著父 Activity 的暫停而停止,當(dāng)用戶返回該界面時再開啟。因此,開發(fā)者應(yīng)該重寫 Activity 的生命周期函數(shù),并添加按照需要開啟和停止服務(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);
}
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: