近期一個項目需要用到低功耗藍牙的開發,由於之前沒有藍牙開發的經驗,發現網上關於藍牙開發的資料不多,不是隨便描述一下就是已經過時的,在此整理一篇低功耗藍牙的入門資料,能夠完成使用藍牙的接受和發送數據。
低功耗藍牙 (BLE,Bluetooth Low Energy的簡稱) 從Android 4.3 開始支持,如今越來越多外設都是使用低功耗藍牙來傳輸數據的,與經典藍牙本質上沒有太多的區別,有很多相似之處,工作流程都是:發現設備 --> 配對/綁定設備 --> 連接設備 --> 數據傳輸。但是,低功耗藍牙在安卓開發中的使用和經典藍牙是完全不同的,如果按照之前很熟悉的經典藍牙開發思維來做,說不定還會踩坑。。。
官方相關的開發指南:
經典藍牙
低功耗藍牙
低功耗藍牙使用實例項目
基本概念
先來了解一些關於低功耗藍牙的基本概念:
- Generic Attribute Profile (GATT)——全稱叫做通用屬性配置文件,GATT按照層級定義了三個概念,服務(Service)、特征(Characteristic)和描述(Descriptor)。一個 Service 包含若干個 Characteristic,一個 Characteristic 包含若干個 Descriptor。
- Characteristic——可以理解為一個類,包含了一個 value 和零至多個對該 value 的描述。
- Descriptor——對 Characteristic 的描述,例如范圍和計量單位等。
- Service——Characteristic的集合。
這些概念不用深入去探究,有一定了解開發的時候不至於一無所知就夠了,想要具體了解低功耗藍牙這里有篇不錯的文章。
低功耗藍牙開發步驟
1.聲明權限
使用藍牙功能首先需要聲明相關的權限,比如:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
同時,也就可以通過藍牙特性配置來限制支持藍牙功能的設備使用APP:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
或者通過在代碼中判斷,不過現在基本沒有什么手機不支持藍牙功能了吧。
// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
需要注意的是,官方說明 Android 5.0 及以上設備使用藍牙時還需要定位權限,需要注意的是開發的時候如果是在 Android 6.0 及以上設備的需要動態獲取定位權限,否則藍牙功能也是無法使用的。
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
...
</manifest>
2.初始化藍牙適配器
private BluetoothAdapter mBluetoothAdapter;
...
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
3.開啟藍牙
在開始掃描發現藍牙設備之前需要確保手機的藍牙功能打開。
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
// 申請打開藍牙
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
然后在 onActivityResultI
方法中判斷用戶是否同意開啟藍牙功能。
4.發現設備
private static final long SCAN_PERIOD = 10000;
private void scanLeDevice(final boolean enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
}
/**
* 發現設備的回調
*/
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
}
};
5.連接設備
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
連接設備的方法需要傳入三個參數,第一個是 context,第二個是 boolean,表示是否自動連接,第三個是連接的回調接口,其中有幾個很重要的方法。
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
mConnectionState = STATE_CONNECTED;
// 開始查找服務,只有找到服務才算是真的連接上
mBluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
} else {
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
byte[] data = characteristic.getValue();
}
};
連接設備成功后需要在回調方法中發現服務 mBluetoothGatt.discoverServices()
,發現服務后會回調onServicesDiscovered
方法,發現服務成功才算是真正的連接上藍牙設備。onCharacteristicWrite
方法是寫操作結果的回調,onCharacteristicChanged
方法是狀態改變的回調,在該方法中能夠獲取藍牙發送的數據。不過,在接收數據之前,我們必須對Characteristic設置監聽才能夠接收到藍牙的數據。
6.Characteristic監聽設置
public void setNotification() {
BluetoothGattService service = mBluetoothGatt.getService(SERVICE_UUID);
if (service == null) {
L.e("未找到藍牙中的對應服務");
return;
}
BluetoothGattCharacteristic characteristic= service.getCharacteristic(CharacteristicUUID);
if (characteristic== null) {
L.e("未找到藍牙中的對應特征");
return;
}
//設置true為啟用通知,false反之
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
//下面為開啟藍牙notify功能,向CCCD中寫入值1
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CCCD);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
先通過 UUID 找到我們需要進行數據傳輸的service,在找到我們想要設置監聽的Characteristic的 UUID 找到響應的characteristic對象,找到響應的characteristic后調用setCharacteristicNotification
方法啟用通知,則該characteristic狀態發生改變后就會回調onCharacteristicChanged
方法,而開啟藍牙的 notify 功能需要向 UUID 為 CCCD 的 descriptor 中寫入值1,其中 CCCD 的值為:
public static final UUID CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
7.向藍牙發送數據
往藍牙發送數據,可以理解為給藍牙的characteristic設置數據。
public void writeRXCharacteristic(byte[] value) {
if (mBluetoothGatt == null) {
return;
}
BluetoothGattService service= mBluetoothGatt.getService(SERVICE_UUID);
BluetoothGattCharacteristic characteristic= service.getCharacteristic(UUID);
characteristic.setValue(value);
mBluetoothGatt.writeCharacteristic(characteristic);
}
我們可以寫入String
和byte[]
的數據,一般為byte[]
。其中我們需要與哪個service的characteristic進行數據傳輸可以聯系硬件工程師或者查看藍牙設備供應商提供的說明獲得。我們也可以通過mBluetoothGatt.getServices()
和mBluetoothGatt.getgetCharacteristics()
方法獲取藍牙設備的所有
service 和某個 service 中的所有 characteristic。
8.數據分包處理
低功耗藍牙一次性只能發送 20 個字節的數據,超過 20 個字節的無法發送,因此需要對發送的數據進行分包處理,在此給出數據分包的一個示例,是在別人 github 中看到的:
//存儲待發送的數據隊列
private Queue<byte[]> dataInfoQueue = new LinkedList<>();
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
private Runnable runnable = new Runnable() {
@Override
public void run() {
send();
}
};
/**
* 向characteristic寫數據
*
* @param value
*/
public void writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value) {
this.mCharacteristic = characteristic;
if (dataInfoQueue != null) {
dataInfoQueue.clear();
dataInfoQueue = splitPacketFor20Byte(value);
handler.post(runnable);
}
// characteristic.setValue(value);
// mBluetoothGatt.writeCharacteristic(characteristic);
}
private void send() {
if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) {
//檢測到發送數據,直接發送
if (dataInfoQueue.peek() != null) {
this.mCharacteristic.setValue(dataInfoQueue.poll());//移除並返回隊列頭部的元素
mBluetoothGatt.writeCharacteristic(mCharacteristic);
}
//檢測還有數據,延時后繼續發送,一般延時100毫秒左右
if (dataInfoQueue.peek() != null) {
handler.postDelayed(runnable, 200);
}
}
}
//數據分包處理
private Queue<byte[]> splitPacketFor20Byte(byte[] data) {
Queue<byte[]> dataInfoQueue = new LinkedList<>();
if (data != null) {
int index = 0;
do {
byte[] surplusData = new byte[data.length - index];
byte[] currentData;
System.arraycopy(data, index, surplusData, 0, data.length - index);
if (surplusData.length <= 20) {
currentData = new byte[surplusData.length];
System.arraycopy(surplusData, 0, currentData, 0, surplusData.length);
index += surplusData.length;
} else {
currentData = new byte[20];
System.arraycopy(data, index, currentData, 0, 20);
index += 20;
}
dataInfoQueue.offer(currentData);
} while (index < data.length);
}
return dataInfoQueue;
}
這篇文章簡單介紹了安卓進行低功耗藍牙開發的流程以及提到了一些注意事項,看完本文基本就能夠進行低功耗藍牙開發,除此意外,強烈推薦去 Github 看一下低功耗藍牙使用實例項目,例子不難理解,看懂了 Demo 能對低功耗藍牙的使用有更深的了解。