描述
最近公司有個(gè)項(xiàng)目,App從后臺(tái)獲取到數(shù)據(jù),App連接打印機(jī),將數(shù)據(jù)在打印機(jī)上打印。公司提供的測試設(shè)備是藍(lán)牙打印機(jī),為了完成項(xiàng)目,決定學(xué)習(xí)一下Android的藍(lán)牙通信機(jī)制,在此記錄全部過程
基于文檔
本文相關(guān)資料:
Android Bluetooth官方文檔:https://developer.android.com/guide/topics/connectivity/bluetooth?hl=zh-cn#EnablingDiscoverability
Android 藍(lán)牙知識(shí)
Android平臺(tái)包含藍(lán)牙網(wǎng)絡(luò)堆棧的支持,設(shè)備能以無線方式與其他藍(lán)牙設(shè)備交換數(shù)據(jù)。應(yīng)用框架提供了通過Android Bluetooth API訪問藍(lán)牙功能的途徑。
使用Bluetooth API,Android應(yīng)用可執(zhí)行以下操作:
- 掃描其他藍(lán)夜設(shè)備
- 查詢本地藍(lán)牙適配器的配對藍(lán)牙設(shè)備
- 建立RFCOMM通道
- 通過服務(wù)發(fā)現(xiàn)連接到其他設(shè)備
- 與其他設(shè)備進(jìn)行雙向數(shù)據(jù)傳輸
- 管理多個(gè)連接
基礎(chǔ)知識(shí)
Android中的 android.bluetooth 包中提供了所有Bluetooth API
BluetoothAdapter
表示本地藍(lán)牙適配器(藍(lán)牙無線裝置)。BluetoothAdapter是所有藍(lán)夜交互的入口點(diǎn)。利用本地藍(lán)牙適配器可以發(fā)現(xiàn)其他藍(lán)牙設(shè)備,查詢綁定(配對)設(shè)備的列表。使用已知的MAC地址實(shí)例化BluetoothDevice。以及創(chuàng)建BluetoothServerSocket 以偵聽來自其他設(shè)備的通信
BluetoothDevice
表示遠(yuǎn)程藍(lán)牙設(shè)備。利用它可以通過BluetoothSocket請求與某個(gè)遠(yuǎn)程設(shè)備建立連接,或查詢有關(guān)該設(shè)備的信息,例如設(shè)備的名稱、地址、類和綁定狀態(tài)等
BluetoothSocket
表示藍(lán)牙套接字接口,允許應(yīng)用通過InputStream和OutputStream與其他藍(lán)牙設(shè)備交換數(shù)據(jù)的連接點(diǎn)
BluetoothServerSocket
表示用于偵聽傳入請求的開放服務(wù)器套接字,要連接兩臺(tái)Android設(shè)備,其中一臺(tái)設(shè)備必須使用此類開放一個(gè)服務(wù)套接字。當(dāng)一臺(tái)遠(yuǎn)程藍(lán)牙設(shè)備向此設(shè)備發(fā)出連接請求是BluetoothServerSocket將會(huì)在接受連接后返回已經(jīng)連接的BluetoothSocket
BluetoothClass
描述藍(lán)牙設(shè)備的一般特征和功能。這是一組只讀屬性,用于定義設(shè)備的主要和次要設(shè)備類及其服務(wù)。不過它不能可靠的描述設(shè)備的所有藍(lán)牙配置文件和服務(wù),而是適合作為設(shè)備類型提示
BluetoothProfile
表示藍(lán)牙配置文件的接口。藍(lán)牙配置文件是適用于設(shè)備間藍(lán)牙通信的無線接口規(guī)范。
BluetoothHeadset
提供藍(lán)牙耳機(jī)支持
BluetoothA2dp
定義高質(zhì)量音頻如何通過藍(lán)牙連接和流式傳輸,從一臺(tái)設(shè)備傳輸?shù)搅硪慌_(tái)設(shè)備 “A2DP”代表高級(jí)音頻分發(fā)配置文件
BluetoothHealth
表示用于控制藍(lán)牙服務(wù)的健康設(shè)備配置文件袋里
BluetoothHealthCallback
用于實(shí)現(xiàn)BluetoothHealth回調(diào)的抽象類
BluetoothHealthAppConfiguration
表示第三方藍(lán)牙健康應(yīng)用注冊的應(yīng)用配置,以便于遠(yuǎn)程藍(lán)牙健康設(shè)備通信
BluetoothProfile.ServiceListener
在BluetoothProfile IPC客戶端連接到服務(wù)或斷開服務(wù)連接時(shí)向其發(fā)送通知的接口
藍(lán)牙權(quán)限
要在應(yīng)用中使用藍(lán)牙功能,必須聲明藍(lán)牙權(quán)限BLUETOOTH,需要權(quán)限才能執(zhí)行任何藍(lán)牙通信
如果希望應(yīng)用啟動(dòng)設(shè)備發(fā)現(xiàn)或操作藍(lán)牙設(shè)置,則還必須聲明BLUETOOTH_ADMIN權(quán)限。如果要使用BLUETOOTH_ADMIN權(quán)限,則還必須擁有BLUETOOTH權(quán)限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
設(shè)置藍(lán)牙
在開發(fā)中使用藍(lán)牙時(shí),需要驗(yàn)證設(shè)備支持藍(lán)牙,如果支持藍(lán)牙還需要確保藍(lán)牙啟用。
如果設(shè)備不支持藍(lán)牙,那還開發(fā)個(gè)毛線
設(shè)置藍(lán)牙的步驟分為:
- 獲取BluetoothAdapter
所有藍(lán)牙Activity都需要BluetoothAdapter。要獲取BluetoothAdapter 請調(diào)用靜態(tài)的getDefaultAdapter()方法,這個(gè)方法將返回一個(gè)表示設(shè)備自身的藍(lán)牙適配器(藍(lán)牙無線裝置)的BluetoothAdapter。整個(gè)系統(tǒng)有一個(gè)藍(lán)牙適配器,并且應(yīng)用可使用此對象與設(shè)備交互 如果getDefaultAdapter()返回null,則表示該設(shè)備不支持藍(lán)牙。
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
// TODO: 設(shè)備不支持藍(lán)牙
Log.d(AssistUtils.TAG, "device not support bluetooth");
}
- 啟用藍(lán)牙
需要確保啟用藍(lán)牙,調(diào)用isEnabled()以檢查當(dāng)前是否已經(jīng)啟用藍(lán)牙,如果此方法返回false,則表示藍(lán)牙處于停用狀態(tài)。在這個(gè)時(shí)候,需要我們使用intent來啟用藍(lán)牙,使用ACTION_REQUEST_ENABLE 操作Intent調(diào)用startActivityForResult()。將通過系統(tǒng)設(shè)置發(fā)出啟用藍(lán)牙的請求
//判斷藍(lán)牙是否可用
if (!bluetoothAdapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent,REQUESTCODE_BLUETOOTH_ENABLECODE);
}
調(diào)用后,將顯示如下圖的對話框,請求用戶允許啟用藍(lán)牙沒如果用戶響應(yīng)yes,并在該進(jìn)程完成后將焦點(diǎn)返回到您的應(yīng)用

傳遞給startActivityForResult()的REQUESTCODE_BLUETOOTH_ENABLECODE常量是在局部定義的整型。系統(tǒng)會(huì)將其作為requestCode參數(shù)傳遞會(huì)onActivityResult()實(shí)現(xiàn)。
如果成功啟用藍(lán)牙,Activity將會(huì)在onActivityResult()回調(diào)中收到RESULT_OK結(jié)果代碼。如果由于某個(gè)錯(cuò)誤(用戶響應(yīng)no)而沒有啟用藍(lán)牙,則結(jié)果代碼為RESULT_CANCELED
除此外還可以選擇偵聽 [ACTION_STATE_CHANGED] 廣播 Intent,每當(dāng)藍(lán)牙狀態(tài)發(fā)生變化時(shí),系統(tǒng)都會(huì)廣播此 Intent。 此廣播包含額外字段 [EXTRA_STATE] 和 [EXTRA_PREVIOUS_STATE],二者分別包含新的和舊的藍(lán)牙狀態(tài)。 這些額外字段可能的值包括 [STATE_TURNING_ON]、[STATE_ON]、[STATE_TURNING_OFF] 和 [STATE_OFF]。偵聽此廣播適用于檢測在您的應(yīng)用運(yùn)行期間對藍(lán)牙狀態(tài)所做的更改。
/**
* @author wxblack-mac
* @DESCRIBE:自定義藍(lán)牙廣播接收者
* @DATE 2018/11/28 11:04
* GOOD LUCK
*/
public class PrinterBlueToothReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int intExtra = intent.getIntExtra(EXTRA_STATE, -1);
Log.d(AssistUtils.TAG, "onReceive: intExtra" + intExtra);
if (intExtra == STATE_TURNING_ON) {
Log.d(AssistUtils.TAG, "onReceive: 藍(lán)牙開啟");
}
}
}
- 注冊廣播
private void registerPrintReceiver() {
printerBlueToothReceiver = new PrinterBlueToothReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(printerBlueToothReceiver, intentFilter);
}
查找設(shè)備
使用BluetoothAdapter 可以通過設(shè)備發(fā)現(xiàn)或通過查詢配對設(shè)備的列表來查找遠(yuǎn)程的藍(lán)牙設(shè)備
設(shè)備發(fā)現(xiàn)是一個(gè)掃描過程,它會(huì)搜索局部區(qū)域內(nèi)已啟動(dòng)藍(lán)牙功能的設(shè)備,然后請求一些關(guān)于各臺(tái)設(shè)備的信息。但是局部區(qū)域內(nèi)的藍(lán)牙設(shè)備僅在其當(dāng)前已啟用可檢測性是才會(huì)響應(yīng)發(fā)現(xiàn)請求。如果設(shè)備可檢測到,設(shè)備將通過共享一些信息(例如設(shè)備名稱、類及其唯一MAC地址)來響應(yīng)發(fā)現(xiàn)請求。利用此信息,執(zhí)行發(fā)現(xiàn)的設(shè)備可以選擇發(fā)起到設(shè)備的連接
在當(dāng)首次與設(shè)備建立連接后,就會(huì)自動(dòng)向用戶顯示配對請求。設(shè)備完成配對后,將會(huì)保存關(guān)于該設(shè)備的基本信息。并且可以使用上面提到過的Bluetooth API讀取這些信息。利用遠(yuǎn)程設(shè)備的已知MAC地址可以隨時(shí)向其
發(fā)起連接,而無需執(zhí)行發(fā)現(xiàn)操作。
被配對與被連接之間是存在差別的,被配對意味著兩臺(tái)設(shè)備知曉彼此的存在,具有可用于身份驗(yàn)證的共享鏈路秘鑰,并且能夠與彼此簡歷加密連接。而被連接意味著設(shè)備當(dāng)前共享一個(gè)RFCOMM通道,并且能夠向彼此傳輸數(shù)據(jù)。當(dāng)前的Android Bluetooth API要求對設(shè)備進(jìn)行配對然后才能建立RFCOMM連接。
也就是說 使用本地藍(lán)牙適配器可以掃描發(fā)現(xiàn)藍(lán)夜設(shè)備,設(shè)備發(fā)現(xiàn)會(huì)掃描啟用藍(lán)牙功能的設(shè)備,然后可以請求啟用了藍(lán)牙發(fā)現(xiàn)設(shè)備的相關(guān)設(shè)備信息,如MAC地址、設(shè)備名稱等。在首次建立設(shè)備連接后,就會(huì)向用戶發(fā)起配對請求,完成配對后,會(huì)保存配對設(shè)備的相關(guān)信息。如果利用遠(yuǎn)程設(shè)備的 唯一的MAC地址時(shí),并且當(dāng)設(shè)備處于發(fā)現(xiàn)狀態(tài)時(shí),可以直接發(fā)起連接,而無需執(zhí)行發(fā)現(xiàn)掃描的操作
查詢配對設(shè)備
在執(zhí)行設(shè)備發(fā)現(xiàn)以前,可以查詢已經(jīng)配對的設(shè)備集,以了解所需的設(shè)備是否處于已知狀態(tài)??梢哉{(diào)用getBonderDevices()。這將返回標(biāo)識(shí)已配對設(shè)備的一組BluetoothDevice.
//獲取已經(jīng)配對的設(shè)備,返回一個(gè)集合
/**
* 查詢配對設(shè)備集
*/
private void findPairDevices() {
Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
if (bondedDevices.size() > 0) {
for (BluetoothDevice bluetoothDevice : bondedDevices) {
Log.d(TAG, "findPairDevices: " + bluetoothDevice.getName() + "\taddress:" + bluetoothDevice.getAddress());
}
}
}
發(fā)現(xiàn)設(shè)備
要開始發(fā)現(xiàn)設(shè)備,需要調(diào)用startDiscovery()。該進(jìn)程為異步進(jìn)程,并且該方法會(huì)立即返回一個(gè)布爾值,表示是否已成功啟動(dòng)發(fā)現(xiàn)操作。啟動(dòng)的發(fā)現(xiàn)進(jìn)程一般包含12秒的查詢掃描,之后對每臺(tái)發(fā)現(xiàn)的設(shè)備進(jìn)行頁面掃描。
App必須針對ACTION_FOUND Intent注冊一個(gè)BroadcastReceiver,以便接收每臺(tái)發(fā)現(xiàn)的設(shè)備的相關(guān)信息。針對每臺(tái)設(shè)備,系統(tǒng)將會(huì)廣播ACTION_FOUNT 的Intent。這個(gè)返回的Intent將攜帶額外的字段 EXTRA_DEVICE和EXTRA_CLASS。分別包含BluetoothDevice 和 BluetoothClass
//獲取發(fā)現(xiàn)的設(shè)備實(shí)體
findDeviceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//獲取發(fā)現(xiàn)的設(shè)備實(shí)體
BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "發(fā)現(xiàn)的設(shè)備" + device.getAddress() + "\tname:" + device.getName());
}
}
};
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(findDeviceReceiver, filter);
執(zhí)行設(shè)備發(fā)現(xiàn)對于藍(lán)牙適配器來說是一個(gè)非常繁重的操作過程,并且會(huì)消耗大量的資源,找到要連接的設(shè)備后,確保使用cancelDiscovery()停止發(fā)現(xiàn),然后嘗試連接,如果已經(jīng)保持與某臺(tái)設(shè)備的連接,執(zhí)行發(fā)現(xiàn)操作可能會(huì)減少可用于該連接的帶寬,不應(yīng)該在處于連接狀態(tài)時(shí)執(zhí)行發(fā)現(xiàn)操作
啟用可檢測性
如果希望本地設(shè)備可以被其他設(shè)備檢測到,請使用ACTION_REQUEST_DISCOVERABLE操作Intent調(diào)用startActivityForResult(),浙江通過系統(tǒng)設(shè)置發(fā)出啟用可檢測到模式的請求。在默認(rèn)情況下,設(shè)備將變?yōu)榭蓹z測到并持續(xù)120秒鐘??梢酝ㄟ^添加EXTRA_DISCOVERABLE_DURATION Intent Extra來定義不同的持續(xù)時(shí)間。應(yīng)用可以設(shè)置的最大持續(xù)時(shí)間為3600秒,值為0則表示設(shè)備始終可檢測到 任何小于0或者大于3600的值都會(huì)自動(dòng)這只為120秒
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
在調(diào)用后,將顯示對話框,請求用戶允許將設(shè)備設(shè)置為可檢測到,如果用戶響應(yīng)yes,則設(shè)備將變?yōu)榭蓹z測到并持續(xù)指定的時(shí)間量。然后您的Activity將會(huì)收到對onActivityResult()回調(diào)的調(diào)用,其結(jié)果代碼等于設(shè)備可檢測到的持續(xù)時(shí)間。如果用戶選擇NO或者出現(xiàn)錯(cuò)誤,結(jié)果代碼將為 [RESULT_CANCELED]`
注:如果尚未在設(shè)備上啟動(dòng)藍(lán)牙,則啟用設(shè)備可檢測性將會(huì)自動(dòng)啟動(dòng)藍(lán)牙
設(shè)備將在分配的時(shí)間內(nèi)以靜默方式保持可檢測到模式。如果希望在可檢測到模式發(fā)生變化時(shí)收到通知,可以針對ACTION_SCAN_MODE_CHANGED intent注冊BroadcastReceiver。接收到的intent將包含額外字段EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE或SCAN_MODE_NONE這些值分別指示設(shè)備處于可檢測到模式、未處于可檢測到模式但仍能接收連接,或未處于可檢測到模式并且無法接收連接
如果您將要發(fā)起到遠(yuǎn)程設(shè)備的連接,則無需啟用設(shè)備可檢測性。僅當(dāng)您希望您的應(yīng)用托管將用戶接受傳入連接的服務(wù)器套接字時(shí),才有必要啟用可檢測性。因?yàn)檫h(yuǎn)程設(shè)備必須能夠發(fā)現(xiàn)該設(shè)備,然后才能發(fā)起連接
連接設(shè)備
要在兩臺(tái)設(shè)備上的應(yīng)用之間創(chuàng)建連接,必須同時(shí)實(shí)現(xiàn)服務(wù)器端和客戶端機(jī)制,因?yàn)槠渲幸慌_(tái)設(shè)備必須開放服務(wù)器套接字,而另一臺(tái)設(shè)備必須發(fā)起連接。當(dāng)服務(wù)器和客戶端在同一RFCOMM通道上分別擁有已連接的BluetoothSocket時(shí),二者將被視為彼此連接。在這種情況下,每臺(tái)設(shè)備都能獲得輸入和輸出流式傳輸,并且可以開始傳輸數(shù)據(jù)。
服務(wù)器設(shè)備和客戶端設(shè)備分別以不同的方法獲得需要的BluetoothSocket。服務(wù)器將在傳入連接被接受時(shí)收到套接字。
客戶端將在其打開到服務(wù)器的RFCOMM通道時(shí)收到該套接字
一種實(shí)現(xiàn)技術(shù)是自動(dòng)將每臺(tái)設(shè)備準(zhǔn)備為一個(gè)服務(wù)器,從而使每臺(tái)設(shè)備開發(fā)一個(gè)服務(wù)器套接字并偵聽連接,然后任一設(shè)備可以發(fā)起與另一臺(tái)設(shè)備的連接,并成為客戶端。或者其中一臺(tái)設(shè)備可顯式“托管”連接并按需開放一個(gè)服務(wù)器套接字,而另一臺(tái)設(shè)備則直接發(fā)起連接
連接為服務(wù)器
當(dāng)您需要連接兩臺(tái)設(shè)備時(shí),其中一臺(tái)設(shè)備必須通過保持開放的BluetoothServerSocket來充當(dāng)服務(wù)器。服務(wù)器套接字的用途是偵聽傳入的連接請求,并在接受一個(gè)請求后提供已經(jīng)連接的BluetoothSocket。從BluetoothServerSocket獲取BluetoothSocket后,可以舍棄BluetoothServerSocket。除非需要更多的連接時(shí)可以不用舍棄
關(guān)于UUID
通用唯一標(biāo)識(shí)符 UUID 是用于唯一標(biāo)識(shí)信息的字符串ID的128位標(biāo)準(zhǔn)化格式。UUID的特點(diǎn)是其足夠龐大,因此,可以選擇任意隨機(jī)值而不會(huì)發(fā)生沖突??梢员挥糜谖ㄒ粯?biāo)識(shí)應(yīng)用的藍(lán)牙服務(wù)
設(shè)置服務(wù)器套接字并接受連接的基本過程:
1.通過調(diào)用listenUsingRfcommWithServiceRecord(String,UUID)獲取BluetoothServerSocket
該字符串是服務(wù)的可識(shí)別名稱,系統(tǒng)會(huì)自動(dòng)將其寫入到設(shè)備上的新服務(wù)發(fā)現(xiàn)協(xié)議(SDP)數(shù)據(jù)庫條目(可使用任意名稱)。UUID也包含在SDP條目中,并且將作為與客戶端設(shè)備的連接協(xié)議的基礎(chǔ)。當(dāng)客戶端嘗試連接此設(shè)備時(shí),它會(huì)攜帶能夠唯一標(biāo)識(shí)其想要連接的服務(wù)的UUID。兩個(gè)UUID必須匹配,在下一步中,連接才會(huì)被接受
2.通過調(diào)用accept()開始偵聽連接請求
阻塞調(diào)用,將在連接被接受或發(fā)生異常時(shí)返回。僅當(dāng)遠(yuǎn)程設(shè)備發(fā)送的連接請求中所包含的UUID與向此偵聽服務(wù)器套接字注冊的UUID相匹配時(shí),連接才會(huì)被接受。操作成功后,accept()將會(huì)返回已連接的BluetoothSocket
3.除非想要接受更多連接,否則請調(diào)用close()
這將釋放服務(wù)器套接字及其所有資源。但不會(huì)關(guān)閉accept()所返回的已連接的BluetoothSocket。與TCP/IP不同,RFCOMM一次只允許每個(gè)通道有一個(gè)已連接的客戶端。
accept()調(diào)用不應(yīng)在主Activity UI線程中執(zhí)行,因?yàn)槭亲枞{(diào)用會(huì)造成ANR。
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) { }
}
}
連接為客戶端
要發(fā)起與遠(yuǎn)程設(shè)備的連接,必須先獲取標(biāo)識(shí)該遠(yuǎn)程設(shè)備的BluetoothDevice對象。然后,必須使用BluetoothDevice來獲取BluetoothSocket并發(fā)起連接
基本過程
- 使用
BluetoothDevice,通過調(diào)用createRfcommScoketToServiceRecord(UUID)獲取BluetoothSocket
這將初始化將要連接到BluetoothDevice的BluetoothSocket。此處傳遞的UUID必須與服務(wù)器設(shè)備在使用listenUsingRfcommWithServiceRecord(String,UUID)開放其BluetoothServerSocket所使用的UUID相匹配。 - 通過調(diào)用
connect()發(fā)起連接
執(zhí)行此調(diào)用時(shí),系統(tǒng)將會(huì)在遠(yuǎn)程設(shè)備上執(zhí)行SDP查找,以便匹配UUID。如果查找成功并且遠(yuǎn)程設(shè)備接受了該連接。它將共享RFCOMM通道以便在連接期間使用。并且connect()將會(huì)返回。此方法為阻塞調(diào)用,應(yīng)當(dāng)在主線程UI線程之外的線程執(zhí)行。
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) { }
}
}
在建立連接之前會(huì)調(diào)用cancelDiscovery(),在進(jìn)行連接之前應(yīng)始終執(zhí)行此調(diào)用,而且調(diào)用時(shí)無需實(shí)際檢查其是否正在運(yùn)行(isDiscovering()進(jìn)行檢查)
管理連接
在成功連接兩臺(tái)(多臺(tái))設(shè)備之后,每臺(tái)設(shè)備都會(huì)有一個(gè)已連接的BluetoothSocket.使用BluetoothSocket傳輸數(shù)據(jù)的方式為:
- 獲取InputStream和OutputStream.二者分別通過套接字以及
getInputStream()和getOutoputStream()來處理傳輸數(shù)據(jù)
2.使用read(buye[])和write(byte[])讀取數(shù)據(jù)并寫入到流式傳輸
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ì)等待數(shù)據(jù)從輸入流中流出。當(dāng)read(byte[])返回字節(jié)時(shí),數(shù)據(jù)將通過父類的Handler被發(fā)送至Activity。之后再返回并等待更多的字節(jié)流。
發(fā)送數(shù)據(jù)則僅僅需要簡單地調(diào)用線程的write()方法即可。
線程中的cancel()方法很重要,因?yàn)檫B接可以隨時(shí)在任意時(shí)間通過BluetoothSocket終止。該方法在結(jié)束使用藍(lán)牙連接后,應(yīng)當(dāng)總是被調(diào)用。