Android BLE开发指南(一)入门基础
Android BLE开发指南(二)中心设备端程序开发详解
这篇文章将会详细讲解低功耗蓝牙外围设备端程序开发的主要流程。对于Android开发者而言,或许涉及外围设备端程序的开发需求不多,但是本着深入学习的原则,最好也对外围设备端程序的运行流程有个了解。这样在开发低功耗蓝牙相关功能遇到问题时,或许能帮你更快的定位到问题原因。
1. 配置权限
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
2. 检查蓝牙开关
同样的,在进行BLE的相关操作前,确保蓝牙开关已经打开。
private BluetoothAdapter mBtAdapter;
public void init(Application application) {
......
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
}
public boolean isBleEnabled() {
return mBtAdapter != null && mBtAdapter.isEnabled();
}
如果开关未开启,则向用户申请开启蓝牙开关
public boolean setBleEnable(Activity activity, boolean enable) {
boolean setSucceed = true;
if (enable) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(intent, BleManager.REQUEST_ENABLE_BT);
} else {
......
}
return setSucceed;
}
用户同意或者拒绝蓝牙开关申请后,会通过 onActivityResult 将结果进行反馈,如果蓝牙开启成功,则继续下一步操作。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && requestCode == BleManager.REQUEST_ENABLE_BT) {
startPeripheralServer();
}
}
3. 开启低功耗蓝牙广播
要想让设备能被其它BLE设备扫描到,就要先开启低功耗蓝牙广播。
之前说过,Android 5.0 (API Level 21)开始才提供了对外围设备相关的API支持。所以当前系统版本如果低于 API Level 21,那么不支持外围设备模式,直接中止操作并退出。
首先,通过 BluetoothAdapter 拿到 BluetoothLeAdvertiser 的实例对象,然后进行广播前的配置初始化工作,可以看到,开启广播的 startAdvertising 函数传入了三个参数,我们一个个来看。
AdvertiseSettings: 提供了广播模式等一些自定义选项的配置,一般情况下使用以下默认配置即可;
AdvertiseData:广播时的附带的数据信息。这里可以通过 addServiceUuid 加入一个UUID信息。这样中心设备在扫描到该外围设备后,可以获取到这个UUID,从而帮助中心设备判断是否找到了目标设备;
AdvertiseCallback:回调方法,在开启广播操作完成后,会有回调结果。
private BluetoothAdapter mBtAdapter;
private BluetoothLeAdvertiser mBleAdvertiser;
private BluetoothGattServer mBtGattServer;
private AdvertiseCallback mAdsCallback;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean startPeripheralServer(... ...) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
LogUtil.w("current SDK version is not supported !!!");
return false;
}
......
if (mBleAdvertiser == null) {
mBleAdvertiser = mBtAdapter.getBluetoothLeAdvertiser();
}
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setConnectable(true)
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
.setTimeout(0)
.build();
AdvertiseData advertiseData = new AdvertiseData.Builder()
.addServiceUuid(new ParcelUuid(UUIDUtil.UUID_DEVICE))
.setIncludeTxPowerLevel(true)
.setIncludeDeviceName(true)
.build();
mAdsCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
LogUtil.i("!!! startAdvertising is succeed ");
initBleService();
......
}
@Override
public void onStartFailure(int errorCode) {
LogUtil.w("!!! startAdvertising is failed. with code: " + errorCode);
......
}
};
mBleAdvertiser.startAdvertising(settings, advertiseData, mAdsCallback);
return true;
}
调用 startAdvertising 开启BLE广播后,如果开启成功,则会回调 AdvertiseCallback 接口中的 onStartSuccess 函数,那么接下来就可以开始初始化BLE服务了。
4. 初始化低功耗蓝牙服务
二话不说,反手就 new 一个BLE服务——BluetoothGattService,需要传入两个参数。每个服务对应一个UUID,这里我们事先定义好了一个UUID,然后将服务类型定义为 SERVICE_TYPE_PRIMARY,表示该服务为主服务。
在 Android BLE开发指南(一)入门基础 说过,一个服务中包含一个或以上的 Characteristic,用于数据的传输。这里定义了两个 Characteristic:
writableCharacteristic 用于中心设备向外围设备发送数据;
mNotifyCharacteristic 用于外围设备向中心设备发送数据。
private BluetoothGattServer mBtGattServer;
private BluetoothGattServerCallback mBtGattServerCallback;
private void initBleService() {
if (mBtGattServer == null) {
BluetoothManager btManager = (BluetoothManager)
mContext.getSystemService(Context.BLUETOOTH_SERVICE);
mBtGattServer = btManager.openGattServer(mContext, mBtGattServerCallback);
}
if (mBtGattServer == null) {
LogUtil.w("!!! openGattServer is failed");
return;
}
BluetoothGattService gattService = new BluetoothGattService(
UUIDUtil.UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
BluetoothGattCharacteristic writableCharacteristic = new BluetoothGattCharacteristic(
UUIDUtil.UUID_CHARIC_WRITABLE,
BluetoothGattCharacteristic.PROPERTY_WRITE
| BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_WRITE);
mNotifyCharacteristic = new BluetoothGattCharacteristic(
UUIDUtil.UUID_CHARIC_READABLE,
BluetoothGattCharacteristic.PROPERTY_READ
| BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ);
gattService.addCharacteristic(writableCharacteristic);
gattService.addCharacteristic(mNotifyCharacteristic);
mBtGattServer.clearServices();
mBtGattServer.addService(gattService);
}
走到这里,运行该程序的外围设备 就可以被其它中心设备扫描到了。
5. 监控设备连接情况
那么,怎么知道有没有其它设备跟我建立连接了呢?
回头看一下,我们发现在初始化BLE服务的时候,openGattServer 函数中传入一个回调接口 BluetoothGattServerCallback,轮到它出场了。
在与其它BLE设备建立连接或者断开连接的时候,会回调 onConnectionStateChange 函数,
其中, newState 等于 BluetoothProfile.STATE_CONNECTED 表示有设备建立连接;而 newState 等于 BluetoothProfile.STATE_DISCONNECTED 表示有设备断开连接。
根据这两个状态,设备连接情况就尽在掌握啦~
private BluetoothGattServerCallback mBtGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTED:
mConnDevController.addConnectedDevice(device.getAddress(), device);
......
break;
case BluetoothProfile.STATE_DISCONNECTED:
mConnDevController.removeConnectedDevice(device.getAddress());
......
break;
}
}
......
};
6. 接收数据
还是熟悉的老面孔——BluetoothGattServerCallback。当接收到数据时,会回调接口中的 onCharacteristicWriteRequest 函数,接收到的数据就是一个byte数组value,数据量最大就20个byte;另外参数 device 包含了一些设备信息,可以用来识别是哪个设备发送过来的数据。
这里需要说明一下的是,在测试中发现,回调 onCharacteristicWriteRequest 函数时,需要调用下 mBtGattServer.sendResponse ,否则设备连接会莫名其妙断开。具体原因有待研究。
private BluetoothGattServerCallback mBtGattServerCallback = new BluetoothGattServerCallback() {
......
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
LogUtil.w("!!! onCharacteristicWriteRequest: " + new String(value));
mBtGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
......
}
};
7. 数据发送
之前定义的 mNotifyCharacteristic 就是用于发送数据的。
首先需要明确需要发送数据到哪个目标设备,然后通过目标设备的MAC地址,获取到之前缓存的目标设备的 BluetoothDevice 实例。接着往 mNotifyCharacteristic 存入需要发送的数据,最后调用 notifyCharacteristicChanged 完成数据的发送。
public boolean sendDataToCentralDevice(String deviceAddress, String data) {
if (mNotifyCharacteristic == null) {
return false;
}
boolean succeed = false;
BluetoothDevice btDevice = mConnDevController.getConnectedDevice(deviceAddress);
if (btDevice != null) {
mNotifyCharacteristic.setValue(data);
succeed = mBtGattServer.notifyCharacteristicChanged(btDevice,
mNotifyCharacteristic, false);
}
return succeed;
}
数据发送完成后,中心设备端程序 BluetoothGattCallback 中的 onCharacteristicChanged 函数会被回调(在 Android BLE开发指南(二)中心设备端程序开发详解 讲过就不赘述了)。
8. 关闭低功耗蓝牙广播
如果想隐藏自己不被找到,那就调用 stopAdvertising 吧,最后把 BluetoothGattServer close掉。
public boolean stopPeripheralServer() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false;
}
mBtGattServerListener = null;
mNotifyCharacteristic = null;
if (mBleAdvertiser != null) {
mBleAdvertiser.stopAdvertising(mAdsCallback);
}
if (mBtGattServer != null) {
mBtGattServer.close();
}
return true;
}
9. Demo运行示例图
程序运行示例图如下,左边是中心设备端程序的界面,右边是外围设备端程序的界面。
中心设备发送 “central dev data” 到外围设备,外围设备发送 ”peripheral dev data“ 到中心设备,两个设备都收到了对方发送的数据并显示在了界面上。