Android 蓝牙 4.0 Ble 读写数据详解 -2

本贴最后更新于 2060 天前,其中的信息可能已经时移俗易

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;
    }
}

蓝牙基本上到这已经结束了。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1090 引用 • 3467 回帖 • 298 关注
  • 蓝牙
    10 引用 • 9 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    331 引用 • 315 回帖 • 82 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...