藍牙
注:本文翻譯自https://developer.android.com/guide/topics/connectivity/bluetooth.html
Android平臺提供了對藍牙網絡棧的支持,藍牙網絡棧可以讓一臺設備與另一臺藍牙設備之間實現無線數據交換。Android平臺通過Android藍牙API提供對藍牙的訪問能力。這些API能讓這些API與其他藍牙設備無線連接,具有通過點對點和多點無線特性。
通過藍牙API,Android應用可是做下面的操作:
- 掃描其他的藍牙設備
- 為已經匹配的藍牙設配查詢本地藍牙適配器。
- 發布RFCOMM通道
- 通過服務查詢鏈接到其他設備
- 傳輸數據給其他設備
- 管理多個鏈接
本文描述如何使用Classic Bluetooth,Classic Bluetooth是針對較多對較多耗電敏感的操作例如流媒體和通信正確的選擇。對只需低耗電的設備,Android 4.3 (API 18版)引入用于支持藍牙低功耗的API。參見 Bluetooth Low Energy了解更多內容。
基礎
本文檔描述了如何使用Android藍牙APIs來完成使用藍牙進行通信所需要的四個主要任務:設置藍牙,檢索周圍匹配的或者可用的設備,連接設備以及設備間傳輸數據。所有藍牙APIs在android.bluetooth 包中。
下面是對創建藍牙連接所要用到的類和接口的一個總結。
BluetoothAdapter
表示本地藍牙適配器(藍牙無線電廣播)。BluetoothAdapter是所有藍牙交互的入口點。你能夠通過它發現其它藍牙設備,查詢一系列已經匹配的設備,使用已知的MAC地址實例化一個 BluetoothDevice,創建 BluetoothServerSocket 監聽來自其他設備的通信。BluetoothDevice
表示遠程藍牙設備。用這個類通過 BluetoothSocket能夠請求同遠程設備的鏈接,或者查詢設備的名字、地址、類和綁定狀態。
BluetoothSocket
表示藍牙套接字通信(類似于TCP Socket)的接口。這是允許應用通過InputStream和OutputStream與其他藍牙設備進行數據交換的連接點。BluetoothServerSocket
表示用于監聽即將到來的請求的對外公開套接字(類似于TCP ServerSocket)。為了連接兩個安卓設備,一個設備必須用這個類開放一個服務端的套接字。當遠程的藍牙設備向該設備發起連接請求時,在連接建立的時候BluetoothServerSocket會返回一個BluetoothSocket。BluetoothClass
描述藍牙設備的普遍的特征和功能。這是一個只讀的屬性集合,它定義了設備的主要和次要的設備類及服務。然而,它并沒有可靠地描述所有的藍牙配置及設備所支持的服務,但是作為設備類型的提示是很有用的。BluetoothProfile
表示藍牙配置文件的接口。一個藍牙配置文件是一個無線接口規格說明書,用于基于藍牙設備間的通信。一個示例就是免提裝置配置文件,想了解更多有關配置文件的內容,參見Working with Profiles。BluetoothHeadset
它為同移動手機一起使用的藍牙耳機提供支持。這包括藍牙耳機和免提裝置配置文件。BluetoothA2dp
定義了通過藍牙連接能夠從一個設備傳輸多么高質量的音頻到另外一個設備上。"A2DP"表示高級音頻發布配置文件(Advanced Audio Distribution Profile)。BluetoothHealth
表示能夠控制藍牙服務的醫療設備配置文件代理。BluetoothHealthCallback
用于實現BluetoothHealth 回調的抽象類。你必須繼承這個類并且實現其中的回調方法來接收與應用注冊狀態和藍牙通道狀態有關的的變化信息。BluetoothHealthAppConfiguration
表示藍牙醫療第三方應用所注冊的用來與遠程藍牙醫療設備通信的配置信息。BluetoothProfile.ServiceListener
用于通知 BluetoothProfile跨進程通信的客戶端已經連接或者從服務端斷開鏈接的接口(也就是說,內部服務運行了一個配置文件)。
藍牙權限
為了在你的應用中使用藍牙特性,你必須聲明藍牙權限 BLUETOOTH。你需要這個權限來執行任何藍牙通信,例如請求連接,接受連接以及傳輸數據。
如果你希望你的應用初始化設備感應或者操作藍牙設置,你也必須聲明 BLUETOOTH_ADMIN 權限。大多數應用為了具有感應周圍藍牙設備的能力,需要單獨申明這個權限。除非這個應用是一個能夠根據用戶請求修改藍牙設置的超級管理員,該權限所賦予的其他功能不應被當使用。注意:如果使用 BLUETOOTH_ADMIN 權限,你仍須申明 BLUETOOTH權限。
在你的manifest文件中聲明藍牙權限。例如:
<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
...
</manifest>
參見 <uses-permission>了解更多有關申明配置權限的信息。
設置藍牙
在應用能夠通過藍牙通信之前,你需要校驗設備是否支持藍牙,如果支持,請確保藍牙是出于開啟狀態。如果不支持藍牙,你應當優雅地禁止掉任何藍牙功能。如果支持藍牙但是沒有打開,你可以在不離開應用的情況下請求應用開啟藍牙,這個設置通過使用BluetoothAdapter在兩步內完成。
- 獲取 BluetoothAdapter
所有的藍牙Activity都需要BluetoothAdapter 。為了得到BluetoothAdapter,調用靜態方法getDefaultAdapter() 。這個方法會返回一個BluetoothAdapter,他表示設備自身的藍牙適配器(藍牙無線電廣播)。整個系統只存在一個藍牙適配器,你的應用通過它來交互。如果 getDefaultAdapter()返回為null則表示設備不支持藍牙,只能到此為止。例如:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}
- 打開藍牙
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
會顯示一個對話框請求用戶權限來啟用藍牙,如圖1所示。如果用戶點擊“yes”,則系統會開始啟用藍牙,并且一旦你的應用處理完畢(或者失敗),界面焦點將會回到你的應用。
傳輸給 [startActivityForResult()](https://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int))的常量REQUEST_ENABLE_BT
是一個本地定義的整型(必須大于零),在[onActivityResult()](https://developer.android.com/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent))實現中,系統將這個整型作為requestCode
參數返回給你。
如果藍牙啟動成功,在 [onActivityResult()](https://developer.android.com/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent)) 回調中,你的Activity接收到RESULT_OK結果碼。如果藍牙由于某一個錯誤沒有啟動,則返回碼為 RESULT_CANCELED。
此外,你的應用同樣能也夠監聽Intent廣播 ACTION_STATE_CHANGED,每當藍牙狀態發生改變,系統就會發送此廣播。這個廣播包含額外的參數 EXTRA_STATE和 EXTRA_PREVIOUS_STATE, 分別包含新的和舊的藍牙狀態。這些額外的參數可能的值有 STATE_TURNING_ON, STATE_ON,STATE_TURNING_OFF,以及 STATE_OFF。在你的應用運行的時候監測藍牙狀態改變,這時偵聽廣播變得重要。
小貼士:開啟藍牙可見性會自動開啟藍牙。如果打算在執行藍牙Activity之前開啟設備的可見性,你可以跳過上面的兩部,參考下方內容開啟可見性。
查找設備
使用 BluetoothAdapter,你能夠找到遠程藍牙設備,不論是通過設備查找還是通過查詢已匹配設備列表。
設備感應是一個掃描的過程,會查詢周圍已經開啟藍牙的設備,然后請求每一個設備的相關信息(有時候會將這個過程稱之為感應、檢索或者掃描),如果一臺設備可以被感應到,它會通過共享一些諸如設備名稱,類型以及它的唯一MAC地址來響應感應請求。利用這些信息,執行查找的設備能夠選擇去初始化連接到一個已經感應到的設備。
一旦與遠程的設備第一次建立起了連接,匹配請求會自動地呈現給用戶,當一臺設備匹配成功,設備相關的基本信息會被保存起來并且可以利用藍牙API來讀取。使用已有的遠程設備MAC地址可以在仍和時候建立連接而無需執行感應(假設設備實在感應范圍內)
記住正在配對和正在連接之間是存在差別的。進行匹配意味著兩個設備都知道各自的存在,擁有一個能夠用來授權的共享連接密鑰,并且能夠互相建立一個加密的連接。進行連接意味著當前設備間共享一個RFCOMM信道,并且能夠互相傳遞數據。
目前Android藍牙APIs在一個RFCOMM連接建立之前要求設備已經配對。(當你使用藍牙APIs初始化一個加密連接的時候,匹配是自動被執行的。)
下面的章節描述如何找到已經匹配的設備,或者使用設備感應感應新的設備。
Note:Android平臺的設備默認是不能夠被感應到的。用戶可以通過系統設置讓設備在一個有限的時間內能夠被感應到。或者在不離開當前應用的情況下,能夠請求用戶使得設備能夠可以被感應,下面的內容講如何讓設備被感應到。
查詢已匹配的設備
在執行設備感應之前,查詢已匹配設備集合,來確認期望設備可見是有價值的。要做到這一點,調用getBondedDevices()。這會返回代表已匹配設備BluetoothDevice
的一個集合。例如,你能夠查詢所有已匹配設備,然后使用ArrayAdapter,將每個設備的名稱顯示給用戶。
為了初始化一個連接,只需要 BluetoothDevice 對象中的MAC地址參數。在這個例子中,它被保存為一個ArrayAdapter的一部分用于顯示給用戶。MAC地址可以在稍后為了初始化連接的時候再提取出來。你可以在 Connecting Devices章節中了解更多有關創建一個連接的內容。
感應設備
只需簡單地調用startDiscovery()即可開始感應設備。這個處理過程是異步的,并且方法會立即返回一個boolean值,用于指明感應操作是否已經成功啟動。感應過程通常包含一個大約十二秒的查詢掃描操作,緊接著的頁面掃描每個已找到的設備取出它的藍牙名稱。
為了接收關于每個感應到的設備的信息,你的應用必須為 ACTION_FOUND intent注冊一個BroadcastReceiver。對每個設備,系統將會廣播 ACTION_FOUND intent。這個intent攜帶額外的參數 EXTRA_DEVICE和 EXTRA_CLASS,分別包含一個相應的BluetoothDevice 和一個 BluetoothClass,例如,下面演示了當設備被發現的時候,你可以如何注冊對廣播的處理。
// 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
為了初始化一個連接,只需要來自 BluetoothDevice 的是MAC地址。在上面的例子中,將其保存為ArrayAdapter 的一部分展示給用戶。MAC地址可以在稍后為了初始化連接的時候再提取出來。你可以在Connecting Devices章節中了解更多有關創建一個連接的內容
注意: 對于藍牙適配器,執行設備查詢是一個重度操作,將會消耗很多它的資源。一旦你發現了一個要去連接的設備,在你試圖開始連接之前,確認你總是使用cancelDiscovery()來停止查找。同樣,如果你已經和一臺設備保持著一個連接,則執行查找會很大程度上削減這個連接的可用帶寬,因此當你已經連接時,你不應當執行查找操作。
開啟可見性
如果你希望本地設備對其他設備是可見的,調用 [startActivityForResult(Intent, int)](https://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int)),并且使用ACTION_REQUEST_DISCOVERABLE action Intent。這會通過系統設置發出一個開啟可見性模式的請求(同時不會停止你的應用)。默認的,這個設備會在120秒內成為可被查找的。通過添加Intent EXTRA_DISCOVERABLE_DURATION extra數據,你能夠定義一個不同的持續時間。一個app能夠設定的最大持續時間為3600秒,并且0意味著這個設備總是可以被查找到的。任何小于0并且大于3600的值都將自動設置為120秒。例如,如下程序片段設置持續時間為300:
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
會顯示一個對話框,請求開啟設備可見性的用戶權限,如圖2所示。如果用戶響應“Yes”,則設備在給定的時間內將會成為可被查找的。然后你的activity將會收到對 [onActivityResult())](https://developer.android.com/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent))回調方法的調用。回調方法返回的result code和設備可見性的持續時間是相等的。如果用戶響應“No.”或者如果發生了一個錯誤,則這個result code將會是 RESULT_CANCELED。
注意:如果在此設備上沒有打開藍牙,則啟用設備可見性將會自動啟用藍牙。
設備在分配的時間后臺里將會保持可見模式。當可被查找模式發生改變的時候,如果你想得到提醒,你可以為 ACTION_SCAN_MODE_CHANGED intent注冊一個BroadcastReceiver。這會包含額外的參數 EXTRA_SCAN_MODE以及 EXTRA_PREVIOUS_SCAN_MODE,它們分別告訴你新的和舊的掃描模式。它們各自可能的值有 SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE,或者 SCAN_MODE_NONE,這表明這個設備在可見模式,或者不在可見模式但是依舊能夠收到連接,或者不在可見模式并且不能夠收到連接。如果你將初始化到一個遠程設備的連接,你不需要啟用設備的可見性。只有當你希望你的應用保持一個server socket的時候,你才需要啟用可見性,這個server socket用于接收新來到的連接,因為遠程設備在能夠初始化連接之前,它必須能夠感應到這個設備。
連接設備
為了能夠在兩臺設備的應用之間創建一個連接,你必須實現客戶端以及服務端的機制,因為一臺設備必須打開一個server socket,同時另外一個必須初始化連接(使用服務端設備的MAC地址來初始化一個連接)。當兩臺設備在相同的RFCOMM信道中各有一個已連接的 BluetoothSocket 的時候,服務端和客戶端被認為是互相連接的。此時每臺設備能夠獲得輸入和輸出流并且開始數據傳輸,本節將介紹如何在兩臺設備之間初始化連接。
服務端和客戶端各自以不同的方式獲得所需的BluetoothSocket 。當一個來到的連接被接受的時候,服務端將會收到BluetoothSocket。當客戶端打開一個連接服務端的RFCOMM信道的時候,它會收到它的BluetoothSocket。
一個技術實現就是自動將每個設備準備好作為一個服務端,因此每個設備都將打開一個server socket并且偵聽連接。然后每個設備都能夠初始化一個到另外一個設備的連接并且成為客戶端。另外一方面,一個設備能夠顯示地保持住這個連接并且根據需要打開一個server socket,另外一個設備能夠簡單地初始化這個連接。
注意:如果兩臺設備之前沒有配對過,則在連接過程中,Android框架層會自動給用戶顯示一個配對請求提示或者對話框,如圖3所示。因此當試圖連接設備的時候,你的應用不需要考慮,你的設備是否已經配對過。在用戶成功配對之前,你的RFCOMM連接請求會一直阻塞,如果用戶拒絕配對的話則RFCOMM連接請求會失敗,這也有可能是配對失敗或者請求超時。
作為服務端來連接
當你希望連接兩臺設備的時候,其中一臺必須通過保持一個打開的BluetoothServerSocket來作為服務器。Server socket的目的是在接受用于偵聽到來的連接請求,同時當一臺接受請求的時候,提供一個已連接的 BluetoothServerSocket。當從 BluetoothServerSocket獲得 BluetoothSocket 的時候,BluetoothServerSocket應當撤銷,除非你希望接受更多的連接。
關于UUID
通用唯一標識符(UUID)是為字符串ID而生成的標準化128bit格式,用于唯一標示信息。UUID的關鍵點是它足夠大,以至于你任意隨即選擇都不會產生沖突。在此情況中,它被用于標識你應用的藍牙服務。為了獲得一個你的應用可以使用的UUID,你可以從眾多的UUID生成器中任意選擇一個,然后通過fromString(String)實例化一個UUID。
下面是設置一個server socket以及獲得一個連接的基本處理流程:
調用 [listenUsingRfcommWithServiceRecord(String, UUID)](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingRfcommWithServiceRecord(java.lang.String, java.util.UUID))得到一個 BluetoothServerSocket。
生成的字符串是你設備的一個標識,系統會自動將這個字符串寫入到位于設備上的新的服務查找協議(SDP)數據庫條目中(名字是任意的,可能就是你的應用名字)。UUID也包含在SDP條目中,并且是允許連接到客戶端設備的基礎。即是,當客戶端試圖去連接這個設備的時候,它會攜帶一個UUID,這個UUID能夠唯一識別它希望連接的服務。為了連接能夠被接受,這些UUID必須匹配(見下一步)。通過調用accept()開始偵聽連接請求
這是一個阻塞調用。當有一個連接被接受或者發生一個異常的時候,調用才會返回。只有當遠程設備發送一個連接請求,并且帶有注冊在監聽服務socket中相匹配的UUID的時候,連接才會被接受。當連接成功的時候, accept()會返回一個已連接的 BluetoothSocket。調用 close(),除非你希望接收其他的連接。
這個操作會釋放掉server socket以及所有它的資源,但是并不關閉 accept()返回的已連接的BluetoothSocket。不像TCP/IP,RFCOMM在同一時刻在每個信道中,僅僅允許一個已連接的客戶端,因此在大多數情況下,在接收一個已連接socket之后,立刻調用BluetoothServerSocket 中 close()是有意義的。
調用 accept() 不應當在主Activity UI線程中被執行,因為它是一個阻塞調用,會阻止任何其他同應用的交互。通常在你的應用中,所有 BluetoothServerSocket或者 BluetoothSocket的操作都應當在一個新的線程中完成。要想終止一個像 accept()這樣的阻塞調用,調用來自另外一個線程中 BluetoothServerSocket(或者 BluetoothSocket)中的 close() 方法,此時阻塞調用會立即返回結果。注意所有在 BluetoothServerSocket或者 BluetoothSocket中的方法都是線程安全的。
示例
下面是用于接收來到連接請求的服務端組件做了簡化的線程。
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) { }
}
}
在本例中,僅希望有一個到來的連接,因此只要接收到連接,并且獲得 BluetoothSocket ,應用程序就會發送獲得的 BluetoothSocket 到一個獨立的線程中,關閉 BluetoothServerSocket 并且終止循環。
注意到當accept()返回 BluetoothSocket的時候,這個socket已經連接上了,因此你應當調用 connect()。
manageConnectedSocket()
是應用中虛構的一個方法,會初始化用于傳輸數據的線程,這會在管理一個連接章節中討論。
只要你一完成監聽到來連接操作,你就應當關閉 BluetoothServerSocket。在本例中,一旦獲得了 BluetoothSocket就調用 close()方法。你也可以在你的線程中提供一個公共方法,它能夠在你需要停止監聽server socket的時候關閉私有屬性 BluetoothSocket 。
作為客戶端來連接
為了初始化一個到遠程設備(保持者一個打開的server socket的設備)的連接,你必須首先獲得一個代表遠程設備的 BluetoothDevice 對象(獲取 BluetoothDevice已經在上面的 查詢設備章節中提到)。然后你必須使用BluetoothDevice去獲取一個 BluetoothSocket 并且初始化這個連接。
這里是基本的操作流程:
- 使用 BluetoothDevice,通過調用 createRfcommSocketToServiceRecord(UUID)到一個 BluetoothSocket.
這將初始化一個連接到 BluetoothDevice的BluetoothSocket。當服務端打開它的 BluetoothServerSocket(使用 [listenUsingRfcommWithServiceRecord(String, UUID)](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingRfcommWithServiceRecord(java.lang.String, java.util.UUID)),傳遞到這里的UUID必須和服務端設備所使用的UUID相匹配。使用同樣的UUID就是簡單地將UUID字符串硬編碼進你的應用,并且在服務端和客戶端代碼中引用它。 - 調用 connect()初始化連接
一旦調用這個接口,為了匹配UUID,系統會在遠程設備上執行一個SDP查詢操作。如果查詢成功,并且遠程設備接收這個連接,則在連接期間它會共享使用RFCOMM信道,并且connect()調用也會返回。這個方法是一個阻塞調用。如果因為任何原因,連接失敗或者connect() 方法超時(大約超過12秒),則它會拋出一個異常。
因為connect() 是一個阻塞調用,這個連接處理應當總是在主activity線程之外的一個獨立線程中執行。
Note:你應當總是確保,當你調用connect()的時候,你的設備沒有在執行設備查找操作。如果正在處理查找,則連接請求將會非常慢并且很有可能失敗。
示例
下面是一個線程中初始化一個藍牙連接的基本示例
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()是在建立連接之前調用的。你應當在連接之前總是執行這個操作,并且不用檢查是否在運行,調用cancelDiscovery()總是安全的(如果你確實想檢查,調用 isDiscovering())。
manageConnectedSocket()在應用程序中是一個虛構的方法,用于初始化傳輸數據的線程,這將在管理連接中討論。
當你用完 BluetoothSocket,一定要調用 close()來完成清理工作。這么做會立即關閉掉已連接的socket并且清理所有中間資源。
管理連接
當你成功連接兩個(或者更多)設備的時候,每個都擁有一個已連接的 BluetoothSocket。這便開始變得比較有趣,因為你能夠在設備之間共享數據。使用 BluetoothSocket,任意傳輸數據的通常處理是簡單的:
通過socket,分別使用 getInputStream() 和 getOutputStream()得到InputStream和OutputStream來處理傳輸。
使用 read(byte[])和 write(byte[])分別讀數據以及寫數據到流中。
就醬!
當然有一些實現細節需要考慮。首先,你應當使用一個專門的線程來處理所有流的讀寫。這是非常重要的,因為所有 read(byte[])以及write(byte[]) 方法都是阻塞調用。 read(byte[])會一直阻塞,直到存在某些東西從流中取讀,write(byte[])通常不會阻塞,但是如果遠程設備沒有足夠快地調用 read(byte[]),就會阻塞流程控制,并且中間緩沖會溢出。因此,你線程中的主循環應當專門被用于從InputStream中讀數據。在線程中的一個獨立的公共方法,能夠被用于實例化到OutputStream中的寫操作。
示例
下面是一個連接管理的可能的范例:
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) { }
}
}
構造函數獲得所需的流并且一旦執行,線程就會通過InputStream等待數據的到來。當 read(byte[])從流中返回字節的時候,使用來自父類中的成員Handler,數據被傳送到主Activity中。然后它會返回并且等待來自stream的更多的字節。
發送向外的數據就像調用主Activity的線程中write()
方法一樣簡單。然后這個方法簡單地調用 write(byte[])發送數據到遠程設備。
線程中的cancel()
方法非常重要,因為通過關閉BluetoothSocket,連接能夠在任何時候被終止。當你使用完藍牙連接之后,你應當總是調用這個方法。
一個作為使用Bluetooth APIs范例, 參見 Bluetooth Chat sample app
使用Profiles
從Android3.0開始,藍牙API支持使用藍牙Profiles。一個藍牙profile是一個無線接口規格說明,用于支持設備間基于藍牙的通信。一個例子就是Hands-Free profile。對于一個移動電話連接到一個無線耳機,兩個設備都必須支持Hands-Free profile。
通過實現接口 BluetoothProfile ,你能夠寫你自己的類來支持一個特別的藍牙Profile。Android藍牙API提供如下藍牙profiles的實現:
耳機 耳機配置文件對于移動手機使用藍牙耳機提供支持。Android提供 BluetoothHeadset類,它是通過跨進程通信(IPC)來控制藍牙耳機服務的代理。其中包括藍牙耳機以及Hands-Free(V1.5)profiles。 BluetoothHeadset類包含對AT命令的支持,想了解更多有關這部分的內容,參見 制造商定制AT命令。
A2DP 高級音頻發布配置文件(Advanced Audio Distribution Profile profile 簡稱A2DP)定義了通過一個藍牙連接將多高質量的音頻能夠從一臺設備傳輸到另外一臺。Android提供 BluetoothA2dp 代理類,通過跨進程通信(IPC)控制藍牙A2DP。
醫療設備 ** Android4.0(API 14)引入對藍牙醫療設備(Health Device Profile 簡稱HDP)的支持。這允許你創建應用,同支持藍牙的醫療設備進行通信,例如心率監測,血壓,溫度計,定標器等等。對于支持的設備列表以及它們對應設備數據特殊碼(device data specialization codes),參見www.bluetooth.org.中的Bluetooth Assigned Numbers*,注意這些值也在 ISO/IEEE 11073-20601 [7] 規格書中作為在命名規范附件(Nomenclature Codes Annex )中的MDC_DEV_SPEC_PROFILE_被引用。想了解更多內容,參見 醫療設備配置文件
這里是使用配置文件的基本步驟:
得到默認的adapter,如設置藍牙章節所述。
使用 [getProfileProxy()](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int)) 建立相關配置文件的配置文件代理對象的一個連接。在如下的示例中,配置文件代理對象是 BluetoothHeadset的一個實例。
建立一個 BluetoothProfile.ServiceListener。當BluetoothProfile IPC客戶端已經連接到服務或者和服務斷開連接的時候,會得到listener的通知。
在 [onServiceConnected()](https://developer.android.com/reference/android/bluetooth/BluetoothProfile.ServiceListener.html#onServiceConnected(int, android.bluetooth.BluetoothProfile))中,獲得一個配置代理對象的句柄。
一旦你獲得配置代理對象,你能夠使用它來監測連接的狀態,并且執行和那個配置文件相關的其他操作。
例如,如下代碼片段顯示如何連接到一個BluetoothHeadset代理對象,通過它你能夠控制耳機配置文件:
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);
制造商定制AT命令
從Android3.0開始,應用能夠注冊,接收耳機發送的,預先定義的,制造商預先定制的AT命令的系統廣播(例如Plantronics + XEVENT命令)。例如,一個應用能夠接收支持已連接設備電池電量的廣播,并且提醒用戶采取其他需要的行動。為ACTION_VENDER_SPECIFIC_
HEADSET_EVENT intent 創建一個廣播接收器,為耳機操作制造商定制的AT命令。
醫療設備配置文件(HDP)
Android 4.0(API 14版)引入對藍牙醫療設備配置文件(HDP)的支持。它能讓你創建這樣的應用程序,它能夠使用藍牙和支持藍牙的醫療設備進行通信,例如心率監測,血壓,溫度計,定標器等等。藍牙醫療API包括類 BluetoothHealth, BluetoothHealthCallback, BluetoothHealthAppConfiguration,這些已經在基礎部分描述過。
要使用醫療設備API,了解下面這些醫療設備配置文件的中的關鍵概念是很有幫助的。
| 概念 | 說明 |
|:----- :| ------ |
|Source | 定義在HDP中的角色。一個source代表一個醫療設備(體重計,葡萄糖計量儀,溫度計等等。),此設備將醫學數據傳輸到例如Android手機或者平板的一個智能設備中。 |
| Sink | 定義在HDP中的角色。一個sink就是接收醫學數據的那個智能設備。在一個Android HDP應用中,Sink通過一個BluetoothHealthAppConfiguration對象來體 |
| Registration | 指為一個特定的醫療設備注冊一個sink |
|Connection | 指在一個智能設備(例如一個Android手機或者平板)和一個醫療設備之間打開一個信道(channel ) |
創建一個HDP應用
下面是創建一個Android HDP應用所需的基本步驟:
獲得一個 BluetoothHealth 代理對象的引用。
類似于常規的耳機和A2DP配置文件設備,你必須用BluetoothProfile.ServiceListener 和HEALTH配置文件類型來調用 [getProfileProxy()](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int)),以便和這個配置代理對象建立一個連接。創建一個 BluetoothHealthCallback,并注冊一個應用配置(BluetoothHealthAppConfiguration)作為一個醫療sink。
建立同醫療設備的連接。某些設備會初始化這個連接。對于那些設備不需要執行這個步驟。
當同一個醫療設備連接成功的時候,使用文件描述符( file descriptor)對醫療設備進行讀寫。 接收到的數據需要使用一個實現了IEEE 11073-xxxxx規范升的醫療管理器進行解析。
當完成操作,關閉醫療信道并且注銷這個應用。當處于長時間不運行狀態的時候,信道也會關閉。
完整的實現步驟相關代碼,可以參考Android Bluetooth HDP (Health Device Profile).。