Android 藍(lán)牙開(kāi)發(fā)詳解

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) 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評(píng)論 3 422
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,947評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,201評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,960評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,350評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,549評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,914評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,089評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,340評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,753評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,007評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,834評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,106評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容