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

Android平臺(tái)支持藍(lán)牙網(wǎng)絡(luò)協(xié)議棧务热,實(shí)現(xiàn)藍(lán)牙設(shè)備之間數(shù)據(jù)的無線傳輸捆毫。本文檔描述了怎樣利用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中查詢已經(jīng)配對的設(shè)備 * 建立RFCOMM通道 * 通過service discovery連接到其它設(shè)備

* 在設(shè)備之間傳輸數(shù)據(jù) * 管理多個(gè)連接

基礎(chǔ)知識

本文檔介紹了如何使用Android的藍(lán)牙API來完成的四個(gè)必要的主要任務(wù)羽嫡,使用藍(lán)牙進(jìn)行設(shè)備通信氛赐,主要包含四個(gè)部分:藍(lán)

牙設(shè)置艰管、搜索設(shè)備(配對的或可見的)滓侍、連接、傳輸數(shù)據(jù)蛙婴。 所有的藍(lán)牙API在android.bluetooth包中粗井。實(shí)現(xiàn)這些功能主要

需要下面這?個(gè)類和接?:

BluetoothAdapter 代表本地藍(lán)牙適配器(藍(lán)牙發(fā)射器),是所有藍(lán)牙交互的??街图。通過它可以搜索其它藍(lán)牙設(shè)備浇衬,查詢已

經(jīng)配對的設(shè)備列表醉冤,通過已知的MAC地址創(chuàng)建BluetoothDevice,創(chuàng)建BluetoothServerSocket監(jiān)聽來自其它設(shè)備的通信澜沟。

BluetoothDevice 代表了?個(gè)遠(yuǎn)端的藍(lán)牙設(shè)備, 使用它請求遠(yuǎn)端藍(lán)牙設(shè)備連接或者獲取 遠(yuǎn)端藍(lán)牙設(shè)備的名稱莹妒、地址百揭、種類

和綁定狀態(tài)。 (其信息是封裝在 bluetoothsocket 中) 固棚。

BluetoothSocket 代表了?個(gè)藍(lán)牙套接字的接?(類似于 tcp 中的套接字) ,他是應(yīng)用程 序通過輸?、輸出流與其他藍(lán)牙

設(shè)備通信的連接點(diǎn)袭厂。

BluetoothServerSocket 代表打開服務(wù)連接來監(jiān)聽可能到來的連接請求 (屬于 server 端) 式矫, 為了連接兩個(gè)藍(lán)牙設(shè)備必須

有?個(gè)設(shè)備作為服務(wù)器打開?個(gè)服務(wù)套接字。 當(dāng)遠(yuǎn)端設(shè)備發(fā)起連 接連接請求的時(shí)候情萤,并且已經(jīng)連接到了的時(shí)

候柒傻,Blueboothserversocket 類將會(huì)返回?個(gè) bluetoothsocket。

BluetoothClass 描述了?個(gè)設(shè)備的特性(profile)或該設(shè)備上的藍(lán)牙?致可以提供哪些服務(wù)(service)莫辨,但不可信郁竟。比如州弟,

設(shè)備是?個(gè)電話雄妥、計(jì)算機(jī)或手持設(shè)備;設(shè)備可以提供audio/telephony服務(wù)等〈履可以用它來進(jìn)行?些UI上的提示已艰。

BluetoothProfile

BluetoothHeadset 提供手機(jī)使用藍(lán)牙耳機(jī)的支持痊末。這既包括藍(lán)牙耳機(jī)和免提(V1.5)模式。

BluetoothA2dp 定義高品質(zhì)的音頻哩掺,可以從?個(gè)設(shè)備傳輸?shù)搅?個(gè)藍(lán)牙連接凿叠。 “A2DP的”代表高級音頻分配模式。

BluetoothHealth 代表了醫(yī)療設(shè)備配置代理控制的藍(lán)牙服務(wù)

BluetoothHealthCallback ?個(gè)抽象類嚼吞,使用實(shí)現(xiàn)BluetoothHealth回調(diào)幔嫂。你必須擴(kuò)展這個(gè)類并實(shí)現(xiàn)回調(diào)方法接收更新應(yīng)用

程序的注冊狀態(tài)和藍(lán)牙通道狀態(tài)的變化。

BluetoothHealthAppConfiguration 代表?個(gè)應(yīng)用程序的配置誊薄,藍(lán)牙醫(yī)療第三方應(yīng)用注冊與遠(yuǎn)程藍(lán)牙醫(yī)療設(shè)備交流履恩。

BluetoothProfile.ServiceListener 當(dāng)他們已經(jīng)連接到或從服務(wù)斷開時(shí)通知BluetoothProfile IPX的客戶時(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)牙通信绽昏,例如連接請求,接收連接和傳送數(shù)據(jù)都必須有BLUETOOTH權(quán)限俏脊。

必須要求BLUETOOTH_ADMIN的權(quán)限來啟動(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ù)用戶要求修改的藍(lán)牙設(shè)置

注釋:要請求BLUETOOTH_ADMIN的話,必須要先有BLUETOOTH卷员。

在你的應(yīng)用manifest 文件中聲明藍(lán)牙權(quán)限盈匾。例如:


<manifest ... >

<uses-permission android:name="android.permission.BLUETOOTH" />

...

</manifest>

通過查看<uses-permission>資料來聲明應(yīng)用權(quán)限獲取更多的信息。

藍(lán)牙設(shè)置

在你的應(yīng)用通過藍(lán)牙進(jìn)行通信之前毕骡,你需要確認(rèn)設(shè)備是否支持藍(lán)牙削饵,如果支持,確信它被打開未巫。

如果不支持窿撬,則不能使用藍(lán)牙功能。如果支持藍(lán)牙叙凡,但不能夠使用尤仍,你剛要在你的應(yīng)用中請求使用藍(lán)牙。這個(gè)要兩步完

成狭姨,使用BluetoothAdapter。

1.獲取BluetoothAdapter

所有的藍(lán)牙活動(dòng)請求BluetoothAdapter苏遥,為了獲取BluetoothAdapter饼拍,呼叫靜態(tài)方法getDefaultAdapter() 。這個(gè)會(huì)返回?

個(gè)BluetoothAdapter田炭,代表設(shè)備自?的藍(lán)牙適配器(藍(lán)牙無線電)师抄。這個(gè)藍(lán)牙適配器應(yīng)用于整個(gè)系統(tǒng)中,你的應(yīng)用可以通

過這個(gè)對象進(jìn)行交互教硫。如果getDefaultAdapter()返回null,則這個(gè)設(shè)備不支持藍(lán)牙。例如:


BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

if (mBluetoothAdapter == null) {

// Device does not support Bluetooth

}

2.打開藍(lán)牙

其次瞬矩。你需要確定藍(lán)牙能夠使用。通過isEnabled()來檢查藍(lán)牙當(dāng)前是否可用涵叮。如果這個(gè)方法返回false,則藍(lán)牙不能夠使

用伞插。為了請求藍(lán)牙使用,呼叫startActivityForResult()與的ACTION_REQUEST_ENABLE動(dòng)作意圖媚污。通過系統(tǒng)設(shè)置中啟

用藍(lán)牙將發(fā)出?個(gè)請求(不停止藍(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]

對話框中顯示請求使用藍(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ò)誤(或用戶

響應(yīng)“NO”那么結(jié)果返回RESULT_CANCELED诚些。

除了通過onActivityResult()飞傀,還可以通過監(jiān)聽ACTION_STATE_CHANGED這個(gè)broadcast Intent來知道藍(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)家破,你可以跳過上面的

步驟2颜说。參閱enabling discoverability。

搜索設(shè)備

使用BluetoothAdapter可以通過設(shè)備搜索或查詢配對設(shè)備找到遠(yuǎn)程Bluetooth設(shè)備汰聋。

Device discovery(設(shè)備搜索)是?個(gè)掃描搜索本地已使能Bluetooth設(shè)備并且從搜索到的設(shè)備請求?些信息的過程(有時(shí)

候會(huì)收到類似“discovering”门粪,“inquiring”或“scanning”)。但是烹困,搜索到的本地Bluetooth設(shè)備只有在打開被發(fā)現(xiàn)功能后才

會(huì)響應(yīng)?個(gè)discovery請求玄妈,響應(yīng)的信息包括設(shè)備名髓梅,類枯饿,唯?的MAC地址屈张。發(fā)起搜尋的設(shè)備可以使用這些信息來初始化

跟被發(fā)現(xiàn)的設(shè)備的連接阁谆。 ?旦與遠(yuǎn)程設(shè)備的第?次連接被建立场绿,?個(gè)pairing請求就會(huì)自動(dòng)提交給用戶璧尸。如果設(shè)備已配對爷光,

配對設(shè)備的基本信息(名稱蛀序,類,MAC地址)就被保存下來了重贺,能夠使用Bluetooth API來讀取這些信息气笙。使用已知的遠(yuǎn)程

設(shè)備的MAC地址,連接可以在任何時(shí)候初始化而不必先完成搜索(當(dāng)然這是假設(shè)遠(yuǎn)程設(shè)備是在可連接的空間范圍內(nèi))蛉谜。

需要記住型诚,配對和連接是兩個(gè)不同的概念:

配對意思是兩個(gè)設(shè)備相互意識到對方的存在也搓,共享?個(gè)用來鑒別身份的鏈路鍵(link-key)傍妒,能夠與對方建立?個(gè)加密的連

接颤练。

連接意思是兩個(gè)設(shè)備現(xiàn)在共享?個(gè)RFCOMM信道患雇,能夠相互傳輸數(shù)據(jù)苛吱。

目前Android Bluetooth API's要求設(shè)備在建立RFCOMM信道前必須配對(配對是在使用Bluetooth API初始化?個(gè)加密連接

時(shí)自動(dòng)完成的)。

下面描述如何查詢已配對設(shè)備彰亥,搜索新設(shè)備任斋。

注意:Android的電源設(shè)備默認(rèn)是不能被發(fā)現(xiàn)的废酷。用戶可以通過系統(tǒng)設(shè)置讓它在有限的時(shí)間內(nèi)可以被發(fā)現(xiàn),或者可以在應(yīng)

用程序中要求用戶使能被發(fā)現(xiàn)功能趴俘。

查找匹配設(shè)備

在搜索設(shè)備前磨淌,查詢配對設(shè)備看需要的設(shè)備是否已經(jīng)是已經(jīng)存在是很值得的搪锣,可以調(diào)用getBondedDevices()來做到,該函

數(shù)會(huì)返回?個(gè)描述配對設(shè)備BluetoothDevice的結(jié)果集。例如,可以使用ArrayAdapter查詢所有配對設(shè)備然后顯示所有設(shè)備

名給用戶:

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對象中需要用來初始化?個(gè)連接唯?需要用到的信息就是MAC地址音五。

掃描設(shè)備

要開始搜索設(shè)備坚嗜,只需簡單的調(diào)用startDiscovery() 。該函數(shù)時(shí)異步的,調(diào)用后立即返回,返回值表示搜索是否成功開始造烁。

搜索處理通常包括?個(gè)12秒鐘的查詢掃描,然后跟隨?個(gè)頁面顯示搜索到設(shè)備Bluetooth名稱。

應(yīng)用中可以注冊?個(gè)帶ACTION_FOUND Intent的BroadcastReceiver围辙,搜索到每?個(gè)設(shè)備時(shí)都接收到消息。對于每?個(gè)設(shè)

備押赊,系統(tǒng)都會(huì)廣播ACTION_FOUND Intent崇棠,該Intent攜帶著而外的字段信息EXTRA_DEVICE和EXTRA_CLASS萎坷,分別包

含?個(gè)BluetoothDevice和?個(gè)BluetoothClass。

下面的示例顯示如何注冊和處理設(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è)備搜索對于Bluetooth適配器來說是?個(gè)重量級的處理插佛,要消耗?量它的資源氢拥。?旦你已經(jīng)找到?個(gè)設(shè)備來連

接冬殃,請確保你在嘗試連接前使用了cancelDiscovery()來停止搜索。同樣耳璧,如果已經(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è)請求通過系統(tǒng)剛設(shè)置使設(shè)備出于可以被發(fā)現(xiàn)的模式(而不影響

應(yīng)用程序)幼驶。默認(rèn)情況下氏淑,設(shè)備在120秒后變?yōu)榭梢员话l(fā)現(xiàn)的』岳粒可以通過額外增加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]

詢問用戶是否允許打開設(shè)備可以被發(fā)現(xiàn)功能時(shí)會(huì)顯示?個(gè)對話框钞楼。如果用戶選擇“Yes”窿凤,設(shè)備會(huì)在指定時(shí)間過后變?yōu)榭梢员?/p>

發(fā)現(xiàn)的。Activity的onActivityResult()回調(diào)函數(shù)被調(diào)用夯秃,結(jié)果碼等于設(shè)備變?yōu)榭梢员话l(fā)現(xiàn)所需時(shí)長仓洼。如果用戶選擇“No”或者

有錯(cuò)誤發(fā)生色建,結(jié)果碼會(huì)是Activity.RESULT_CANCELLED箕戳。

提示:如果Bluetooth沒有啟用玻墅,啟用Bluetooth可被發(fā)現(xiàn)功能能夠自動(dòng)開啟Bluetooth澳厢。

在規(guī)定的時(shí)間內(nèi)剩拢,設(shè)備會(huì)靜靜的保持可以被發(fā)現(xiàn)模式裸扶。如果想在可以被發(fā)現(xiàn)模式被更改時(shí)受到通知呵晨,可以用

ACTION_SCAN_MODE_CHANGED Intent注冊?個(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è)備就不需要打開設(shè)備的可以被發(fā)現(xiàn)功能。只在應(yīng)用作為?個(gè)服

務(wù)器socket的宿主用來接收進(jìn)來的連接時(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ù)器端和客戶端的機(jī)制梳杏,因?yàn)?個(gè)設(shè)備必須必須打開?個(gè)server

socket,而另?個(gè)必須初始化這個(gè)連接(使用服務(wù)器端設(shè)備的MAC地址進(jìn)行初始化)劲适。 當(dāng)服務(wù)器端和客戶端在同?個(gè)

RFCOMM信道上都有?個(gè)BluetoothSocket時(shí)霞势,就可以認(rèn)為它們之間建立了?個(gè)連接愕贡。在這個(gè)時(shí)刻固以,每個(gè)設(shè)備能獲得?個(gè)

輸出流和?個(gè)輸?流,也能夠開始數(shù)據(jù)傳輸篙螟。本節(jié)介紹如何在兩個(gè)設(shè)備之間初始化?個(gè)連接问拘。 服務(wù)器端和客戶端獲得

BluetoothSocket的方法是不同的墅冷,服務(wù)器端是當(dāng)?個(gè)進(jìn)?的連接被接受時(shí)才產(chǎn)生?個(gè)BluetoothSocket寞忿,客戶端是在打開

?個(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)聽連接端逼。然后每個(gè)設(shè)備都

能作為客戶端建立?個(gè)到另?臺(tái)設(shè)備的連接余掖。另外?種代替方法是,?個(gè)設(shè)備按需打開?個(gè)server socket找田,另外?個(gè)設(shè)備

僅初始化?個(gè)到這個(gè)設(shè)備的連接墩衙。

Note: 如果兩個(gè)設(shè)備在建立連接之前并沒有配對,那么在建立連接的過程中挫剑,Android框架將自動(dòng)顯示?個(gè)配對請求的

notification或者?個(gè)對話框樊破,如Figure 3所示。所以顺少,在嘗試連接設(shè)備時(shí)脆炎,你的應(yīng)用程序無需確保設(shè)備之間已經(jīng)進(jìn)行了配

對袱蚓。你的RFCOMM連接將會(huì)在用戶確認(rèn)配對之后繼續(xù)進(jìn)行喇潘,或者用戶拒絕或者超時(shí)之后失敗硕勿。

作為服務(wù)器連接

如果要連接兩個(gè)設(shè)備扼褪,其中?個(gè)必須充當(dāng)服務(wù)器,通過持有?個(gè)打開的BluetoothServerSocket對象幔崖。服務(wù)器socket的作用

是偵聽進(jìn)來的連接,如果?個(gè)連接被接受嗅定,提供?個(gè)連接好的BluetoothSocket對象。從BluetoothServerSocket獲取

到BluetoothSocket對象之后碎乃,BluetoothServerSocket就可以(也應(yīng)該)丟棄了,除非你還要用它來接收更多的連接证九。

下面是建立服務(wù)器socket和接收?個(gè)連接的基本步驟:

1.通過調(diào)用listenUsingRfcommWithServiceRecord(String, UUID)得到?個(gè)BluetoothServerSocket對象呀页。

該字符串為服務(wù)的識別名稱,系統(tǒng)將自動(dòng)寫?到?個(gè)新的服務(wù)發(fā)現(xiàn)協(xié)議(SDP)數(shù)據(jù)庫接??到設(shè)備上的(名字是任意

的猜惋,可以簡單地是應(yīng)用程序的名稱)項(xiàng)丸氛。 UUID也包括在SDP接??中,將是客戶端設(shè)備連接協(xié)議的基礎(chǔ)著摔。也就是說缓窜,當(dāng)

客戶端試圖連接本設(shè)備,它將攜帶?個(gè)UUID用來唯?標(biāo)識它要連接的服務(wù)谍咆,UUID必須匹配禾锤,連接才會(huì)被接受。

2.通過調(diào)用accept()來偵聽連接請求摹察。

這是?個(gè)阻塞的調(diào)用供嚎,知道有連接進(jìn)來或者產(chǎn)生異常才會(huì)返回氮凝。只有遠(yuǎn)程設(shè)備發(fā)送?個(gè)連接請求稿壁,并且攜帶的UUID與偵聽

它socket注冊的UUID匹配帽驯,連接請求才會(huì)被接受。如果成功度气,accept()將返回?個(gè)連接好的BluetoothSocket對象晒旅。

3.除非需要再接收另外的連接鱼鼓,否則的話調(diào)用close() 。

close()釋放server socket和它的資源迂曲,但不會(huì)關(guān)閉連接accept()返回的連接好的BluetoothSocket對象队寇。與TCP/IP不

同,RFCOMM同?時(shí)刻?個(gè)信道只允許?個(gè)客戶端連接,因此?多數(shù)情況下意味著在BluetoothServerSocket接受?個(gè)連

接請求后應(yīng)該立即調(diào)用close()扫俺。

accept()調(diào)用不應(yīng)該在主Activity UI線程中進(jìn)行,因?yàn)檫@是個(gè)阻塞的調(diào)用太示,會(huì)妨礙其他的交互。經(jīng)常是在在?個(gè)新線程中做

BluetoothServerSocket或BluetoothSocket的所有?作來避免UI線程阻塞。注意所有BluetoothServerSocket或

BluetoothSocket的方法都是線程安全的破停。

示例:

下面是?個(gè)簡單的接受連接的服務(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)來的連接,?旦連接被接受獲取到BluetoothSocket沼沈,就發(fā)送獲取到的BluetoothSocket給?個(gè)

單獨(dú)的線程,然后關(guān)閉BluetoothServerSocket并跳出循環(huán)左电。

注意:accept()返回BluetoothSocket后染簇,socket已經(jīng)連接了,所以在客戶端不應(yīng)該呼叫connnect()策橘。

manageConnectedSocket()是?個(gè)虛方法,用來初始化線程好傳輸數(shù)據(jù)。

通常應(yīng)該在處理完偵聽到的連接后立即關(guān)閉BluetoothServerSocket。在本例中,close()在得到BluetoothSocket后馬上被

調(diào)用。還需要在線程中提供?個(gè)公共的方法來關(guān)閉私有的BluetoothSocket蔫浆,停止服務(wù)端socket的偵聽。

作為客戶端連接

為了實(shí)現(xiàn)與遠(yuǎn)程設(shè)備的連接,你必須首先獲得?個(gè)代表遠(yuǎn)程設(shè)備BluetoothDevice對象亡哄。然后使用BluetoothDevice對象來

獲取?個(gè)BluetoothSocket來實(shí)現(xiàn)來接酝豪。

下面是基本的步驟:

1.用BluetoothDevice調(diào)用createRfcommSocketToServiceRecord(UUID)獲取?個(gè)BluetoothSocket對象坑赡。 這個(gè)初始化的

BluetoothSocket會(huì)連接到BluetoothDevice徐许。UUID必須匹配服務(wù)器設(shè)備在打開BluetoothServerSocket 時(shí)用到的

UUID(用java.util.UUID) listenUsingRfcommWithServiceRecord(String, UUID)趾牧『逦撸可以簡單的生成?個(gè)UUID串然后在服務(wù)

器和客戶端都使用該UUID。

2.調(diào)用connect()完成連接 當(dāng)調(diào)用這個(gè)方法的時(shí)候,系統(tǒng)會(huì)在遠(yuǎn)程設(shè)備上完成?個(gè)SDP查找來匹配UUID凉翻。如果查找成功并

且遠(yuǎn)程設(shè)備接受連接侧巨,就共享RFCOMM信道,connect()會(huì)返回鞭达。這也是?個(gè)阻塞的調(diào)用司忱,不管連接失敗還是超時(shí)(12

秒)都會(huì)拋出異常。

注意:要確保在調(diào)用connect()時(shí)沒有同時(shí)做設(shè)備查找畴蹭,如果在查找設(shè)備坦仍,該連接嘗試會(huì)顯著的變慢,慢得類似失敗了叨襟。

實(shí)例: 下面是?個(gè)完成Bluetooth連接的樣例線程:

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)用繁扎。在連接之前,不管搜索有沒有進(jìn)行,該調(diào)用都是安全的梳玫,不需要確認(rèn)

(當(dāng)然如果有要確認(rèn)的需求爹梁,可以調(diào)用isDiscovering() )。 manageConnectedSocket()是?個(gè)虛方法提澎,用來初始化線程

好傳輸數(shù)據(jù)姚垃。 在對BluetoothSocket的處理完成后,記得調(diào)用close()來關(guān)閉連接的socket和清理所有的內(nèi)部資源盼忌。

管理連接

如果已經(jīng)連接了兩個(gè)設(shè)備积糯,他們都已經(jīng)擁有各自的連接好的BluetoothSocket對象。那就是?個(gè)有趣的開始谦纱,因?yàn)槟憧梢栽?/p>

設(shè)備間共享數(shù)據(jù)了看成。使用BluetoothSocket,傳輸任何數(shù)據(jù)通常來說都很容易了:

1.通過socket獲取輸?輸出流來處理傳輸(分別使用getInputStream()和getOutputStream() )跨嘉。

2.用read(byte[])和write(byte[])來實(shí)現(xiàn)讀寫川慌。

僅此而已。

當(dāng)然偿荷,還是有很多細(xì)節(jié)需要考慮的窘游。首要的,需要用?個(gè)專門的線程來實(shí)現(xiàn)流的讀寫跳纳。只是很重要的忍饰,因?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ū)滿,它也可能阻塞斗塘。所以線程中的主循環(huán)應(yīng)該用于讀取InputStream赢织。線程中也應(yīng)該

有單獨(dú)的方法用來完成寫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í)行于置,線程會(huì)等待從InputStream來的數(shù)據(jù)。當(dāng)read(byte[])返回從流中讀到的字節(jié)后贞岭,數(shù)

據(jù)通過父類的成員Handler被送到主Activity八毯,然后繼續(xù)等待讀取流中的數(shù)據(jù)。 向外發(fā)送數(shù)據(jù)只需簡單的調(diào)用線程的write()

方法瞄桨。 線程的cancel()方法時(shí)很重要的话速,以便連接可以在任何時(shí)候通過關(guān)閉BluetoothSocket來終止。它應(yīng)該總在處理完

Bluetooth連接后被調(diào)用芯侥。

使用配置文件

從Android 3.0開始泊交,Bluetooth API就包含了對Bluetooth profiles的支持乳讥。 Bluetooth profile是基于藍(lán)牙的設(shè)備之間通信的

無線接?規(guī)范。 例如Hands-Free profile(免提模式)廓俭。 如果移動(dòng)電話要連接?個(gè)無線耳機(jī)云石,他們都要支持Hands-Free

profile。

你在你的類里可以完成BluetoothProfile接?來支持某?Bluetooth profiles白指。Android Bluetooth API完成了下面的Bluetooth

profile:

耳機(jī)留晚。 Headset profile提供了移動(dòng)電話上的Bluetooth耳機(jī)支持酵紫。Android提供了BluetoothHeadset類告嘲,它是?個(gè)協(xié)議,用

來通過IPC(interprocess communication)控制Bluetooth Headset Service奖地。BluetoothHeadset既包含Bluetooth

Headset profile也包含Hands-Free profile橄唬,還包括對AT命令的支持。

A2DP. Advanced Audio Distribution Profile (A2DP) profile参歹,高級音頻傳輸模式仰楚。Android提供了BluetoothA2dp類,這是

?個(gè)通過IPC來控制Bluetooth A2DP的協(xié)議犬庇。

Android4.0(API級別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ù)專業(yè)化代碼葬荷,請參閱藍(lán)牙分配在

www.bluetooth.org數(shù)。請注意纽帖,這些值的ISO / IEEE11073-20601引用[7] MDC_DEV_SPEC_PROFILE_* 命名代碼附件

的規(guī)范宠漩。 對于更多的HDP討論, 查看Health Device Profile.

下面是使用profile的基本步驟:

1.獲取默認(rèn)的Bluetooth適配器。

2.使用getProfileProxy()來建立?個(gè)與profile相關(guān)的profile協(xié)議對象的連接懊直。在下面的例?中扒吁,profile協(xié)議對象

是BluetoothHeadset的?個(gè)實(shí)例。

3.設(shè)置BluetoothProfile.ServiceListener室囊。該listener通知BluetoothProfile IPC客戶端雕崩,當(dāng)客戶端連接或斷連服務(wù)器的時(shí)候

4.在[android.bluetooth.BluetoothProfile)

onServiceConnected()](http://docs.eoeandroid.com/reference/android/bluetooth/BluetoothProfile.ServiceListener.html#onServiceConnected(int,)

內(nèi),得到?個(gè)profile協(xié)議對象的句柄波俄。

5.?旦擁有了profile協(xié)議對象晨逝,就可以用它來監(jiān)控連接的狀態(tài),完成于該profile相關(guān)的其他操作懦铺。

例如捉貌,下面的代碼片段顯示如何連接到?個(gè)BluetoothHeadset協(xié)議對象,用來控制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開始,應(yīng)用程序可以注冊偵聽預(yù)定義的Vendor-specific AT命令這樣的系統(tǒng)廣播(如Plantronics +XEVENT

command)趁窃。例如牧挣,應(yīng)用可以接收到?個(gè)廣播,該廣播表明連接的設(shè)備電量過低醒陆,然后通知用戶做好其他需要的操作瀑构。

創(chuàng)建?個(gè)帶ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent的broadcast receiver來為耳機(jī)處理

醫(yī)療設(shè)備模式

Android4.0(API級別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ǔ)

類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代理對象.

類似普通的耳機(jī)和A2DP設(shè)備嚷闭,你必須調(diào)用BluetoothProfile與getProfileProxy() 攒岛。ServiceListener 和醫(yī)療配置類型來建

立?個(gè)配置代理對象的連接。

2.創(chuàng)建?個(gè)BluetoothHealthCallback和注冊的應(yīng)用程序配置(BluetoothHealthAppConfiguration)作為?個(gè)醫(yī)療sink胞锰。

3.建立?個(gè)連接到醫(yī)療設(shè)備灾锯。?些設(shè)備將初始化連接。 開展這?步對于這些設(shè)備胜蛉,這是不必要的挠进。

4.當(dāng)連接成功到?個(gè)醫(yī)療設(shè)備時(shí),使用文件描述符讀/寫到醫(yī)療設(shè)備誊册。

接收到的數(shù)據(jù)需要使用健康管理领突,實(shí)現(xiàn)了IEEE11073-XXXXX規(guī)范進(jìn)行解釋。

5.當(dāng)完成后案怯,關(guān)閉醫(yī)療通道和注銷申請君旦。通道也有延伸靜止時(shí)關(guān)閉。

為了完善這個(gè)例?說明這些步驟嘲碱。查看Bluetooth HDP (Health Device Profile) 金砍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市麦锯,隨后出現(xiàn)的幾起案子恕稠,更是在濱河造成了極大的恐慌,老刑警劉巖扶欣,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹅巍,死亡現(xiàn)場離奇詭異千扶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)骆捧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門澎羞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人敛苇,你說我怎么就攤上這事妆绞。” “怎么了枫攀?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵括饶,是天一觀的道長。 經(jīng)常有香客問我脓豪,道長巷帝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任扫夜,我火速辦了婚禮,結(jié)果婚禮上驰徊,老公的妹妹穿的比我還像新娘笤闯。我一直安慰自己,他們只是感情好棍厂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布颗味。 她就那樣靜靜地躺著,像睡著了一般牺弹。 火紅的嫁衣襯著肌膚如雪浦马。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天张漂,我揣著相機(jī)與錄音晶默,去河邊找鬼。 笑死航攒,一個(gè)胖子當(dāng)著我的面吹牛磺陡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播漠畜,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼币他,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了憔狞?” 一聲冷哼從身側(cè)響起蝴悉,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘾敢,沒想到半個(gè)月后拍冠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硝枉,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年倦微,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妻味。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欣福,死狀恐怖责球,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拓劝,我是刑警寧澤雏逾,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站郑临,受9級特大地震影響栖博,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厢洞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一仇让、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躺翻,春花似錦丧叽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至陕靠,卻和暖如春迂尝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剪芥。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工垄开, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粗俱。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓说榆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寸认。 傳聞我的和親對象是個(gè)殘疾皇子签财,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

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