Android ble低功耗蓝牙开发-客户端
什么是BLE(低功耗蓝牙)
BLE(Bluetooth Low Energy,低功耗蓝牙)是对传统蓝牙BR/EDR技术的补充。
尽管BLE和传统蓝牙都称之为蓝牙标准,且共享射频,但是,BLE是一个完全不一样的技术。
BLE不具备和传统蓝牙BR/EDR的兼容性。它是专为小数据率、离散传输的应用而设计的。
通信距离上也有改变,传统蓝牙的传输距离几十米到几百米不等,BLE则规定为100米。
概述
在Android4.3(API等级18)平台上开始支持低功耗蓝牙中央设备角色,而且提供可供应用去发现服务、查询服务和读写特性的相关API接口。与传统蓝牙相比,低功耗蓝牙的设计对电量消耗更低,这允许Android应用与其他的低功耗设备通信时对电量的需求更低,如距离传感器、心率监视器和医疗健康设备等等。
一、声明BLE权限
为了在你的应用中使用蓝牙功能,你必须声明蓝牙权限“android.permission.BLUETOOTH”。你需要使用这个权限如执行所有的蓝牙通信,如请求连接,接受连接和传输数据。
如果想要你的应用去初始化设备发现或者操纵蓝牙设置,你还必须声明“android.permission.BLUETOOTH_ADMIN”权限。
在应用的AndroidManifest.xml文件中声明蓝牙权限。如:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果想要声明你的应用仅对低功耗蓝牙是有效的,在app的manifest中还应包含下面这句:
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="true" />
二、设置BLE
获取 BluetoothAdapter
所有的蓝牙活动都需要蓝牙适配器。BluetoothAdapter代表设备本身的蓝牙适配器(蓝牙无线)。整个系统只有一个蓝牙适配器,而且你的app使用它与系统交互。
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter = bluetoothManager.getAdapter();
}
开启蓝牙
调用isEnabled())去检测蓝牙当前是否开启。如果该方法返回false,蓝牙被禁用。下面的代码检查蓝牙是否开启,如果没有开启,将显示错误提示用户去设置开启蓝牙
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
搜索蓝牙设备
为了发现BLE设备,使用startLeScan())方法。这个方法需要一个参数BluetoothAdapter.LeScanCallback。你必须实现它的回调函数,那就是返回的扫描结果。因为扫描非常消耗电量,你应当遵守以下准则:
只要找到所需的设备,停止扫描。
不要在循环里扫描,并且对扫描设置时间限制。以前可用的设备可能已经移出范围,继续扫描消耗电池电量。
boolean mScanning = false;
int SCAN_PERIOD = 1000;
/**
* 定时扫描
*
* @param enable
*/
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
// 预先定义停止蓝牙扫描的时间(因为蓝牙扫描需要消耗较多的电量)
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter.stopLeScan(callback);
}
}
}, SCAN_PERIOD);
mScanning = true;
// 定义一个回调接口供扫描结束处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter.startLeScan(callback);
}
} else {
mScanning = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter.stopLeScan(callback);
}
}
}
扫描回调--扫描到可用设备
List<BluetoothDevice> mBluetoothDeviceList = new ArrayList<>();
final BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
if (device.getName() != null) {
mBluetoothDeviceList.add(device);
mMyBlueAdapter.notifyDataSetChanged();
}
Log.e(TAG, "run: scanning..." + device.getName() + "," + device.getAddress());
}
};
GATT连接
搜索结束后,我们可得到一个搜索结果 BluetoothDevice ,它表示搜到的蓝牙设备
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter.stopLeScan(callback);
mBluetoothGatt = connectGatt.connectGatt(MainActivity.this, true, mBluetoothGattCallback);
}
- 可以建立一个GATT连接,它需要一个 回调mBluetoothGattCallback参数。
- 在回调方法的 onConnectionStateChange 中,我们可以通过 status 判断是否GATT连接成功
- 在GATT连接建立成功后,我们调用 mBluetoothGatt.discoverServices() 方法 发现GATT服务。
如果搜到服务将会触发onServicesDiscovered回调
public BluetoothGatt mBluetoothGatt;
// 状态改变
BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.e(TAG, "onConnectionStateChange: thread "
+ Thread.currentThread() + " status " + newState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (status != BluetoothGatt.GATT_SUCCESS) {
String err = "Cannot connect device with error status: " + status;
// 当尝试连接失败的时候调用 disconnect 方法是不会引起这个方法回调的,所以这里
// 直接回调就可以了。
gatt.close();
Log.e(TAG, err);
return;
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.e(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
Log.e(TAG, "connect--->success" + newState + "," + gatt.getServices().size());
setState(ConnectionState.STATE_CONNECTING);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.e(TAG, "Disconnected from GATT server.");
Log.e(TAG, "connect--->failed" + newState);
setState(ConnectionState.STATE_NONE);
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "onServicesDiscovered received: SUCCESS");
setState(ConnectionState.STATE_CONNECTED);
initCharacteristic();
try {
Thread.sleep(200);//延迟发送,否则第一次消息会不成功
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "onServicesDiscovered error falure " + status);
setState(ConnectionState.STATE_NONE);
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Log.e(TAG, "onCharacteristicWrite status: " + status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
Log.e(TAG, "onDescriptorWrite status: " + status);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
Log.e(TAG, "onDescriptorRead status: " + status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.e(TAG, "onCharacteristicRead status: " + status);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.e(TAG, "onCharacteristicChanged characteristic: " + characteristic);
readCharacteristic(characteristic);
}
};
发现服务 (触发onServicesDiscovered)
在发现服务后,会触发 GATT回调的onServicesDiscovered 方法,我们需要在这里初始化我们的操作,包括:
- 查看服务。或者便利查找指定的(和目标硬件UUID符合的)服务。
- 获得指定服务的特征 characteristicRead 、characteristicWrite
- 订阅“特征”发生变化的通知”
public synchronized void initCharacteristic() {
if (mBluetoothGatt == null)
throw new NullPointerException();
List<BluetoothGattService> services = mBluetoothGatt.getServices();
Log.e(TAG, services.toString());
BluetoothGattService service = mBluetoothGatt.getService(uuidServer);
characteristicRead = service.getCharacteristic(uuidCharRead);
characteristicWrite = service.getCharacteristic(uuidCharWrite);
if (characteristicRead == null)
throw new NullPointerException();
if (characteristicWrite == null)
throw new NullPointerException();
mBluetoothGatt.setCharacteristicNotification(characteristicRead, true);
BluetoothGattDescriptor descriptor = characteristicRead.getDescriptor(uuidDescriptor);
if (descriptor == null)
throw new NullPointerException();
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
订阅“特征”发生变化的通知”
调用 mBluetoothGatt.setCharacteristicNotification() 方法,传入一个特征 characteristic 对象。
当这个特征里的数据发生变化(接收到数据了),会触发 回调方法的 onCharacteristicChanged 方法。我们在这个回调方法中读取数据。
mBluetoothGatt.setCharacteristicNotification(characteristicRead, true);
BluetoothGattDescriptor descriptor = characteristicRead.getDescriptor(uuidDescriptor);
if (descriptor == null)
throw new NullPointerException();
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
读取数据
GATT的回调中有 onCharacteristicChanged 方法,我们在这里可以获得接收的数据
调用 characteristic.getValue() 方法,获得字节
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.e(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
byte[] bytes = characteristic.getValue();
String str = new String(bytes);
mHandler.obtainMessage(READ_MESSAGE, str).sendToTarget();
Log.e(TAG, "## readCharacteristic, 读取到: " + str);
}
写入数据
写入数据时,我们需要先获得特征,特征存在于服务内,一般在发现服务的 onServicesDiscovered 时,查找到特征对象。
public void write(byte[] cmd) {
Log.e(TAG, "write:" + new String(cmd));
if (cmd == null || cmd.length == 0)
return;
// synchronized (LOCK) {
characteristicWrite.setValue(cmd);
characteristicWrite.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
mBluetoothGatt.writeCharacteristic(characteristicWrite);
Log.e(TAG, "write:--->" + new String(cmd));
// }
}
关闭蓝牙连接
/**
* 关闭
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
setState(ConnectionState.STATE_NONE);
}
注意
调用 BluetoothGattCharactristic#setValue 传入需要写入的数据(蓝牙最多单次1支持 20 个字节数据的传输,如果需要传输的数据大于这一个字节则需要分包传输)
static void performPeriodicTask( void )
{
attHandleValueNoti_t noti;
//dummy handle
noti.handle = 0x2E;
noti.len = 20;
uint8 i;
for (i= 0; i < 20; i++)
{
noti.value[i] = message_counter;
}
if (!(GATT_Notification(0, ¬i, FALSE))) //if sucessful
{
message_counter++;
}
}
Android ble低功耗蓝牙开发-客户端
Android ble低功耗蓝牙开发-服务端
源码传送门
源码: