藍牙
Android 平臺包含藍牙網絡堆棧支持骑脱,憑借此項支持,設備能以無線方式與其他藍牙設備交換數據袭异。應用框架提供了通過 Android Bluetooth API 訪問藍牙功能的途徑焙糟。 這些 API 允許應用以無線方式連接到其他藍牙設備,從而實現(xiàn)點到點和多點無線功能晾捏。
使用 Bluetooth API蒿涎,Android 應用可執(zhí)行以下操作:
- 掃描其他藍牙設備
- 查詢本地藍牙適配器的配對藍牙設備
- 建立 RFCOMM 通道
- 通過服務發(fā)現(xiàn)連接到其他設備
- 與其他設備進行雙向數據傳輸
- 管理多個連接
本文將介紹如何使用傳統(tǒng)藍牙。傳統(tǒng)藍牙適用于電池使用強度較大的操作惦辛,例如 Android 設備之間的流式傳輸和通信等劳秋。 針對具有低功耗要求的藍牙設備,Android 4.3(API 級別 18)中引入了面向低功耗藍牙的 API 支持。 如需了解更多信息玻淑,請參閱低功耗藍牙嗽冒。
基礎知識
本文將介紹如何使用 Android Bluetooth API 來完成使用藍牙進行通信的四項主要任務:設置藍牙、查找局部區(qū)域內的配對設備或可用設備补履、連接設備添坊,以及在設備之間傳輸數據。
本文將介紹如何使用 Android Bluetooth API 來完成使用藍牙進行通信的四項主要任務:設置藍牙箫锤、查找局部區(qū)域內的配對設備或可用設備贬蛙、連接設備,以及在設備之間傳輸數據麻汰。
android.bluetooth包中提供了所有 Bluetooth API速客。 下面概要列出了創(chuàng)建藍牙連接所需的類和接口:
BluetoothAdapter
表示本地藍牙適配器(藍牙無線電)戚篙。這 BluetoothAdapter是所有藍牙互動的入門點五鲫。使用此功能,您可以發(fā)現(xiàn)其他藍牙設備岔擂,查詢已綁定(配對)設備的列表位喂,BluetoothDevice使用已知的MAC地址實例化,并創(chuàng)建一個BluetoothServerSocket監(jiān)聽來自其他設備的通信乱灵。
BluetoothDevice
表示遠程藍牙設備塑崖。使用此方法通過BluetoothSocket關于設備的或查詢信息(如其名稱,地址痛倚,類別和綁定狀態(tài))來請求與遠程設備的連接规婆。
BluetoothSocket
表示藍牙插座的接口(類似于TCP Socket)。這是允許應用程序通過InputStream和OutputStream與另一個藍牙設備交換數據的連接點蝉稳。
BluetoothServerSocket
表示用于偵聽傳入請求(類似于TCP ServerSocket)的打開的服務器套接字抒蚜。為了連接兩個Android設備,一個設備必須打開這個類的服務器套接字耘戚。當遠程藍牙設備向該設備發(fā)出連接請求時嗡髓,當接受BluetoothServerSocket連接BluetoothSocket時, 將返回連接收津。
BluetoothClass
描述藍牙設備的一般特性和功能饿这。這是一組只讀屬性,用于定義設備的主要和次要設備類及其服務撞秋。但是长捧,這不能可靠地描述設備支持的所有藍牙配置文件和服務,但對設備類型的提示很有用吻贿。
BluetoothProfile
表示藍牙配置文件的界面唆姐。甲藍牙配置文件是用于在設備之間基于藍牙的通信的無線接口規(guī)范。一個例子是免提配置文件。有關配置文件的更多討論奉芦,請參閱使用配置文件
BluetoothHeadset
支持藍牙耳機與手機配合使用赵抢。這包括藍牙耳機和免提(v1.5)配置文件。
BluetoothA2dp
定義通過藍牙連接將高質量的音頻流從一個設備傳輸到另一個設備声功》橙矗“A2DP”表示高級音頻分配配置文件。
BluetoothHealth
表示控制藍牙服務的運行狀況設備配置文件代理先巴。
BluetoothHealthCallback
用于實現(xiàn)BluetoothHealth回調的抽象類其爵。您必須擴展此類并實現(xiàn)回調方法以接收有關應用程序注冊狀態(tài)和藍牙通道狀態(tài)更改的更新。
BluetoothHealthAppConfiguration
表示藍牙健康第三方應用程序注冊以與遠程藍牙健康設備進行通信的應用程序配置伸蚯。
BluetoothProfile.ServiceListener
BluetoothProfile當IPC客戶端連接到服務器或與服務斷開連接(即運行特定配置文件的內部服務器)時摩渺,可以通知IPC客戶端。
藍牙權限
要在應用中使用藍牙功能剂邮,必須聲明藍牙權限BLUETOOTH摇幻,您需要此權限才能執(zhí)行任何藍牙通信,例如請求連接挥萌、接受連接和傳輸數據等绰姻。
如果您希望您的應用啟動設備發(fā)現(xiàn)或操作藍牙設置,則還必須聲明 BLUETOOTH_ADMIN 權限引瀑。 大多數應用需要此權限僅僅為了能夠發(fā)現(xiàn)本地藍牙設備狂芋。 除非該應用是將要應用戶請求修改藍牙設置的“超級管理員”,否則不應使用此權限所授予的其他能力憨栽。
Android6.0及以上需要位置權限才能掃描到設備帜矾;位置權限是危險權限,需要
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>
<uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
設置藍牙
- 獲取 BluetoothAdapter
將BluetoothAdapter所需的任何和所有的藍牙活動屑柔。要獲取BluetoothAdapter屡萤,請調用靜態(tài)getDefaultAdapter()方法。這將返回一個 BluetoothAdapter表示設備自己的藍牙適配器(藍牙無線電)的锯蛀。整個系統(tǒng)有一個藍牙適配器灭衷,您的應用程序可以使用此對象與其進行交互。如果 getDefaultAdapter()返回null旁涤,則設備不支持藍牙翔曲,您的操作將在此結束。例如:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}
2.啟用藍牙
接下來劈愚,您需要確保啟用藍牙瞳遍。調用isEnabled()檢查藍牙是否當前啟用。如果此方法返回false菌羽,則藍牙被禁用掠械。要請求啟用藍牙,請startActivityForResult() 使用ACTION_REQUEST_ENABLEIntent操作調用。這將發(fā)出通過系統(tǒng)設置啟用藍牙的請求(不停止您的應用程序)猾蒂。例如:
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
查找設備
使用該功能BluetoothAdapter均唉,您可以通過設備發(fā)現(xiàn)或通過查詢配對(綁定)設備列表來查找遠程藍牙設備。
設備發(fā)現(xiàn)是一種掃描過程肚菠,可以在本地搜索藍牙設備舔箭,然后請求一些關于每個設備的信息(這有時被稱為“發(fā)現(xiàn)”,“查詢”或“掃描”)蚊逢。但是层扶,本地區(qū)內的藍牙設備只有在當前啟用才能發(fā)現(xiàn)的情況下才能響應發(fā)現(xiàn)請求。如果設備是可發(fā)現(xiàn)的烙荷,它將通過共享一些信息來響應發(fā)現(xiàn)請求镜会,例如設備名稱,類別及其唯一的MAC地址终抽。使用此信息戳表,執(zhí)行發(fā)現(xiàn)的設備隨后可以選擇啟動與發(fā)現(xiàn)的設備的連接。
一旦與第一次使用遠程設備進行連接拿诸,配對請求將自動呈現(xiàn)給用戶扒袖。當設備配對時塞茅,將保存有關該設備的基本信息(如設備名稱亩码,類和MAC地址),并使用藍牙API進行讀取野瘦。使用已知的MAC地址進行遠程設備描沟,可以在任何時間啟動連接,而無需執(zhí)行發(fā)現(xiàn)(假定設備在范圍內)鞭光。
記住配對和連接之間有區(qū)別吏廉。要配對意味著兩個設備都知道彼此的存在,具有可以用于認證的共享鏈路密鑰惰许,并且能夠建立彼此的加密連接席覆。要連接意味著設備當前共享RFCOMM信道,并且能夠彼此傳輸數據汹买。在建立RFCOMM連接之前佩伤,目前的Android藍牙API需要配對設備。(當您使用藍牙API啟動加密連接時晦毙,會自動執(zhí)行配對生巡。)
以下部分介紹如何查找已配對的設備,或使用設備發(fā)現(xiàn)來發(fā)現(xiàn)新設備见妒。
查找配對的設備
在執(zhí)行設備發(fā)現(xiàn)之前孤荣,它值得查詢一組配對的設備,以查看所需設備是否已知。要這樣做盐股,打電話getBondedDevices()钱豁。這將返回一組BluetoothDevice代表配對的設備。例如疯汁,您可以查詢所有配對的設備寥院,然后使用ArrayAdapter向用戶顯示每個設備的名稱:
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());
}
}
發(fā)現(xiàn)設備
要開始發(fā)現(xiàn)設備,只需調用startDiscovery()涛目。該進程是異步的秸谢,該方法將立即返回一個布爾值,指示發(fā)現(xiàn)是否已成功啟動霹肝。發(fā)現(xiàn)過程通常涉及大約12秒的查詢掃描估蹄,隨后是每個找到的設備的頁面掃描以檢索其藍牙名稱。
你的應用程序必須登記為action_found意圖接收有關每個設備發(fā)現(xiàn)BroadcastReceiver沫换。對每一個設備臭蚁,系統(tǒng)將播出action_found意圖。這種意圖進行額外的領域extra_device和extra_class讯赏,包含一個藍牙設備和藍牙類垮兑,分別。例如漱挎,在這里的你如何登記辦理廣播設備時發(fā)現(xiàn)的:
// 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
連接設備
為了在兩個設備之間創(chuàng)建應用程序之間的連接系枪,您必須同時實現(xiàn)服務器端和客戶端機制,因為一個設備必須打開一個服務器套接字磕谅,另一個設備必須啟動連接(使用服務器設備的MAC地址啟動連接)私爷。當服務器和客戶端都BluetoothSocket在相同的RFCOMM通道上連接時,服務器和客戶端被認為是相互連接 的膊夹。此時衬浑,每個設備可以獲取輸入和輸出流,并且可以開始數據傳輸放刨,這將在“ 管理連接 ”一節(jié)中討論工秩。本節(jié)介紹如何啟動兩臺設備之間的連接。
服務器設備和客戶端設備都BluetoothSocket以不同的方式獲得所需的設備进统。當接收到連接時助币,服務器將收到該消息。當客戶端向服務器打開RFCOMM通道時麻昼,客戶端將收到該消息奠支。
一種實現(xiàn)技術是將每個設備自動準備為服務器,以便每個設備都有一個服務器套接字打開并監(jiān)聽連接抚芦。那么任一設備都可以啟動與其他設備的連接并成為客戶端倍谜÷趺或者,一個設備可以顯式地“主持”連接并按需打開服務器套接字尔崔,而另一個設備可以簡單地啟動連接答毫。
連接為服務器
當您要連接兩個設備時,必須通過持續(xù)打開來充當服務器BluetoothServerSocket季春。服務器套接字的目的是監(jiān)聽傳入的連接請求洗搂,并且當被接受時,提供連接BluetoothSocket载弄。當從BluetoothServerSocket獲取BluetoothSocket時耘拇,BluetoothServerSocket可以(應該)被丟棄,除非你想接受更多的連接宇攻。
以下是設置服務器套接字并接受連接的基本步驟:
- 獲得BluetoothServerSocket通過調用 listenUsingRfcommWithServiceRecord(String, UUID)惫叛。
該字符串是您的服務的可標識名稱,系統(tǒng)將自動寫入設備上的新服務發(fā)現(xiàn)協(xié)議(SDP)數據庫條目(名稱是任意的逞刷,可以僅僅是您的應用程序名稱)嘉涌。UUID也包含在SDP條目中,并將作為與客戶端設備的連接協(xié)議的基礎夸浅。也就是說仑最,當客戶端嘗試與此設備連接時,它將攜帶唯一標識要連接的服務的UUID帆喇。這些UUID必須匹配才能接受連接(在下一步中)警医。 - 通過調用開始監(jiān)聽連接請求 accept()。
這是一個阻塞調用番枚。當連接被接受或發(fā)生異常時法严,它將返回损敷。僅當遠程設備發(fā)送了一個與該偵聽服務器套接字注冊的UUID相匹配的UUID的連接請求時才接受連接葫笼。當成功時,accept()將返回一個已連接BluetoothSocket拗馒。 - 除非你想接受額外的連接路星,請打電話 close()。
這將釋放服務器socket和它的所有資源诱桂,但并沒有關閉連接的BluetoothSocket一個已經被退回accept()洋丐。不像TCP / IP,RFCOMM只允許每個信道的一個連接的客戶端的時間挥等,所以在大多數情況下是有意義的調用close()在BluetoothServerSocket接受連接的套接字之后友绝。
accept()調用不應在主 Activity UI 線程中執(zhí)行,因為它是阻塞調用肝劲,并會阻止與應用的任何其他交互迁客。 在您的應用所管理的新線程中使用 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) { }
}
}
連接為客戶端
為了啟動與遠程設備(持有打開服務器套接字的設備)的連接,您必須首先獲取BluetoothDevice表示遠程設備的對象海雪。(BluetoothDevice有關查找設備的上述部分將介紹以下部分)锦爵。然后,您必須使用它 BluetoothDevice來獲取BluetoothSocket并啟動連接奥裸。
這是基本的過程:
使用BluetoothDevice棉浸,BluetoothSocket通過調用得到一個createRfcommSocketToServiceRecord(UUID)。
這將初始化一個BluetoothSocket將連接到的BluetoothDevice刺彩。在此處傳遞的UUID必須與服務器設備打開BluetoothServerSocket(使用listenUsingRfcommWithServiceRecord(String, UUID))時使用的UUID相匹配 迷郑。使用相同的UUID只是將UUID字符串硬編碼到應用程序中,然后從服務器和客戶端代碼引用它创倔。
通過調用啟動連接connect()嗡害。
在此呼叫之后,系統(tǒng)將在遠程設備上執(zhí)行SDP查找畦攘,以匹配UUID霸妹。如果查找成功并且遠程設備接受連接,則它將共享在連接期間使用的RFCOMM通道并connect()返回知押。這種方法是一個阻塞調用叹螟。如果出于任何原因,連接失敗或connect()方法超時(約12秒鐘后)台盯,則會引發(fā)異常罢绽。
因為connect()是一個阻塞調用,這個連接過程應該總是在與主活動線程分開的線程中執(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) { }
}
}