概述
藍芽(英語:Bluetooth),一種無線通訊技術標準,用來讓固定與移動裝置,在短距離間交換資料,以形成個人區域網(PAN)。藍芽技術當前由藍芽技術聯盟(SIG)來負責維護其技術標準。
版本
分類
藍芽可分為經典藍芽(Classic Bluetooth)、低功耗藍芽(Bluetooth Low Energy)、雙模藍芽三大類。2009 年釋出的藍芽 3.0 及之前的藍芽版本包括 BR、EDR 和 HS(AMP) 三種藍芽技術,統稱為經典藍芽技術,只支援經典藍芽技術的藍芽稱為經典藍芽。2010 年 SIG 聯盟合併了 Wibree 聯盟,並把 Wibree 聯盟提出的低功耗無線技術重新命名為低功耗藍芽技術(BLE)。2010 年釋出的藍芽 4.0 規格就同時包含經典藍芽和低功耗藍芽,只支援低功耗藍芽技術的藍芽稱為低功率藍芽,同時支援經典藍芽和低功率藍芽技術的藍芽稱為雙模藍芽。低功耗藍芽與經典藍芽技術是不相容的,所以低功耗藍芽和經典藍芽兩者之間是不能相互通訊的。
優缺點
經典藍芽技術持續保持連線,可以傳輸大資料,適合檔案傳輸、音訊播放等,相容性高,廣播通道多,缺點是連線成本高,功耗高;低功耗藍芽連線速度快,低功耗,廣播通道少,缺點是資料傳輸量有限制。
Android
Android 平臺包含藍芽網路堆疊支援,憑藉此項支援,裝置能以無線方式與其他藍芽裝置交換資料。應用框架提供了通過 Android Bluetooth API 訪問藍芽功能的途徑。針對具有低功耗要求的藍芽裝置,Android 4.3(API 級別 18)中引入了面向低功耗藍芽的 API 支援。即 SPP 協議(經典藍芽)和 GATT 協議(低功耗藍芽)的連線方式。 使用 Android 藍芽功能需要在 Manifest.xml 檔案中宣告藍芽許可權,如果使用了藍芽掃描功能還需要宣告位置許可權,因為藍芽是具有定位功能的,目標版本是 Android 23 及以上版本需要新增動態許可權申請。
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
// 藍芽掃描需要位置許可權
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
複製程式碼
SPP 方式
- 啟用藍芽
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
複製程式碼
- 查詢配對的裝置
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());
}
}
複製程式碼
- 發現裝置,這裡需要註冊 ACTION_FOUND Intent BroadcastReceiver, Intent 攜帶額外欄位 EXTRA_DEVICE 和 EXTRA_CLASS,二者分別包含 BluetoothDevice 和 BluetoothClass,如果沒申請位置許可權,這裡發現不了其他裝置。
mBluetoothAdapter.startDiscovery();
// 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
複製程式碼
- 連線裝置,藍芽連線屬於耗時操作,需要放在子執行緒執行。在藍芽協議中,UUID 被用來標識藍芽裝置所提供的服務,並非是標識藍芽裝置本身。一個藍芽裝置可以提供多種服務,比如 A2DP(藍芽音訊傳輸)、HEADFREE(擴音)、PBAP(電話本)等,每種服務都對應一個UUID。連線成功後可以獲取藍芽裝置的輸入輸出流,進行資料互動操作。
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
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();
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} 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) { }
}
}
複製程式碼
GATT 方式
- 啟用藍芽和 SPP 方式是一樣的
- 發現裝置,同樣需要在子執行緒執行,不過通過接收回撥的方式獲取發現的裝置,而不是註冊廣播。不過需要注意的是發現裝置是高功耗行為,需要設定一個超時時間停止掃描。
private ScanCallback mLeScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
bluetoothLeScanner.stopScan(mLeScanCallback);
}
}, SCAN_PERIOD);
bluetoothLeScanner.startScan(mLeScanCallback);
複製程式碼
- 連線裝置,可以在 BluetoothGattCallback 中實現各種回撥方法,如發現服務、監聽藍芽資料等。值得注意的一點是連線 API 在 Android version >= 23 時多了一個引數,如果不加上 BluetoothDevice.TRANSPORT_LE 可能導致連線不上 BLE 裝置
// Various callback methods defined by the BLE API.
private final BluetoothGattCallback gattCallback =
new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
connectionState = STATE_CONNECTED;
Log.i(TAG, "Connected to GATT server.");
Log.i(TAG, "Attempting to start service discovery:" +
bluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
connectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
}
}
@Override
// New services discovered
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
// Result of a characteristic read operation
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
...
};
BluetoothGatt bluetoothGatt;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
bluetoothGatt = device.connectGatt(context, false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
} else {
bluetoothGatt = device.connectGatt(context, false, mGattCallback);
}
複製程式碼
總結
其實 Android 藍芽相關的 API 使用還是比較簡單的,對於初次接觸藍芽開發的小白來說,弄清楚藍芽裝置的型別以及連線方式就成功了一半(經典藍芽 SPP,低功耗藍芽 GATT)。另外一半在於藍芽裝置的適配及細節的掌握,適配問題可以多聯絡藍芽裝置供應商的技術支援,細節問題就靠平時積累和多搜尋查閱資料了。 最後安利兩個 BLE 開源庫:RxAndroidBle、flutter_blue。
@123lxw123, 本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。