近期一个项目需要用到低功耗蓝牙的开发,由于之前没有蓝牙开发的经验,发现网上关于蓝牙开发的资料不多,不是随便描述一下就是已经过时的,在此整理一篇低功耗蓝牙的入门资料,能够完成使用蓝牙的接受和发送数据。
低功耗蓝牙 (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 能对低功耗蓝牙的使用有更深的了解。
本文原文地址:Android 低功耗蓝牙开发