美文网首页移动互联网Android知识Android开发
Android蓝牙4.0 Ble读写数据详解 -2

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

作者: Pencilso | 来源:发表于2017-04-28 17:54 被阅读1710次

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

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

    相关文章

      网友评论

      • 因為風d緣故:请问楼主有源码的工程参考下吗????有github的地址连接吗?不胜感激。
        Pencilso:这篇文章的源码没有了,但是之前我封装了一套蓝牙连接 你可以看看这个
        https://github.com/pencilso/ManyBlue
      • iwgang:请问一下,静态的UUID是根据什么生成的还是随意定死的?
        JoNiu:大佬demo呢
        iwgang: @a铅笔丶画在羙也是灰色 嗯 , 我后面看了下,一般就是硬件那块提供,谢谢你的答复
        Pencilso: @iwgang UUID的生成用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。设备的UUID都是由厂家来设定的,其实随意写死也可以啊,这东西就像是一个通道的id,有id就能找到通道,而你不需要管这个id是怎么生成的。
      • 墨白1629:上一篇看的好好的,怎么忽然就看不懂了呢
        Pencilso:http://www.jianshu.com/p/a39096ac781d
        你可以看看这个,我把蓝牙的一些操作封装了一个框架
        Pencilso:这篇写的有点仓促,有时间再整理整理。

      本文标题:Android蓝牙4.0 Ble读写数据详解 -2

      本文链接:https://www.haomeiwen.com/subject/cydazttx.html