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;
}
}
蓝牙基本上到这已经结束了。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于