Android平臺(tái)支持藍(lán)牙網(wǎng)絡(luò)協(xié)議棧,實(shí)現(xiàn)藍(lán)牙設(shè)備之間數(shù)據(jù)的無(wú)線(xiàn)傳輸。本文檔描述了怎樣利用android平臺(tái)提供的藍(lán)牙
API去實(shí)現(xiàn)藍(lán)壓設(shè)備之間的通信。藍(lán)牙具有point-to-point 和 multipoint兩種連接功能。 使用藍(lán)牙API,可以做到: * 搜索藍(lán)
牙設(shè)備 * 從本地的Bluetooth adapter中查詢(xún)已經(jīng)配對(duì)的設(shè)備 * 建立RFCOMM通道 * 通過(guò)service discovery連接到其它設(shè)備
* 在設(shè)備之間傳輸數(shù)據(jù) * 管理多個(gè)連接
基礎(chǔ)知識(shí)
本文檔介紹了如何使用Android的藍(lán)牙API來(lái)完成的四個(gè)必要的主要任務(wù),使用藍(lán)牙進(jìn)行設(shè)備通信,主要包含四個(gè)部分:藍(lán)
牙設(shè)置、搜索設(shè)備(配對(duì)的或可見(jiàn)的)、連接、傳輸數(shù)據(jù)。 所有的藍(lán)牙API在android.bluetooth包中。實(shí)現(xiàn)這些功能主要
需要下面這?個(gè)類(lèi)和接?:
BluetoothAdapter 代表本地藍(lán)牙適配器(藍(lán)牙發(fā)射器),是所有藍(lán)牙交互的??。通過(guò)它可以搜索其它藍(lán)牙設(shè)備,查詢(xún)已
經(jīng)配對(duì)的設(shè)備列表,通過(guò)已知的MAC地址創(chuàng)建BluetoothDevice,創(chuàng)建BluetoothServerSocket監(jiān)聽(tīng)來(lái)自其它設(shè)備的通信。
BluetoothDevice 代表了?個(gè)遠(yuǎn)端的藍(lán)牙設(shè)備, 使用它請(qǐng)求遠(yuǎn)端藍(lán)牙設(shè)備連接或者獲取 遠(yuǎn)端藍(lán)牙設(shè)備的名稱(chēng)、地址、種類(lèi)
和綁定狀態(tài)。 (其信息是封裝在 bluetoothsocket 中) 。
BluetoothSocket 代表了?個(gè)藍(lán)牙套接字的接?(類(lèi)似于 tcp 中的套接字) ,他是應(yīng)用程 序通過(guò)輸?、輸出流與其他藍(lán)牙
設(shè)備通信的連接點(diǎn)。
BluetoothServerSocket 代表打開(kāi)服務(wù)連接來(lái)監(jiān)聽(tīng)可能到來(lái)的連接請(qǐng)求 (屬于 server 端) , 為了連接兩個(gè)藍(lán)牙設(shè)備必須
有?個(gè)設(shè)備作為服務(wù)器打開(kāi)?個(gè)服務(wù)套接字。 當(dāng)遠(yuǎn)端設(shè)備發(fā)起連 接連接請(qǐng)求的時(shí)候,并且已經(jīng)連接到了的時(shí)
候,Blueboothserversocket 類(lèi)將會(huì)返回?個(gè) bluetoothsocket。
BluetoothClass 描述了?個(gè)設(shè)備的特性(profile)或該設(shè)備上的藍(lán)牙?致可以提供哪些服務(wù)(service),但不可信。比如,
設(shè)備是?個(gè)電話(huà)、計(jì)算機(jī)或手持設(shè)備;設(shè)備可以提供audio/telephony服務(wù)等。可以用它來(lái)進(jìn)行?些UI上的提示。
BluetoothProfile
BluetoothHeadset 提供手機(jī)使用藍(lán)牙耳機(jī)的支持。這既包括藍(lán)牙耳機(jī)和免提(V1.5)模式。
BluetoothA2dp 定義高品質(zhì)的音頻,可以從?個(gè)設(shè)備傳輸?shù)搅?個(gè)藍(lán)牙連接。 “A2DP的”代表高級(jí)音頻分配模式。
BluetoothHealth 代表了醫(yī)療設(shè)備配置代理控制的藍(lán)牙服務(wù)
BluetoothHealthCallback ?個(gè)抽象類(lèi),使用實(shí)現(xiàn)BluetoothHealth回調(diào)。你必須擴(kuò)展這個(gè)類(lèi)并實(shí)現(xiàn)回調(diào)方法接收更新應(yīng)用
程序的注冊(cè)狀態(tài)和藍(lán)牙通道狀態(tài)的變化。
BluetoothHealthAppConfiguration 代表?個(gè)應(yīng)用程序的配置,藍(lán)牙醫(yī)療第三方應(yīng)用注冊(cè)與遠(yuǎn)程藍(lán)牙醫(yī)療設(shè)備交流。
BluetoothProfile.ServiceListener 當(dāng)他們已經(jīng)連接到或從服務(wù)斷開(kāi)時(shí)通知BluetoothProfile IPX的客戶(hù)時(shí)?個(gè)接?(即運(yùn)行
?個(gè)特定的配置文件,內(nèi)部服務(wù))。
藍(lán)牙權(quán)限
為了在你的應(yīng)用中使用藍(lán)牙功能,至少要在AndroidManifest.xml中聲明兩個(gè)權(quán)限:BLUETOOTH(任何藍(lán)牙相關(guān)API都要
使用這個(gè)權(quán)限) 和 BLUETOOTH_ADMIN(設(shè)備搜索、藍(lán)牙設(shè)置等)。
為了執(zhí)行藍(lán)牙通信,例如連接請(qǐng)求,接收連接和傳送數(shù)據(jù)都必須有BLUETOOTH權(quán)限。
必須要求BLUETOOTH_ADMIN的權(quán)限來(lái)啟動(dòng)設(shè)備發(fā)現(xiàn)或操縱藍(lán)牙設(shè)置。?多數(shù)應(yīng)用程序都需要這個(gè)權(quán)限能?,發(fā)現(xiàn)當(dāng)?shù)?/p>
的藍(lán)牙設(shè)備。此權(quán)限授予其他的能?不應(yīng)該使用,除非應(yīng)用程序是?個(gè)“電源管理”,將根據(jù)用戶(hù)要求修改的藍(lán)牙設(shè)置
注釋?zhuān)阂?qǐng)求BLUETOOTH_ADMIN的話(huà),必須要先有BLUETOOTH。
在你的應(yīng)用manifest 文件中聲明藍(lán)牙權(quán)限。例如:
<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
...
</manifest>
通過(guò)查看<uses-permission>資料來(lái)聲明應(yīng)用權(quán)限獲取更多的信息。
藍(lán)牙設(shè)置
在你的應(yīng)用通過(guò)藍(lán)牙進(jìn)行通信之前,你需要確認(rèn)設(shè)備是否支持藍(lán)牙,如果支持,確信它被打開(kāi)。
如果不支持,則不能使用藍(lán)牙功能。如果支持藍(lán)牙,但不能夠使用,你剛要在你的應(yīng)用中請(qǐng)求使用藍(lán)牙。這個(gè)要兩步完
成,使用BluetoothAdapter。
1.獲取BluetoothAdapter
所有的藍(lán)牙活動(dòng)請(qǐng)求BluetoothAdapter,為了獲取BluetoothAdapter,呼叫靜態(tài)方法getDefaultAdapter() 。這個(gè)會(huì)返回?
個(gè)BluetoothAdapter,代表設(shè)備自?的藍(lán)牙適配器(藍(lán)牙無(wú)線(xiàn)電)。這個(gè)藍(lán)牙適配器應(yīng)用于整個(gè)系統(tǒng)中,你的應(yīng)用可以通
過(guò)這個(gè)對(duì)象進(jìn)行交互。如果getDefaultAdapter()返回null,則這個(gè)設(shè)備不支持藍(lán)牙。例如:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}
2.打開(kāi)藍(lán)牙
其次。你需要確定藍(lán)牙能夠使用。通過(guò)isEnabled()來(lái)檢查藍(lán)牙當(dāng)前是否可用。如果這個(gè)方法返回false,則藍(lán)牙不能夠使
用。為了請(qǐng)求藍(lán)牙使用,呼叫startActivityForResult()與的ACTION_REQUEST_ENABLE動(dòng)作意圖。通過(guò)系統(tǒng)設(shè)置中啟
用藍(lán)牙將發(fā)出?個(gè)請(qǐng)求(不停止藍(lán)牙應(yīng)用)。例如:
if (mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
![http://developer.android.com/images/bt_enable_request.png]
對(duì)話(huà)框中顯示請(qǐng)求使用藍(lán)牙權(quán)限。如果響應(yīng)"Yes",這個(gè)進(jìn)程完成(或失敗)后你的應(yīng)用將能夠使用藍(lán)牙。
REQUEST_ENABLE_BT常量作為?個(gè)整型傳到startActivityForResult()中(值必須?于0),該系統(tǒng)傳回給你,在你
onActivityResult()作為實(shí)現(xiàn)的requestCode參數(shù)。
如果調(diào)用藍(lán)牙成功,你的Activity就會(huì)在onActivityResult()中收到RESULT_OK結(jié)果,如果藍(lán)牙不能使用由于錯(cuò)誤(或用戶(hù)
響應(yīng)“NO”那么結(jié)果返回RESULT_CANCELED。
除了通過(guò)onActivityResult(),還可以通過(guò)監(jiān)聽(tīng)ACTION_STATE_CHANGED這個(gè)broadcast Intent來(lái)知道藍(lán)牙狀態(tài)是否改
變。這個(gè)Intent包含EXTRA_STATE,EXTRA_PREVIOUS_STATE兩個(gè)字段,分別代表新舊狀態(tài)。可能的值是
STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 還有STATE_OFF。
?貼: Enabling discoverability 將自動(dòng)啟用藍(lán)牙。如果您計(jì)劃執(zhí)行藍(lán)牙活動(dòng)之前,始終使設(shè)備可發(fā)現(xiàn),你可以跳過(guò)上面的
步驟2。參閱enabling discoverability。
搜索設(shè)備
使用BluetoothAdapter可以通過(guò)設(shè)備搜索或查詢(xún)配對(duì)設(shè)備找到遠(yuǎn)程Bluetooth設(shè)備。
Device discovery(設(shè)備搜索)是?個(gè)掃描搜索本地已使能Bluetooth設(shè)備并且從搜索到的設(shè)備請(qǐng)求?些信息的過(guò)程(有時(shí)
候會(huì)收到類(lèi)似“discovering”,“inquiring”或“scanning”)。但是,搜索到的本地Bluetooth設(shè)備只有在打開(kāi)被發(fā)現(xiàn)功能后才
會(huì)響應(yīng)?個(gè)discovery請(qǐng)求,響應(yīng)的信息包括設(shè)備名,類(lèi),唯?的MAC地址。發(fā)起搜尋的設(shè)備可以使用這些信息來(lái)初始化
跟被發(fā)現(xiàn)的設(shè)備的連接。 ?旦與遠(yuǎn)程設(shè)備的第?次連接被建立,?個(gè)pairing請(qǐng)求就會(huì)自動(dòng)提交給用戶(hù)。如果設(shè)備已配對(duì),
配對(duì)設(shè)備的基本信息(名稱(chēng),類(lèi),MAC地址)就被保存下來(lái)了,能夠使用Bluetooth API來(lái)讀取這些信息。使用已知的遠(yuǎn)程
設(shè)備的MAC地址,連接可以在任何時(shí)候初始化而不必先完成搜索(當(dāng)然這是假設(shè)遠(yuǎn)程設(shè)備是在可連接的空間范圍內(nèi))。
需要記住,配對(duì)和連接是兩個(gè)不同的概念:
配對(duì)意思是兩個(gè)設(shè)備相互意識(shí)到對(duì)方的存在,共享?個(gè)用來(lái)鑒別身份的鏈路鍵(link-key),能夠與對(duì)方建立?個(gè)加密的連
接。
連接意思是兩個(gè)設(shè)備現(xiàn)在共享?個(gè)RFCOMM信道,能夠相互傳輸數(shù)據(jù)。
目前Android Bluetooth API's要求設(shè)備在建立RFCOMM信道前必須配對(duì)(配對(duì)是在使用Bluetooth API初始化?個(gè)加密連接
時(shí)自動(dòng)完成的)。
下面描述如何查詢(xún)已配對(duì)設(shè)備,搜索新設(shè)備。
注意:Android的電源設(shè)備默認(rèn)是不能被發(fā)現(xiàn)的。用戶(hù)可以通過(guò)系統(tǒng)設(shè)置讓它在有限的時(shí)間內(nèi)可以被發(fā)現(xiàn),或者可以在應(yīng)
用程序中要求用戶(hù)使能被發(fā)現(xiàn)功能。
查找匹配設(shè)備
在搜索設(shè)備前,查詢(xún)配對(duì)設(shè)備看需要的設(shè)備是否已經(jīng)是已經(jīng)存在是很值得的,可以調(diào)用getBondedDevices()來(lái)做到,該函
數(shù)會(huì)返回?個(gè)描述配對(duì)設(shè)備BluetoothDevice的結(jié)果集。例如,可以使用ArrayAdapter查詢(xún)所有配對(duì)設(shè)備然后顯示所有設(shè)備
名給用戶(hù):
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
};
BluetoothDevice對(duì)象中需要用來(lái)初始化?個(gè)連接唯?需要用到的信息就是MAC地址。
掃描設(shè)備
要開(kāi)始搜索設(shè)備,只需簡(jiǎn)單的調(diào)用startDiscovery() 。該函數(shù)時(shí)異步的,調(diào)用后立即返回,返回值表示搜索是否成功開(kāi)始。
搜索處理通常包括?個(gè)12秒鐘的查詢(xún)掃描,然后跟隨?個(gè)頁(yè)面顯示搜索到設(shè)備Bluetooth名稱(chēng)。
應(yīng)用中可以注冊(cè)?個(gè)帶ACTION_FOUND Intent的BroadcastReceiver,搜索到每?個(gè)設(shè)備時(shí)都接收到消息。對(duì)于每?個(gè)設(shè)
備,系統(tǒng)都會(huì)廣播ACTION_FOUND Intent,該Intent攜帶著而外的字段信息EXTRA_DEVICE和EXTRA_CLASS,分別包
含?個(gè)BluetoothDevice和?個(gè)BluetoothClass。
下面的示例顯示如何注冊(cè)和處理設(shè)備被發(fā)現(xiàn)后發(fā)出的廣播:
代碼如下:
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
警告:完成設(shè)備搜索對(duì)于Bluetooth適配器來(lái)說(shuō)是?個(gè)重量級(jí)的處理,要消耗?量它的資源。?旦你已經(jīng)找到?個(gè)設(shè)備來(lái)連
接,請(qǐng)確保你在嘗試連接前使用了cancelDiscovery()來(lái)停止搜索。同樣,如果已經(jīng)保持了?個(gè)連接的時(shí)候,同時(shí)執(zhí)行搜索
設(shè)備將會(huì)顯著的降低連接的帶寬,所以在連接的時(shí)候不應(yīng)該執(zhí)行搜索發(fā)現(xiàn)。
使能被發(fā)現(xiàn)
如果想讓本地設(shè)備被其他設(shè)備發(fā)現(xiàn),可以帶ACTION_REQUEST_DISCOVERABLE action Intent調(diào)用
startActivityForResult(Intent, int) 方法。該方法會(huì)提交?個(gè)請(qǐng)求通過(guò)系統(tǒng)剛設(shè)置使設(shè)備出于可以被發(fā)現(xiàn)的模式(而不影響
應(yīng)用程序)。默認(rèn)情況下,設(shè)備在120秒后變?yōu)榭梢员话l(fā)現(xiàn)的。可以通過(guò)額外增加EXTRA_DISCOVERABLE_DURATION
Intent自定義?個(gè)值,最?值是3600秒,0表示設(shè)備總是可以被發(fā)現(xiàn)的(?于0或者?于3600則會(huì)被自動(dòng)設(shè)置為120秒)。
下面示例設(shè)置時(shí)間為300:
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
! [http://developer.android.com/images/bt_enable_discoverable.png]
詢(xún)問(wèn)用戶(hù)是否允許打開(kāi)設(shè)備可以被發(fā)現(xiàn)功能時(shí)會(huì)顯示?個(gè)對(duì)話(huà)框。如果用戶(hù)選擇“Yes”,設(shè)備會(huì)在指定時(shí)間過(guò)后變?yōu)榭梢员?/p>
發(fā)現(xiàn)的。Activity的onActivityResult()回調(diào)函數(shù)被調(diào)用,結(jié)果碼等于設(shè)備變?yōu)榭梢员话l(fā)現(xiàn)所需時(shí)長(zhǎng)。如果用戶(hù)選擇“No”或者
有錯(cuò)誤發(fā)生,結(jié)果碼會(huì)是Activity.RESULT_CANCELLED。
提示:如果Bluetooth沒(méi)有啟用,啟用Bluetooth可被發(fā)現(xiàn)功能能夠自動(dòng)開(kāi)啟Bluetooth。
在規(guī)定的時(shí)間內(nèi),設(shè)備會(huì)靜靜的保持可以被發(fā)現(xiàn)模式。如果想在可以被發(fā)現(xiàn)模式被更改時(shí)受到通知,可以用
ACTION_SCAN_MODE_CHANGED Intent注冊(cè)?個(gè)BroadcastReceiver,包含額外的字段信息EXTRA_SCAN_MODE和
EXTRA_PREVIOUS_SCAN_MODE分別表示新舊掃描模式,其可能的值為
SCAN_MODE_CONNECTABLE_DISCOVERABLE(discoverable mode),SCAN_MODE_CONNECTABLE(not in
discoverable mode but still able to receive connections),SCAN_MODE_NONE(not in discoverable mode and
unable to receive connections)。 如果只需要連接遠(yuǎn)程設(shè)備就不需要打開(kāi)設(shè)備的可以被發(fā)現(xiàn)功能。只在應(yīng)用作為?個(gè)服
務(wù)器socket的宿主用來(lái)接收進(jìn)來(lái)的連接時(shí)才需要使能可以被發(fā)現(xiàn)功能,因?yàn)檫h(yuǎn)程設(shè)備在初始化連接前必須先發(fā)現(xiàn)了你的設(shè)
備。
連接設(shè)備
為了在兩臺(tái)設(shè)備上創(chuàng)建?個(gè)連接,你必須在軟件上實(shí)現(xiàn)服務(wù)器端和客戶(hù)端的機(jī)制,因?yàn)?個(gè)設(shè)備必須必須打開(kāi)?個(gè)server
socket,而另?個(gè)必須初始化這個(gè)連接(使用服務(wù)器端設(shè)備的MAC地址進(jìn)行初始化)。 當(dāng)服務(wù)器端和客戶(hù)端在同?個(gè)
RFCOMM信道上都有?個(gè)BluetoothSocket時(shí),就可以認(rèn)為它們之間建立了?個(gè)連接。在這個(gè)時(shí)刻,每個(gè)設(shè)備能獲得?個(gè)
輸出流和?個(gè)輸?流,也能夠開(kāi)始數(shù)據(jù)傳輸。本節(jié)介紹如何在兩個(gè)設(shè)備之間初始化?個(gè)連接。 服務(wù)器端和客戶(hù)端獲得
BluetoothSocket的方法是不同的,服務(wù)器端是當(dāng)?個(gè)進(jìn)?的連接被接受時(shí)才產(chǎn)生?個(gè)BluetoothSocket,客戶(hù)端是在打開(kāi)
?個(gè)到服務(wù)器端的RFCOMM信道時(shí)獲得BluetoothSocket的。
! [http://developer.android.com/images/bt_pairing_request.png]
?種實(shí)現(xiàn)技術(shù)是,每?個(gè)設(shè)備都自動(dòng)作為?個(gè)服務(wù)器,所以每個(gè)設(shè)備都有?個(gè)server socket并監(jiān)聽(tīng)連接。然后每個(gè)設(shè)備都
能作為客戶(hù)端建立?個(gè)到另?臺(tái)設(shè)備的連接。另外?種代替方法是,?個(gè)設(shè)備按需打開(kāi)?個(gè)server socket,另外?個(gè)設(shè)備
僅初始化?個(gè)到這個(gè)設(shè)備的連接。
Note: 如果兩個(gè)設(shè)備在建立連接之前并沒(méi)有配對(duì),那么在建立連接的過(guò)程中,Android框架將自動(dòng)顯示?個(gè)配對(duì)請(qǐng)求的
notification或者?個(gè)對(duì)話(huà)框,如Figure 3所示。所以,在嘗試連接設(shè)備時(shí),你的應(yīng)用程序無(wú)需確保設(shè)備之間已經(jīng)進(jìn)行了配
對(duì)。你的RFCOMM連接將會(huì)在用戶(hù)確認(rèn)配對(duì)之后繼續(xù)進(jìn)行,或者用戶(hù)拒絕或者超時(shí)之后失敗。
作為服務(wù)器連接
如果要連接兩個(gè)設(shè)備,其中?個(gè)必須充當(dāng)服務(wù)器,通過(guò)持有?個(gè)打開(kāi)的BluetoothServerSocket對(duì)象。服務(wù)器socket的作用
是偵聽(tīng)進(jìn)來(lái)的連接,如果?個(gè)連接被接受,提供?個(gè)連接好的BluetoothSocket對(duì)象。從BluetoothServerSocket獲取
到BluetoothSocket對(duì)象之后,BluetoothServerSocket就可以(也應(yīng)該)丟棄了,除非你還要用它來(lái)接收更多的連接。
下面是建立服務(wù)器socket和接收?個(gè)連接的基本步驟:
1.通過(guò)調(diào)用listenUsingRfcommWithServiceRecord(String, UUID)得到?個(gè)BluetoothServerSocket對(duì)象。
該字符串為服務(wù)的識(shí)別名稱(chēng),系統(tǒng)將自動(dòng)寫(xiě)?到?個(gè)新的服務(wù)發(fā)現(xiàn)協(xié)議(SDP)數(shù)據(jù)庫(kù)接??到設(shè)備上的(名字是任意
的,可以簡(jiǎn)單地是應(yīng)用程序的名稱(chēng))項(xiàng)。 UUID也包括在SDP接??中,將是客戶(hù)端設(shè)備連接協(xié)議的基礎(chǔ)。也就是說(shuō),當(dāng)
客戶(hù)端試圖連接本設(shè)備,它將攜帶?個(gè)UUID用來(lái)唯?標(biāo)識(shí)它要連接的服務(wù),UUID必須匹配,連接才會(huì)被接受。
2.通過(guò)調(diào)用accept()來(lái)偵聽(tīng)連接請(qǐng)求。
這是?個(gè)阻塞的調(diào)用,知道有連接進(jìn)來(lái)或者產(chǎn)生異常才會(huì)返回。只有遠(yuǎn)程設(shè)備發(fā)送?個(gè)連接請(qǐng)求,并且攜帶的UUID與偵聽(tīng)
它socket注冊(cè)的UUID匹配,連接請(qǐng)求才會(huì)被接受。如果成功,accept()將返回?個(gè)連接好的BluetoothSocket對(duì)象。
3.除非需要再接收另外的連接,否則的話(huà)調(diào)用close() 。
close()釋放server socket和它的資源,但不會(huì)關(guān)閉連接accept()返回的連接好的BluetoothSocket對(duì)象。與TCP/IP不
同,RFCOMM同?時(shí)刻?個(gè)信道只允許?個(gè)客戶(hù)端連接,因此?多數(shù)情況下意味著在BluetoothServerSocket接受?個(gè)連
接請(qǐng)求后應(yīng)該立即調(diào)用close()。
accept()調(diào)用不應(yīng)該在主Activity UI線(xiàn)程中進(jìn)行,因?yàn)檫@是個(gè)阻塞的調(diào)用,會(huì)妨礙其他的交互。經(jīng)常是在在?個(gè)新線(xiàn)程中做
BluetoothServerSocket或BluetoothSocket的所有?作來(lái)避免UI線(xiàn)程阻塞。注意所有BluetoothServerSocket或
BluetoothSocket的方法都是線(xiàn)程安全的。
示例:
下面是?個(gè)簡(jiǎn)單的接受連接的服務(wù)器組件代碼示例:
示例
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app's UUID string, also used by the client code
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
/* * Will cancel the listening socket, and cause the thread to finish * /
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
本例中,僅僅只接受?個(gè)進(jìn)來(lái)的連接,?旦連接被接受獲取到BluetoothSocket,就發(fā)送獲取到的BluetoothSocket給?個(gè)
單獨(dú)的線(xiàn)程,然后關(guān)閉BluetoothServerSocket并跳出循環(huán)。
注意:accept()返回BluetoothSocket后,socket已經(jīng)連接了,所以在客戶(hù)端不應(yīng)該呼叫connnect()。
manageConnectedSocket()是?個(gè)虛方法,用來(lái)初始化線(xiàn)程好傳輸數(shù)據(jù)。
通常應(yīng)該在處理完偵聽(tīng)到的連接后立即關(guān)閉BluetoothServerSocket。在本例中,close()在得到BluetoothSocket后馬上被
調(diào)用。還需要在線(xiàn)程中提供?個(gè)公共的方法來(lái)關(guān)閉私有的BluetoothSocket,停止服務(wù)端socket的偵聽(tīng)。
作為客戶(hù)端連接
為了實(shí)現(xiàn)與遠(yuǎn)程設(shè)備的連接,你必須首先獲得?個(gè)代表遠(yuǎn)程設(shè)備BluetoothDevice對(duì)象。然后使用BluetoothDevice對(duì)象來(lái)
獲取?個(gè)BluetoothSocket來(lái)實(shí)現(xiàn)來(lái)接。
下面是基本的步驟:
1.用BluetoothDevice調(diào)用createRfcommSocketToServiceRecord(UUID)獲取?個(gè)BluetoothSocket對(duì)象。 這個(gè)初始化的
BluetoothSocket會(huì)連接到BluetoothDevice。UUID必須匹配服務(wù)器設(shè)備在打開(kāi)BluetoothServerSocket 時(shí)用到的
UUID(用java.util.UUID) listenUsingRfcommWithServiceRecord(String, UUID)。可以簡(jiǎn)單的生成?個(gè)UUID串然后在服務(wù)
器和客戶(hù)端都使用該UUID。
2.調(diào)用connect()完成連接 當(dāng)調(diào)用這個(gè)方法的時(shí)候,系統(tǒng)會(huì)在遠(yuǎn)程設(shè)備上完成?個(gè)SDP查找來(lái)匹配UUID。如果查找成功并
且遠(yuǎn)程設(shè)備接受連接,就共享RFCOMM信道,connect()會(huì)返回。這也是?個(gè)阻塞的調(diào)用,不管連接失敗還是超時(shí)(12
秒)都會(huì)拋出異常。
注意:要確保在調(diào)用connect()時(shí)沒(méi)有同時(shí)做設(shè)備查找,如果在查找設(shè)備,該連接嘗試會(huì)顯著的變慢,慢得類(lèi)似失敗了。
實(shí)例: 下面是?個(gè)完成Bluetooth連接的樣例線(xiàn)程:
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
/* * Will cancel an in-progress connection, and close the socket * /
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
注意 : 到cancelDiscovery()在連接操作前被調(diào)用。在連接之前,不管搜索有沒(méi)有進(jìn)行,該調(diào)用都是安全的,不需要確認(rèn)
(當(dāng)然如果有要確認(rèn)的需求,可以調(diào)用isDiscovering() )。 manageConnectedSocket()是?個(gè)虛方法,用來(lái)初始化線(xiàn)程
好傳輸數(shù)據(jù)。 在對(duì)BluetoothSocket的處理完成后,記得調(diào)用close()來(lái)關(guān)閉連接的socket和清理所有的內(nèi)部資源。
管理連接
如果已經(jīng)連接了兩個(gè)設(shè)備,他們都已經(jīng)擁有各自的連接好的BluetoothSocket對(duì)象。那就是?個(gè)有趣的開(kāi)始,因?yàn)槟憧梢栽?/p>
設(shè)備間共享數(shù)據(jù)了。使用BluetoothSocket,傳輸任何數(shù)據(jù)通常來(lái)說(shuō)都很容易了:
1.通過(guò)socket獲取輸?輸出流來(lái)處理傳輸(分別使用getInputStream()和getOutputStream() )。
2.用read(byte[])和write(byte[])來(lái)實(shí)現(xiàn)讀寫(xiě)。
僅此而已。
當(dāng)然,還是有很多細(xì)節(jié)需要考慮的。首要的,需要用?個(gè)專(zhuān)門(mén)的線(xiàn)程來(lái)實(shí)現(xiàn)流的讀寫(xiě)。只是很重要的,因?yàn)閞ead(byte[])和
write(byte[])都是阻塞的調(diào)用。read(byte[])會(huì)阻塞直到流中有數(shù)據(jù)可讀。write(byte[])通常不會(huì)阻塞,但是如果遠(yuǎn)程設(shè)備調(diào)
用read(byte[])不夠快導(dǎo)致中間緩沖區(qū)滿(mǎn),它也可能阻塞。所以線(xiàn)程中的主循環(huán)應(yīng)該用于讀取InputStream。線(xiàn)程中也應(yīng)該
有單獨(dú)的方法用來(lái)完成寫(xiě)OutputStream。
示例
下面是?個(gè)如上面描述那樣的例?:
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main activity to send data to the remote device * /
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main activity to shutdown the connection * /
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
構(gòu)造函數(shù)中得到需要的流,?旦執(zhí)行,線(xiàn)程會(huì)等待從InputStream來(lái)的數(shù)據(jù)。當(dāng)read(byte[])返回從流中讀到的字節(jié)后,數(shù)
據(jù)通過(guò)父類(lèi)的成員Handler被送到主Activity,然后繼續(xù)等待讀取流中的數(shù)據(jù)。 向外發(fā)送數(shù)據(jù)只需簡(jiǎn)單的調(diào)用線(xiàn)程的write()
方法。 線(xiàn)程的cancel()方法時(shí)很重要的,以便連接可以在任何時(shí)候通過(guò)關(guān)閉BluetoothSocket來(lái)終止。它應(yīng)該總在處理完
Bluetooth連接后被調(diào)用。
使用配置文件
從Android 3.0開(kāi)始,Bluetooth API就包含了對(duì)Bluetooth profiles的支持。 Bluetooth profile是基于藍(lán)牙的設(shè)備之間通信的
無(wú)線(xiàn)接?規(guī)范。 例如Hands-Free profile(免提模式)。 如果移動(dòng)電話(huà)要連接?個(gè)無(wú)線(xiàn)耳機(jī),他們都要支持Hands-Free
profile。
你在你的類(lèi)里可以完成BluetoothProfile接?來(lái)支持某?Bluetooth profiles。Android Bluetooth API完成了下面的Bluetooth
profile:
耳機(jī)。 Headset profile提供了移動(dòng)電話(huà)上的Bluetooth耳機(jī)支持。Android提供了BluetoothHeadset類(lèi),它是?個(gè)協(xié)議,用
來(lái)通過(guò)IPC(interprocess communication)控制Bluetooth Headset Service。BluetoothHeadset既包含Bluetooth
Headset profile也包含Hands-Free profile,還包括對(duì)AT命令的支持。
A2DP. Advanced Audio Distribution Profile (A2DP) profile,高級(jí)音頻傳輸模式。Android提供了BluetoothA2dp類(lèi),這是
?個(gè)通過(guò)IPC來(lái)控制Bluetooth A2DP的協(xié)議。
Android4.0(API級(jí)別14)推出了支持藍(lán)牙醫(yī)療設(shè)備模式(HDP),這使您可以創(chuàng)建支持藍(lán)牙的醫(yī)療設(shè)備,使用藍(lán)牙通信的應(yīng)
用程序,例如心率監(jiān)視器,血液,溫度計(jì)和秤等等。 支持的設(shè)備和相應(yīng)的設(shè)備數(shù)據(jù)專(zhuān)業(yè)化代碼,請(qǐng)參閱藍(lán)牙分配在
www.bluetooth.org數(shù)。請(qǐng)注意,這些值的ISO / IEEE11073-20601引用[7] MDC_DEV_SPEC_PROFILE_* 命名代碼附件
的規(guī)范。 對(duì)于更多的HDP討論, 查看Health Device Profile.
下面是使用profile的基本步驟:
1.獲取默認(rèn)的Bluetooth適配器。
2.使用getProfileProxy()來(lái)建立?個(gè)與profile相關(guān)的profile協(xié)議對(duì)象的連接。在下面的例?中,profile協(xié)議對(duì)象
是BluetoothHeadset的?個(gè)實(shí)例。
3.設(shè)置BluetoothProfile.ServiceListener。該listener通知BluetoothProfile IPC客戶(hù)端,當(dāng)客戶(hù)端連接或斷連服務(wù)器的時(shí)候
4.在[android.bluetooth.BluetoothProfile)
onServiceConnected()](http://docs.eoeandroid.com/reference/android/bluetooth/BluetoothProfile.ServiceListener.html#onServiceConnected(int,)
內(nèi),得到?個(gè)profile協(xié)議對(duì)象的句柄。
5.?旦擁有了profile協(xié)議對(duì)象,就可以用它來(lái)監(jiān)控連接的狀態(tài),完成于該profile相關(guān)的其他操作。
例如,下面的代碼片段顯示如何連接到?個(gè)BluetoothHeadset協(xié)議對(duì)象,用來(lái)控制Headset profile:
BluetoothHeadset mBluetoothHeadset;
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = (BluetoothHeadset) proxy;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = null;
}
}
};
// ... call functions on mBluetoothHeadset
// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset)
Vendor-specific AT? 指令
從Android 3.0開(kāi)始,應(yīng)用程序可以注冊(cè)偵聽(tīng)預(yù)定義的Vendor-specific AT命令這樣的系統(tǒng)廣播(如Plantronics +XEVENT
command)。例如,應(yīng)用可以接收到?個(gè)廣播,該廣播表明連接的設(shè)備電量過(guò)低,然后通知用戶(hù)做好其他需要的操作。
創(chuàng)建?個(gè)帶ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent的broadcast receiver來(lái)為耳機(jī)處理
醫(yī)療設(shè)備模式
Android4.0(API級(jí)別14)推出了支持藍(lán)牙醫(yī)療設(shè)備模式(HDP),這使您可以創(chuàng)建支持藍(lán)牙的醫(yī)療設(shè)備,使用藍(lán)牙通信的應(yīng)
用程序,例如心率監(jiān)視器,血液,溫度計(jì)和秤。藍(lán)牙衛(wèi)生API包括基礎(chǔ)
類(lèi)BluetoothHealth,BluetoothHealthCallback,BluetoothHealthAppConfiguration。 在使用藍(lán)牙衛(wèi)生API,它有助于理解
這些關(guān)鍵的HDP概念:
創(chuàng)建?個(gè) HDP? 應(yīng)用
創(chuàng)建 ?個(gè)Android HDP應(yīng)用要下面?步:
1.獲取?個(gè)參考的BluetoothHealth代理對(duì)象.
類(lèi)似普通的耳機(jī)和A2DP設(shè)備,你必須調(diào)用BluetoothProfile與getProfileProxy() 。ServiceListener 和醫(yī)療配置類(lèi)型來(lái)建
立?個(gè)配置代理對(duì)象的連接。
2.創(chuàng)建?個(gè)BluetoothHealthCallback和注冊(cè)的應(yīng)用程序配置(BluetoothHealthAppConfiguration)作為?個(gè)醫(yī)療sink。
3.建立?個(gè)連接到醫(yī)療設(shè)備。?些設(shè)備將初始化連接。 開(kāi)展這?步對(duì)于這些設(shè)備,這是不必要的。
4.當(dāng)連接成功到?個(gè)醫(yī)療設(shè)備時(shí),使用文件描述符讀/寫(xiě)到醫(yī)療設(shè)備。
接收到的數(shù)據(jù)需要使用健康管理,實(shí)現(xiàn)了IEEE11073-XXXXX規(guī)范進(jìn)行解釋。
5.當(dāng)完成后,關(guān)閉醫(yī)療通道和注銷(xiāo)申請(qǐng)。通道也有延伸靜止時(shí)關(guān)閉。
為了完善這個(gè)例?說(shuō)明這些步驟。查看Bluetooth HDP (Health Device Profile) 。