Android:BLE智能开发
目录
- 前言
- BLE是个什么
- BLE中的角色分工
- 主要的关键词和概念
- GATT(Generic Attribute Profile )
- Characteristic
- Service
- Android如何使用BLE
- 蓝牙权限
- APP和BLE外设交互流程
- 后记
前言
前些年,智能硬件炒的挺火的,那今天,咱就来说说智能硬件那些事。BLE是智能硬件的一种通讯方式,通过BLE连接,iOS & Android手机和智能硬件就可以进行自定义的交互了。交互的体验如何,很大程度上取决于智能硬件的驱动工程师驱动写的好不好,以及App的代码质量如何。
笔者曾参与过多款BLE智能硬件的开发,许久不用,怕忘了,把自己的整理的一些知识记录与此,同时也希望能够给一些同学带来帮助。本文将尽力向读者讲清楚BLE是什么,以及在实际Android开发中该如何使用BLE。
Android如何使用BLE
蓝牙权限
使用BLE需要两个权限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果你想要APP只适配具备BLE的手机,那个可以再添加一个硬件权限特性
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
APP和BLE外设交互流程
APP和BLE外设交互的一个大概流程就是:
- BLE外设打开电源
- APP初始化蓝牙
- APP扫描周边BLE外设
- APP连接到周边BLE外设
- APP读写BLE外设
- 交互完成,APP向BLE外设写入关机/待机指令(可选)
- BLE外设关机
- APP关闭本地蓝牙连接
以下将逐步利用代码进行讲解APP和BLE外设交互.
初始化BLE
Java代码判断当前手机是否支持BLE低功耗蓝牙
// 判断手机是否支持BLE if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish();// 如果手机不支持BLE就关闭程序,仅供参考 }
初始化蓝牙管理者和适配器,这2个对象是ble通讯的基石.
// 初始化蓝牙管理者和适配器,这2个对象是ble通讯的基石. private BluetoothAdapter mBluetoothAdapter; ... final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter();
跳转到系统蓝牙设置界面
private BluetoothAdapter mBluetoothAdapter; ... // 验证蓝牙是否已打开,如果没打开就提示用户跳转打开. if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
APP扫描周边BLE外设
需要实现一个BluetoothAdapter.LeScanCallback回调接口,得到扫描结果。该接口只有一个回调方法:
/** * @param device 被手机蓝牙扫描到的BLE外设实体对象 * @param rssi 大概就是表示BLE外设的信号强度,如果为0,则表示BLE外设不可连接。 * @param scanRecord 被扫描到的BLE外围设备提供的扫描记录,一般没什么用 */ public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)
由于扫描BLE设备比较消耗资源,官方推荐间歇性扫描,示例代码如下
private BluetoothAdapter mBluetoothAdapter; private boolean mScanning; private Handler mHandler; // 每扫描10s休息一下 private static final long SCAN_PERIOD = 10000; private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { // TODO 这里可以进行连接操作,连接操作见下一小节 if (device != null && device.getName() != null && device.getName().contain("你的产品名称")){ // 连接设备 connectDevice(device); // 停止扫描 scanLeDevice(false); } } }); } }; ... /** * @param enable 是否进行扫描,false则停止扫描 */ private void scanLeDevice(final boolean enable) { if (enable) { // 利用Handler进行间歇性扫描,每次扫描时间:10s mHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } }, SCAN_PERIOD); mScanning = true; mBluetoothAdapter.startLeScan(mLeScanCallback); } else { // 停止扫描 mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } ... }
APP连接周边BLE外设
连接操作是进行手机和BLE外设交互的基础,请看下面connectDevice(BluetoothDevice)方法实现。
分两步走:
1. 判断该设备是否连接过,连接过则首先尝试直接连接:BluetoothGatt.connect()
2. 首次连接或者直连失败使用:BluetoothDevice.connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)
public boolean connectDevice(final BluetoothDevice device) { if (mBluetoothAdapter == null || device == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } String address = device.getAddress(); // 之前连接过的设备,尝试直接连接。mBluetoothDeviceAddress表示刚才连接过的设备地址 if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) { Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); if (mBluetoothGatt.connect()) {// 连接成功 // 修改连接状态变量 mConnectionState = STATE_CONNECTING; return true; } else { return false; } } final BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address); if (remoteDevice == null) { Log.w(TAG, "Device not found. Unable to connect."); return false; } mBluetoothGatt = remoteDevice.connectGatt(context, false, mGattCallback); Log.d(TAG, "Trying to create a new connection."); // 将当前连接上的设备地址赋值给连接过的设备地址变量 mBluetoothDeviceAddress = address; // 改变连接状态变量 mConnectionState = STATE_CONNECTING; return true; }
连接BEL外设时,需要一个实现回调接口以得到连接状态,BluetoothGattCallback大概实现如下:
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { // 该方法在连接状态改变时回调,newState即代表当前连接状态 String intentAction; // 连接上了 if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = ACTION_GATT_CONNECTED; // 改变蓝牙连接状态变量 mConnectionState = STATE_CONNECTED; // 发送自定义广播:连接上了 broadcastUpdate(intentAction); // 当前外设相当于前面章节提到的Server角色:提供数据被手机读取 Log.i(TAG, "Connected to GATT server."); // 获取读/写服务:Service。该方法会触发下面的onServicesDiscovered()回调 mBluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 断开连接了 intentAction = ACTION_GATT_DISCONNECTED; mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); // 发送自定义广播:断开了连接 broadcastUpdate(intentAction); } } @Override // 该方法在蓝牙服务被发现时回调。由上述的mBluetoothGatt.discoverServices()触发结果。 public void onServicesDiscovered(BluetoothGatt gatt, int status) { // 发现服务。status表示发现服务的结果码 if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); // TODO 从发现的Service来找出读数据用的BluetoothGattCharacteristic和写数据用的BluetoothGattCharacteristic。 initReadAndWriteCharacteristic(gatt.getServices()); } else {// 未发现服务 Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override // 读取操作的回调结果 public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } @Override // 写入操作的回调结果 public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { }; ... }; ...
找出读写”数据包”的”搬运工”
下面是找出读写”搬运工”BluetoothGattCharacteristic的initReadAndWriteCharacteristic()代码实现
BluetoothGattCharacteristic mReadCharacteristic; BluetoothGattCharacteristic mWriteCharacteristic; public void initReadAndWriteCharacteristic( List<BluetoothGattService> gattServices) { if (gattServices == null) return; // 遍历所有的 GATT Services. for (BluetoothGattService gattService : gattServices) { if (!gattService.getUuid().toString().trim().equalsIgnoreCase("这里是你期望的Service的uuid,由你司智能外色的驱动工程师决定")) continue; List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); // 遍历当前Service中所有的Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { if (gattCharacteristic.getUuid().toString().trim().equalsIgnoreCase(""这里是你期望的写数据的uuid,由你司驱动工程师决定"")) { mWriteCharacteristic = gattCharacteristic; } else if (gattCharacteristic.getUuid().toString().trim().equalsIgnoreCase("这里是你期望的读数据的uuid,由你司驱动工程师决定")) { mReadCharacteristic = gattCharacteristic; } } } }
至此,我们就拿到了可携带读写数据的“搬运工”-『mReadCharacteristic & mWriteCharacteristic』,下面就可以和智能硬件进行交互了。
APP读取BLE外设蓝牙数据
想要读取BLE外设的数据时,比如:心跳速率,电量等等。可通过下面方式。
// 告诉”搬运工“我想知道BLE外设当前数据,将回调BluetoothGattCallback接口的onCharacteristicRead()方法 mBluetoothGatt.readCharacteristic(mReadCharacteristic); // 读取BLE蓝牙数据操作的回调方法 @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); // ”搬运工“把”数据包“搬出来了 byte[] data = characteristic.getValue(); // 根据驱动工程师给的协议文档,解析该数组,该处假设数组0位上表示心跳速率 int heartRateR = data[0];// 得到心跳速率,做相应UI更新和操作 } } }
APP向BLE外设写入数据
比如说你想告诉BLE外设让他锁屏,或者进行某个动作,APP向操纵BLE外设时可通过以下方式
// 根据驱动工程师给的协议文档,组织一个数组命令 byte[] data = getData(); // 将该条命令“数据包”给“搬运工" mWriteCharacteristic.setValue(data); // ”搬运工“将数据搬到BLE外设里面了,将回调BluetoothGattCallback接口的onCharacteristicWrite()方法 mBluetoothGatt.writeCharacteristic(characteristic); // 向BLE蓝牙外设写入数据操作的回调方法 @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if(status == BluetoothGatt.GATT_SUCCESS) { // 命令写入成功,数据包成功写入BLE外设中 } };
APP关闭蓝牙连接
交互完了,不需要了,还是把APP蓝牙连接给断掉吧
public void close() { if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null; }
注意事项
在Android 6.0+搜索蓝牙是需要定位权限的,还有BLE搜索在Android 5.0以前和以后是不一样的。最后你还会发现使用官方这套搜索在一些手机型号上也是搜不到的!只能通过传统蓝牙(非BLE方式)搜索然后过滤出BLE设备。
本文作者MichaelX,博客地址:http://blog.csdn.net/xiong_it 转载请注明来源