Android 蓝牙 4.0 Ble 读写数据详解 -2
上一篇说了如何扫描与链接蓝牙 这篇文章讲讲与蓝牙的数据传输,与一些踩到的坑。
先介绍一款调试工具,专门调试 Ble 蓝牙的 app。名字叫:nRF-Connect 谷歌应用商店也能下载到。
这里我先连接一个蓝牙设备 贴几个截图。
UUID 的话 就相当于钥匙,蓝牙设备当中有通道,那么通道是需要 UUID 进行匹配的
当连接上设备之后,可以看到 UUID 的通道 接下来,按照设备厂商提供的文档,找到我们需要的 UUID 通道
比如说我这里需要的是 0x6a 的 Service 通道 然后点开最后一个 Service 通道查看
展开 Service 后 可以看到有两个 Characteristic 通道
我们看 Properties 属性 一个是 NOTIFY 一个是 WRITE 也有可能会有 READ 这个属性的通道
可以拿这个 app 输出写出指令给蓝牙,在不清楚是蓝牙的问题还是自己的问题的时候,这个工具还是挺好使的。
Notify 的话,需要注意这个 Descriptors 的 UUID 这个在注册 Notify 的时候,需要用到,这里虽然看不全,但是之后可以通过打印得到。
简单说一下这三种属性的通道的用途
WRITE:顾名思义,写的意思,该通道是用来向蓝牙设备写出数据的通道
READ:向蓝牙设备进行读取数据的通道 这个通道有一个坑 后续会详细写上
Notify:该通道需要注册监听,这是一个通知的通道,蓝牙向你传输数据的话,就能直接收到监听。
我这边的话 因为一些原因,所以没有使用 READ 通道获取数据 只用了 Notify 通道 当然 也会讲讲怎么使用 READ
准备工作
先将 UUID 管理起来,我这里的话 采用静态常量的形式保存起来了。
public class UUIDManager { /** * 服务的UUID */ public static final String SERVICE_UUID = "00006a00-0000-1000-8000-00805f9b34fb"; /** * 订阅通知的UUID */ public static final String NOTIFY_UUID = "00006a02-0000-1000-8000-00805f9b34fb"; /** * 写出数据的UUID */ public static final String WRITE_UUID = "00006a02-0000-1000-8000-00805f9b34fb"; /** * NOTIFY里面的Descriptor UUID */ public static final String NOTIFY_DESCRIPTOR = "00002902-0000-1000-8000-00805f9b34fb"; }
处理通知回调接口
蓝牙的数据回调,其实并不是回调到主线程当中,所以如果接收到数据之后,就进行视图操作的话,是会失败的。
所以我打算切换到主线程进行回调,当然,也可以使用 EventBus,不过我不喜欢这东西就没去用。
回调接口的话,打算使用 list 集合存储起来,然后回调到各个需要数据的地方。 创建以下三个类
/** * Created by Pencilso on 2017/4/20. * 蓝牙数据回调监听接口 */ public interface BlueNotifyListener { public void onNotify(Message notify); } /** * Created by Pencilso on 2017/4/25. * 处理回调所有接口 */ public class NotifyThread implements Runnable { private List<BlueNotifyListener> listeners; private Message notify; @Override public void run() { if (notify == null || listeners==null) return; try { Iterator<BlueNotifyListener> iterator = listeners.iterator(); while (iterator.hasNext()) { BlueNotifyListener next = iterator.next(); if (next == null) iterator.remove(); else next.onNotify(notify); } } catch (Exception e) { e.printStackTrace(); } } public void setListeners(List<BlueNotifyListener> listeners) { this.listeners = listeners; } public void setNotify(Message notify) { this.notify = notify; } } /** * Created by Pencilso on 2017/4/26. * 蓝牙的Code类 用来自定义回调的标识 */ public class BlueCodeUtils { /** * 蓝牙状态 已连接 */ public static final int BLUETOOTH_STATE_CONNECT = 0x1; /** * 蓝牙状态 已断开 */ public static final int BLUETOOTH_STATE_DISCONNECT = 0x2; //*******这些只是自定义的code 就不复制太多了 }
编写蓝牙的功能
新建 BluetoothBinder 类 继承自 BluetoothGattCallback
然后把蓝牙的功能模块写在这里面。
主要的功能都在这个类里面,我把这个放到了服务里面,所以在创建 BluetoothBinder 的时候需要传递一个 Service 对象
import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.os.Build; import android.os.Message; import android.support.annotation.RequiresApi; import android.util.Log; import java.util.List; import java.util.UUID; import cc.petnet.trenmotion.Interface.IBluetoothInterface; import cc.petnet.trenmotion.utils.HandleUtils; import cc.petnet.trenmotion.utils.LogUtils; /** * Created by Pencilso on 2017/4/20. * 蓝牙操作的Binder */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) public class BluetoothBinder extends BluetoothGattCallback implements IBluetoothInterface { private static BluetoothBinder bluetoothBinder; private final Service bluetoothService;//service服务 private final BluetoothAdapter adapter;//蓝牙的适配器 private List<BlueNotifyListener> notifyList;//监听的集合 private BluetoothManager bluetoothManager;//蓝牙管理者 private BluetoothGattService gattService;//通道服务 private BluetoothGatt bluetoothGatt; public static IBluetoothInterface getInstace() { return bluetoothBinder; } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) protected BluetoothBinder(BluetoothService bluetoothService) { bluetoothBinder = this; this.bluetoothService = bluetoothService; bluetoothManager = (BluetoothManager) bluetoothService.getSystemService(Context.BLUETOOTH_SERVICE); adapter = bluetoothManager.getAdapter(); } @Override public void onDestroy() { bluetoothBinder = null; } @Override public <T extends BlueNotifyListener> void addNotifyListener(T notifyListener) { if (notifyListener != null) notifyList.add(notifyListener); } @Override public void removeNotifyListener(BlueNotifyListener notifyListener) { if (notifyListener != null) notifyList.remove(notifyListener); } /** * 广播蓝牙监听消息 * 因为蓝牙发送过来的消息 并不是处于主线程当中的 * 所以如果直接对蓝牙的数据展示视图的话 会展示不了的 这里的话 封装到主线程当中遍历回调数据 * * @param notify */ public void traverseListener(Message notify) { NotifyThread notifyThread = new NotifyThread();//创建一个遍历线程 notifyThread.setListeners(notifyList); notifyThread.setNotify(notify); HandleUtils.getInstace().post(notifyThread); } /** * 系统的蓝牙是否已经打开 * * @return */ @Override public boolean isEnable() { return adapter.isEnabled(); } @Override public void enableBluetooth(boolean enable) { if (enable) adapter.enable(); else adapter.disable(); } @SuppressWarnings("deprecation") @SuppressLint("NewApi") @Override public void startScanner(BluetoothAdapter.LeScanCallback callback) { adapter.startLeScan(callback); } @SuppressLint("NewApi") @SuppressWarnings("deprecation") @Override public void stopScanner(BluetoothAdapter.LeScanCallback callback) { adapter.stopLeScan(callback); } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @Override public void connectDevices(String address) { BluetoothDevice remoteDevice = adapter.getRemoteDevice(address); BluetoothGatt bluetoothGatt = remoteDevice.connectGatt(bluetoothService, false, this); } /** * 蓝牙设备状态的监听 * * @param gatt * @param status * @param newState 蓝牙的状态被改变 */ @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); Message message = new Message(); switch (newState) {//对蓝牙反馈的状态进行判断 case BluetoothProfile.STATE_CONNECTED://已链接 message.what = BlueCodeUtils.BLUETOOTH_STATE_CONNECT; gatt.discoverServices(); break; case BluetoothProfile.STATE_DISCONNECTED://已断开 message.what = BlueCodeUtils.BLUETOOTH_STATE_DISCONNECT; break; } traverseListener(message); /** * 这里还有一个需要注意的,比如说蓝牙设备上有一些通道是一些参数之类的信息,比如最常见的版本号。 * 一般这种情况 版本号都是定死在某一个通道上,直接读取,也不需要发送指令的。 * 如果遇到这种情况,一定要在发现服务之后 再去读取这种数据 不要一连接成功就去获取 */ } /** * 发现服务 * * @param gatt * @param status */ @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); gattService = gatt.getService(UUID.fromString(UUIDManager.SERVICE_UUID));// 获取到服务的通道 bluetoothGatt = gatt; //获取到Notify的Characteristic通道 这个根据协议来定 如果设备厂家给的协议不是Notify的话 就不用做以下操作了 BluetoothGattCharacteristic notifyCharacteristic = gattService.getCharacteristic(UUID.fromString(UUIDManager.NOTIFY_UUID)); BluetoothUtils.enableNotification(gatt, true, notifyCharacteristic);//注册Notify通知 } /** * 向蓝牙写入数据 * * @param data */ @SuppressLint("NewApi") @Override public boolean writeBuletoothData(String data) { if (bluetoothService == null) { return false; } BluetoothGattCharacteristic writeCharact = gattService.getCharacteristic(UUID.fromString(UUIDManager.WRITE_UUID)); bluetoothGatt.setCharacteristicNotification(writeCharact, true); // 设置监听 // 当数据传递到蓝牙之后 // 会回调BluetoothGattCallback里面的write方法 writeCharact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); // 将需要传递的数据 打碎成16进制 writeCharact.setValue(BluetoothUtils.getHexBytes(data)); return bluetoothGatt.writeCharacteristic(writeCharact); } /** * 蓝牙Notify推送过来的数据 * * @param gatt * @param characteristic */ @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); //如果推送的是十六进制的数据的写法 String data = BluetoothUtils.bytesToHexString(characteristic.getValue()); // 将字节转化为String字符串 Message message = new Message(); message.what = BlueCodeUtils.BLUETOOTH_PUSH_MESSAGE; message.obj = data; traverseListener(message);//回调数据 // String data = characteristic.getStringValue(0); // 使用场景 例如某个通道里面静态的定死了某一个值,就用这种写法获取 直接获取到String类型的数据 } /** * 这里有一个坑 一定要注意,如果设备返回数据用的不是Notify的话 一定要注意这个问题 * 这个方法是 向蓝牙设备写出数据成功之后回调的方法,写出成功之后干嘛呢? 主动去蓝牙获取数据,没错,自己主动去READ通道获取蓝牙数据 * 如果用的是Notify的话 不用理会该方法 写出到蓝牙之后 等待Notify的监听 即onCharacteristicChanged方法回调。 * * @param gatt * @param characteristic * @param status */ @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); if (status == BluetoothGatt.GATT_SUCCESS) { //写出成功 接下来 该去读取蓝牙设备的数据了 //这里的READUUID 应该是READ通道的UUID 不过我这边的设备没有用到READ通道 所以我这里就注释了 具体使用 视情况而定 // BluetoothGattCharacteristic readCharact = gattService.getCharacteristic(UUID.fromString(READUUID)); // gatt.readCharacteristic(readCharact); } } /** * 调用读取READ通道后返回的数据回调 * 比如说 在onCharacteristicWrite里面调用 gatt.readCharacteristic(readCharact);之后 会回调该方法 * * @param gatt * @param characteristic * @param status */ @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); //如果推送的是十六进制的数据的写法 String data = BluetoothUtils.bytesToHexString(characteristic.getValue()); // 将字节转化为String字符串 Message message = new Message(); message.what = BlueCodeUtils.BLUETOOTH_PUSH_MESSAGE; message.obj = data; traverseListener(message);//回调数据 // String data = characteristic.getStringValue(0); // 使用场景 例如某个通道里面静态的定死了某一个值,就用这种写法获取 直接获取到String类型的数据 } }
其实这个 BluetoothBinder 还定义了一个接口 IBluetoothInterface,然后让 BluetoothBinder 实现,并且将 BluetoothBinder 设置单利,暴露方法,提供返回 IBluetoothInterface 对象
import android.bluetooth.BluetoothAdapter; import cc.petnet.trenmotion.service.bluetooth.BlueNotifyListener; /** * Created by Pencilso on 2017/4/20. */ public interface IBluetoothInterface { /** * * @param notifyListener 添加监听事件 * @param <T> BlueNotifyListener监听回调接口 */ <T extends BlueNotifyListener> void addNotifyListener(T notifyListener); /** * * @param notifyListener 删除监听接口 */ void removeNotifyListener(BlueNotifyListener notifyListener); /** * 系统是否开启了蓝牙 * * @return */ boolean isEnable(); /** * 打开或者关闭系统蓝牙 * * @param enable */ void enableBluetooth(boolean enable); /** * 启动扫描 * * @param callback */ void startScanner(BluetoothAdapter.LeScanCallback callback); /** * 停止扫描 * * @param callback */ void stopScanner(BluetoothAdapter.LeScanCallback callback); void onDestroy(); /** * 连接设备 * * @param address */ void connectDevices(String address); /** * 向蓝牙写出数据 * @param data * @return */ public boolean writeBuletoothData(String data); }
Handler 工具 主要用来切换到主线程当中
public class HandleUtils { private static Handler handler = null; public static Handler getInstace() { if (handler == null) handler = new Handler(); return handler; } }
BluetoothUtils
public class BluetoothUtils { /** * 是否开启蓝牙的通知 * * @param enable * @param characteristic * @return */ @SuppressLint("NewApi") public static boolean enableNotification(BluetoothGatt bluetoothGatt, boolean enable, BluetoothGattCharacteristic characteristic) { if (bluetoothGatt == null || characteristic == null) { return false; } if (!bluetoothGatt.setCharacteristicNotification(characteristic, enable)) { return false; } //获取到Notify当中的Descriptor通道 然后再进行注册 BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString(UUIDManager.NOTIFY_DESCRIPTOR)); if (clientConfig == null) { return false; } if (enable) { clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); } else { clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); } return bluetoothGatt.writeDescriptor(clientConfig); } /** * 将字节 转换为字符串 * * @param src 需要转换的字节数组 * @return 返回转换完之后的数据 */ public static String bytesToHexString(byte[] src) { StringBuilder stringBuilder = new StringBuilder(""); if (src == null || src.length <= 0) { return null; } for (int i = 0; i < src.length; i++) { int v = src[i] & 0xFF; String hv = Integer.toHexString(v); if (hv.length() < 2) { stringBuilder.append(0); } stringBuilder.append(hv); } return stringBuilder.toString(); } /** * 将字符串转化为16进制的字节 * * @param message * 需要被转换的字符 * @return */ public static byte[] getHexBytes(String message) { int len = message.length() / 2; char[] chars = message.toCharArray(); String[] hexStr = new String[len]; byte[] bytes = new byte[len]; for (int i = 0, j = 0; j < len; i += 2, j++) { hexStr[j] = "" + chars[i] + chars[i + 1]; bytes[j] = (byte) Integer.parseInt(hexStr[j], 16); } return bytes; } }
蓝牙基本上到这已经结束了。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于