Android平台包含了對藍牙網絡協議棧的支持,它允許一個藍牙設備跟其他的藍牙設備進行無線的數據交換。應用程序通過Android藍牙API提供訪問藍牙的功能。這些API會把應用程序無線連接到其他的藍牙設備上,具有點到點和多點無線特征。
使用藍牙API,Android應用程序能夠執行以下功能:
1. 掃描其他藍牙設備
2. 查詢本地已經配對的藍牙適配器
3. 建立RFCOMM通道
4. 通過服務發現來連接其他設備
5. 在設備間傳輸數據
6. 管理多個藍牙連接
基礎
本文介紹如何使用Android的藍牙API來完成使用藍牙通信所需要的四項主要任務:設置藍牙、查找已配對或區域內可用的藍牙設備、連接設備、設備間傳輸數據。
所有的可用的API都在android.bluetooth包中。以下概要的介紹創建藍牙連接所需要的類和接口:
BluetoothAdapter
代表本地藍牙適配器(藍牙無線)。BluetoothAdapter是所有藍牙交互的入口。使用這個類,你能夠發現其他的藍牙設備,查詢已配對設備的列表,使用已知的MAC地址來實例化一個BluetoothDevice對象,並且創建一個BluetoothServerSocket對象來監聽與其他設備的通信。
BluetoothDevice
代表一個遠程的藍牙設備。使用這個類通過BluetoothSocket或查詢諸如名稱、地址、類和配對狀態等設備信息來請求跟遠程設備的連接。
BluetoothSocket
代表藍牙socket的接口(類似TCP的Socket)。這是允許一個應用程序跟另一個藍牙設備通過輸入流和輸出流進行數據交換的連接點。
BluetoothServerSocket
代表一個打開的監聽傳入請求的服務接口(類似於TCP的ServerSocket)。為了連接兩個Android設備,一個設備必須用這個類打開一個服務接口。當遠程藍牙設備請求跟本設備建立連接請求時,BluetoothServerSocket會在連接被接收時返回一個被連接的BluetoothSocket對象。
BluetoothClass
描述了藍牙設備的一般性特征和功能。這個類是一個只讀的屬性集,這些屬性定義了設備的主要和次要設備類和服務。但是,這個類並不保證描述了設備所支持的所有的藍牙配置和服務,但是這種對設備類型的提示是有益的。
BluetoothProfile
代表一個藍牙配置的接口。藍牙配置是基於藍牙通信的設備間的無線接口規范。一個例子是免提的配置。更多的配置討論,請看下文的用配置來工作。
BluetoothHeadset
提供對使用藍牙耳機的移動電話的支持。它同時包含了Bluetooth Headset和Hands-Free(v1.5)的配置。
BluetoothA2dp
定義如何把高品質的音頻通過藍牙連接從一個設備流向另一個設備。“A2DP”是Advanced Audio Distribution Profile的縮寫。
BluetoothHealth
代表一個健康保健設備配置的控制藍牙服務的代理。
BluetoothHealthCallback
用於實現BluetoothHealth回調的抽象類。你必須繼承這個類,並實現它的回調方法,來接收應用程序的注冊狀態和藍牙通道狀態變化的更新。
BluetoothHealthAppConfiguration
代表藍牙相關的第三方健康保健應用程序所注冊的與遠程藍牙健康保健設備進行通信的配置。
BluetoothProfile.ServiceListener
BluetoothProfile IPC客戶端連接或斷開服務的通知接口(它是運行特俗配置的內部服務)。
Android的聯通性---Bluetooth(二)
藍牙權限
為了在你的應用程序中使用藍牙功能,至少要聲明兩個藍牙權限(BLUETOOTH和BLUETOOTH_ADMIN)中的一個。
為了執行任何藍牙通信(如請求連接、接收連接和傳輸數據),你必須申請BLUETOOTH權限。
為了啟動設備發現或維護藍牙設置,你必須申請BLUETOOTH_ADMIN權限。大多數需要這個權限的應用程序,僅僅是為能夠發現本地的藍牙設備。這個權限所授予的其他能力應該不被使用,除非是電源管理的應用程序,它會在依據用戶的請求來修改藍牙設置。注意:如果你使用了BLUETOOTH_ADMIN權限,那麼必須要有BLUETOOTH權限。
在你的應用程序清單文件中聲明藍牙權限,例如:
...
< /manifest>
關於聲明應用程序權限的更多信息,請參閱
設置藍牙
在應用程序能夠利用藍牙通道通信之前,需要確認設備是否支持藍牙通信,如果支持,要確保它是可用的。
如果不支持藍牙,那麼你應該有好的禁用所有藍牙功能。如果支持藍牙,但是被禁用的,那麼你要在不離開你的應用程序的情況下,請求用戶啟用藍牙功能,這種設置要使用BluetoothAdapter對象,在以下兩個步驟中完成。
1. 獲得BluetoothAdapter對象
BluetoothAdapter對象是所有藍牙活動都需要的,要獲得這個對象,就要調用靜態的getDefaultAdapter()方法。這個方法會返回一個代表設備自己的藍牙適配器的BluetoothAdapter對象。整個系統有一個藍牙適配器,你的應用程序能夠使用這個對象來進行交互。如果getDefaultAdapter()方法返回null,那麼該設備不支持藍牙,你的處理也要在此結束。例如:
BluetoothAdapter mBluetoothAdapter =BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter ==null){
// Device does not support Bluetooth
}
2. 啟用藍牙功能
接下來,你需要確保藍牙是可用的。調用isEnabled()方法來檢查當前藍牙是否可用。如果這個方法返回false,那麼藍牙是被禁用的。要申請啟用藍牙功能,就要調用帶有ACTION_REQUEST_ENABLE操作意圖的startActivityForResult()方法。它會給系統設置發一個啟用藍牙功能的請求(不終止你的應用程序)。例如:
if(!mBluetoothAdapter.isEnabled()){
Intent enableBtIntent =newIntent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
這時會顯示一個請求用戶啟用藍牙功能的對話框,如圖1所示:
圖1.啟用藍牙功能的對話框。
如果用戶響應“Yes”,那麼系統會開始啟用藍牙功能,完成啟動過程(有可能失敗),焦點會返回給你的應用程序。
傳遞給startActivityForResult()方法的REQUEST_ENABLE_BT常量,是一個你的應用程序中定義的整數(它必須大於0),系統會把它作為requestCode參數返回到你的onActivityResult()回調實現中。
如果藍牙功能啟用成功,你的Activity會在onActivityResult()回調中接收到RESULT_OK結果,如果藍牙沒有被啟動(或者用戶響應了“No”),那麼該結果編碼是RESULT_CANCELED。
可選地,你的應用程序還可以監聽ACTION_STATE_CHANGED廣播Intent,無論藍牙狀態何時改變,系統都會廣播這個Intent。這個廣播包含的附加字段EXTRA_STATE和EXTRA_PREVIOUS_STATE中分別指明了新的和舊的藍牙狀態。這些附加字段中可能的值是:STATE_TURNING_ON、STATE_ON、STATE_TURNING_OFF和STATE_OFF。監聽這個廣播對於在應用程序運行時檢測藍牙的狀態是有用的。
提示:啟用可發現能力會自動啟動藍牙功能。如果你計劃在執行藍牙活動之前,要始終啟用設備的可發現機制,就可以跳過上面的第2步,詳細請參閱下文“啟用藍牙可發現”。
Android的聯通性---Bluetooth(三)
查找設備
使用BluetoothAdapter對象,能夠通過設備發現或查詢已配對的設備列表來找到遠程的藍牙設備。
設備發現是一個掃描過程,該過程搜索本地區域內可用的藍牙設備,然後請求一些彼此相關的一些信息(這個過程被叫做“發現”、“查詢”或“掃描”)。但是,本地區域內的藍牙設備只有在它們也啟用了可發現功能時,才會響應發現請求。如果一個設備是可發現的,那麼它會通過共享某些信息(如設備名稱、類別和唯一的MAC地址)來響應發現請求。使用這些信息,執行發現處理的設備能夠有選擇的初始化跟被發現設備的連接。
一旦跟遠程的設備建立的首次連接,配對請求就會自動的被展現給用戶。當設備完成配對,相關設備的基本信息(如設備名稱、類別和MAC地址)就會被保存,並能夠使用藍牙API來讀取。使用已知的遠程設備的MAC地址,在任何時候都能夠初始化一個連接,而不需要執行發現處理(假設設備在可連接的范圍內)。
要記住配對和連接之間的差異。配對意味著兩個設備對彼此存在性的感知,它們之間有一個共享的用於驗證的連接密鑰,用這個密鑰兩個設備之間建立被加密的連接。連接意味著當前設備間共享一個RFCOMM通道,並且能夠被用於設備間的數據傳輸。當前Android藍牙API在RFCOMM連接被建立之前,要求設備之間配對。(在使用藍牙API初始化加密連接時,配對是自動被執行的。)
以下章節介紹如何發現已配對的設備,或發現新的使用了可發現功能的設備。
注意:默認Android設備是不可發現的。用戶能夠通過系統設置在限定的時間內變成可發現的設備,或者應用程序能夠請求用戶啟用可發現性,而不離開應用程序。如何啟用可發現性,會在下文來討論。
查詢配對設備
在執行設備發現之前,應該先查詢已配對的設備集合,來看期望的設備是否是已知的。調用getBondedDevices()方法來完成這件工作。這個方法會返回一個代表已配對設備的BluetoothDevice對象的集合。例如,你能夠查詢所有的配對設備,然後使用一個ArrayAdapter對象把每個已配對設備的名稱顯示給用戶。
Set 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對象來初始化一個連接所需要的所有信息就是MAC地址。在這個例子中,MAC地址被作為ArrayAdapter的一部分來保存,並顯示給用戶。隨後,該MAC地址能夠被提取用於初始化連接。
發現設備
簡單的調用startDiscovery()方法就可以開始發現設備。該過程是異步的,並且該方法會立即返回一個布爾值來指明發現處理是否被成功的啟動。通常發現過程會查詢掃描大約12秒,接下來獲取掃描發現的每個設備的藍牙名稱。
為了接收每個被發現設備的的信息,你的應用程序必須注冊一個ACTION_FOUND類型的廣播接收器。對應每個藍牙設備,系統都會廣播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
警告:執行設備發現,對於藍牙適配器來說是一個沉重的過程,它會消耗大量的資源。一旦發現要連接設備,在嘗試連接之前一定要確認用cancelDiscovery()方法來終止發現操作。另外,如果已經有一個跟設備的連接,那麼執行發現會明顯的減少連接的可用帶寬,因此在有連接的時候不應該執行發現處理。
Android的聯通性---Bluetooth(四)
啟用設備的可發現性
如果要讓本地設備可以被其他設備發現,那麼就要調用ACTION_REQUEST_DISCOVERABLE操作意圖的startActivityForResult(Intent, int)方法。這個方法會向系統設置發出一個啟用可發現模式的請求(不終止應用程序)。默認情況下,設備的可發現模式會持續120秒。通過給Intent對象添加EXTRA_DISCOVERABLE_DURATION附加字段,可以定義不同持續時間。應用程序能夠設置的最大持續時間是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()回調方法的調用。如果用戶響應“No”或有錯誤發生,則結果代碼等於RESULT_CANCELED.
圖2.啟用可發現性對話框。
注意:如果設備沒有開啟藍牙功能,那麼開啟設備的可發現模式會自動開啟藍牙。
在可發現模式下,設備會靜靜的把這種模式保持到指定的時長。如果你想要在可發現模式被改變時獲得通知,那麼你可以注冊一個ACTION_SCAN_MODE_CHANGED類型的Intent廣播。這個Intent對象中包含了EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE附加字段,它們會分別告訴你新舊掃描模式。它們每個可能的值是:SCAN_MODE_CONNECTABLE_DISCOVERABLE,SCAN_MODE_CONNECTABLE或SCAN_MODE_NONE,它們分別指明設備是在可發現模式下,還是在可發現模式下但依然可接收連接,或者是在可發現模式下並不能接收連接。
如果你要初始化跟遠程設備的連接,你不需要啟用設備的可現性。只有在你想要把你的應用程序作為服務端來接收輸入連接時,才需要啟用可發現性,因為遠程設備在跟你的設備連接之前必須能夠發現它。
連接設備
為了讓兩個設備上的兩個應用程序之間建立連接,你必須同時實現服務端和客戶端機制,因為一個設備必須打開服務端口,同時另一個設備必須初始化跟服務端設備的連接(使用服務端的MAC地址來初始化一個連接)。當服務端和客戶端在相同的RFCOMM通道上有一個BluetoothSocket連接時,才能夠被認為是服務端和客戶端之間建立了連接。這時,每個設備能夠獲得輸入和輸出流,並且能夠彼此開始傳輸數據。
服務端設備和客戶端設備彼此獲取所需的BluetoothSocket的方法是不同的。服務端會在接收輸入連接的時候接收到一個BluetoothSocket對象。客戶端會在打開跟服務端的RFCOMM通道時接收到一個BluetoothSocket對象。
一種實現技術是自動的准備一個設備作為服務端,以便在每個設備都會有一個服務套接字被打開,並監聽連接請求。當另一個設備初始化一個跟服務端套接字的連接時,它就會變成一個客戶端。另一種方法,一個設備是明確的”host”連接,並且根據要求打開一個服務套接字,而其他的設備只是簡單的初始化連接。
注意:如果兩個設備之前沒有配對,那麼Android框架會在連接過程期間,自動的顯示一個配對請求通知或對話框給用戶,如圖3所示。因此在試圖連接設備時,你的應用程序不需要關心設備是否被配對。FRCOMM的嘗試性連接會一直阻塞,一直到用戶成功的配對,或者是因用戶拒絕配對或配對超時而失敗。
圖3.藍牙配對對話框
Android的聯通性---Bluetooth(五)
作為連接的服務端
當你想要連接兩個設備時,一個必須通過持有一個打開的BluetoothServerSocket對象來作為服務端。服務套接字的用途是監聽輸入的連接請求,並且在一個連接請求被接收時,提供一個BluetoothSocket連接對象。在從BluetoothServerSocket對象中獲取BluetoothSocket時,BluetoothServerSocket能夠(並且也應該)被廢棄,除非你想要接收更多的連接。
以下是建立服務套接字和接收一個連接的基本過程。
1. 調用listenUsingRfcommWithServiceRecord(String, UUID)方法來獲得一個BluetoothServerSocket對象。該方法中的String參數是一個可識別的你的服務端的名稱,系統會自動的把它寫入設備上的Service Discovery Protocol(SDP)數據庫實體(該名稱是任意的,並且可以簡單的使用你的應用程序的名稱)。UUID參數也會被包含在SDP實體中,並且是跟客戶端設備連接的基本協議。也就是說,當客戶端嘗試跟服務端連接時,它會攜帶一個它想要連接的服務端能夠唯一識別的UUID。只有在這些UUID完全匹配的情況下,連接才可能被接收。
2. 通過調用accept()方法,啟動連接請求。這是一個阻塞調用。只有在連接被接收或發生異常的情況下,該方法才返回。只有在發送連接請求的遠程設備所攜帶的UUID跟監聽服務套接字所注冊的一個UUID匹配的時候,該連接才被接收。連接成功,accept()方法會返回一個被連接的BluetoothSocket對象。
3. 除非你想要接收其他連接,否則要調用close()方法。該方法會釋放服務套接字以及它所占用的所有資源,但不會關閉被連接的已經有accept()方法所返回的BluetoothSocket對象。跟TCP/IP不一樣,每個RFCOMM通道一次只允許連接一個客戶端,因此在大多數情況下,在接收到一個連接套接字之後,立即調用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對象時,套接字已經是被連接的,因此你不應該再調用像客戶端那樣調用connect()方法了。
應用程序中的manageConnectedSocket()方法是一個自定義方法,它會初始化用於傳輸數據的線程。
通常,一旦你監聽完成呼入連接,就應該關閉BluetoothServerSocket對象。在這個示例中,close()方法是在獲得BluetoothSocket對象之後被調用的。你可能還想要提供一個公共方法,以便在你的線程中能夠關閉你想要終止監聽的服務套接字事件中的私有BluetoothSocket對象。
作為連接的客戶端
為了初始化一個與遠程設備(持有打開的服務套接字的設備)的連接,首先必須獲取個代表遠程設備的BluetoothDevice對象。然後使用BluetoothDevice對象來獲取一個BluetoothSocket對象,並初始化該連接。
以下是一個基本的連接過程:
1. 通過調用BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法,獲得一個BluetoothSocket對象。這個方法會初始化一個連接到BluetoothDevice對象的BluetoothSocket對象。傳遞給這個方法的UUID參數必須與服務端設備打開BluetoothServerSocket對象時所使用的UUID相匹配。在你的應用程序中簡單的使用硬編碼進行比對,如果匹配,服務端和客戶端代碼就可以應用這個BluetoothSocket對象了。
2. 通過調用connect()方法來初始化連接。在這個調用中,為了找到匹配的UUID,系統會在遠程的設備上執行一個SDP查詢。如果查詢成功,並且遠程設備接收了該連接請求,那麼它會在連接期間共享使用RFCOMM通道,並且connect()方法會返回。這個方法是一個阻塞調用。如果因為某些原因,連接失敗或連接超時(大約在12秒之後),就會拋出一個異常。
因為connect()方法是阻塞調用,這個連接過程始終應該在獨立與主Activity線程之外的線程中被執行。
注意:在調用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()方法。在連接之前應該始終調用這個方法,並且不用實際的檢查藍牙發現處理是否正在運行也是安全的(如果想要檢查,調用isDiscovering()方法)。
manageConnectedSocket()方法是一個應用程序中自定義的方法,它會初始化傳輸數據的線程。
當使用完BluetoothSocket對象時,要始終調用close()方法來進行清理工作。這樣做會立即關閉被連接的套接字,並清理所有的內部資源。
Android的聯通性---Bluetooth(六)
管理連接
當你成功的連接了兩個(或更多)設備時,每一個設備都有一個被連接的BluetoothSocket對象。這是良好的開始,因為你能夠在設備之間共享數據。使用BluetoothSocket對象來傳輸任意數據的過程是簡單的:
1. 分別通過getInputStream()和getOutputStream()方法來獲得通過套接字來處理傳輸任務的InputStream和OutputStream對象;
2. 用read(byte[])和write(byte[])方法來讀寫流中的數據。
當然,有更多實現細節要考慮。首先和前提,對於所有數據流的讀寫應該使用專用的線程。這是重要的,因為read(byte[])和write(byte[])方法是阻塞式調用。Read(byte[])方法在從數據流中讀取某些數據之前一直是阻塞的。write(byte[])方法通常是不阻塞的,但是對於流的控制,如果遠程設備不是足夠快的調用read(byte[])方法,並且中間緩存被填滿,那麼write(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,然後返回繼續等待流中的更多的字節。
發送輸出數據就像在主Activity中調用線程的write()方法一樣簡單,並且給該方法傳入要發送的字節。然後這個方法簡單的調用write(byte[])方法把數據發送給遠程設備。
線程的cancel()方法是至關重要的,以便該連接在任何時候能夠通過關閉BluetoothSocket對象來終止。這個方法在使用完藍牙連接時應該始終被調用。
使用配置來工作
從Android3.0開始,藍牙API就包含了對用藍牙配置來進行工作的支持。Bluetooth Profile是設備之間基於藍牙進行通信的無線接口規范。其中一個示例是Hands-Free配置。對於一個連接到無線耳機的移動電話,移動電話和藍牙耳機都必須支持Hands-Free配置。
你能夠實現BluetoothProfile接口,來編寫你自己的支持特殊藍牙配置的類。Android藍牙API提供了對以下藍牙配置的實現:
· Headset(耳機):該配置提供了對用於移動電話的藍牙耳機的支持。Android提供了BluetoothHeadset類,這個類是一個代理,它通過進程間通信(IPC)來控制藍牙耳機服務。它包含了Bluetoot Headset和Hands-Free(v1.5)配置。BluetoothHeadset類包含了對AT命令的支持。關於AT命令的更多討論,請看“Vendor-specific AT commands”。
· A2DP:Advanced Audio Distribution Profile的縮寫。它定義如何流化高品質的音頻,並讓音頻流在通過藍牙連接在設備之間傳輸。Android提供了BluetoothA2dp類,它一個通過IPC來控制的藍牙A2DP服務的代理。
· Health Device:Android4.0(API Level 14)中引入了對Bluetooth Health Device Profile(HDP)的支持。它能夠讓你創建一個跟支持藍牙的健康保健設備進行藍牙通信的應用程序,這些健康保健包括:心率監護儀、血壓測量儀、體溫計、體重秤等。對應它支持的設備的列表和它們相應的設備數據規范,藍牙聯盟的網站:www.bluetooth.org。注意,這些值也是參考了ISO/IEEE11073-20601[7]規范中命名編碼規范附件中的名為MDC_DEV_SPEC_PROFILE_*規范。
以下是使用配置進行工作的基本步驟
1. 獲取默認的適配器;
2.使用getProfileProxy()方法來建立一個與配置相匹配的配置代理對象的連接。在下面的示例中,配置代理對象是一個BluetoothHeadset對象實例;
3.創建一個BluetoothProfile.ServiceListener監聽器,該監聽器會在它們連接到服務器或中斷與服務器的連接時,通知BluetoothProfile的IPC客戶端。
4.在onServiceConnected()事件中,獲得對配置代理對象的處理權;
5.一旦獲得配置代理對象,就可以用它來監視連接的狀態,並執行與配置有關的其他操作。
例如,以下代碼片段顯示如何連接一個BluetoothHeadset代理對象,以便控制Headset配置:
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);
Android的聯通性---Bluetooth(七)
健康設備配置
Android4.0(APILevel 14)中引入了對Bluetooth Health Device Profile(HDP)支持,這回讓你創建跟支持藍牙的健康設備進行藍牙通信的應用程序,如心率監護儀、血壓測量儀、體溫計、體重秤等。Bluetooth Health API包含了BluetoothHealth、BluetoothHealthCallbackhe和BluetoothHealthAppConfiguration等類。
在使用的Bluetooth Health API中,有助於理解以下關鍵的HDP概念:
概念
介紹
Source
HDP中定義的一個角色,一個Source是一個把醫療數據(如體重、血彈、體溫等)傳輸給諸如Android手機或平板電腦等的設備,
Sink
HDP中定義的一個角色,在HDP中,一個Sink是一個接收醫療數據的小設備。在一個Android HDP應用程序中,Sink用BluetoothHealthAppConfiguration對象來代表。
Registration
指的是給特定的健康設備注冊一個Sink。
Connection
指的是健康設備和Android手機或平板電腦之間打開的通信通道。
創建HDP應用程序
以下是創建Android HDP應用中所涉及到的基本步驟:
1. 獲得BluetoothHealth代理對象的引用。
類似於常規的耳機和A2DP配置設備,必須調用帶有BluetoothProfile.ServiceListener和HEALTH配置類型參數的getProfileProxy()方法來建立與配置代理對象的連接,
2. 創建BluetoothHealthCallback對象,並注冊一個扮演Health Sink角色的應用程序配(BluetoothHealthAppConfiguration)。
3. 建立跟健康設備的連接。某些設備會初始化連接,在這樣的設備中進行這一個步是沒有必要的。
4. 當成功的連接到健康設備時,就可以使用文件描述來讀寫健康設備。所接收到的數據需要使用健康管理器來解釋,這個管理器實現了IEEE 11073-xxxxx規范。
5. 完成以上步驟後,關閉健康通道,並注銷應用程序。該通道在長期被閒置時,也會被關閉。