美文网首页
Android 蓝牙模块的封装

Android 蓝牙模块的封装

作者: Lilee丶 | 来源:发表于2018-09-23 11:46 被阅读0次

引入

蓝牙4.0为低功耗蓝牙,从安卓4.4版本开始支持。安卓中蓝牙的基本使用在官方的指南中有详细的说明,地址如下:https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le.html

  • 结合项目中的开发需求,经常需要在一个app中连接多种设备,如果设备的传输协议一致的话,那么设备与app之间传输指令可以公用一套代码。可是现实情况复杂,经常项目开发到一半就需要兼容新的设备。如果在项目的一开始,就可以做一个高扩展的兼容多设备的sdk,那么不管是以后其他项目使用,还是再添加新设备兼容,都将是一个不错的选择。

android提供的api简介

  • 获取蓝牙的管理adapter(在获取之前,记得检查权限)
private BluetoothAdapter mBluetoothAdapter;

// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
  • 检查蓝牙是否可用,返回true,表明支持4.0低功耗蓝牙。
mBluetoothAdapter.isEnabled()
  • 扫描蓝牙设备
    • BluetoothAdapter.LeScanCallback为扫描的回调函数。扫描到了设备,会在onLeScan中回调(不过要留意的是,同一个设备可能会被多次返回,在显示时,记得通过mac码进行筛选)。
    • 开启和停止扫描的方法也很方便。
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        //device 为扫描到的设备
   }
};

mBluetoothAdapter.startLeScan(mLeScanCallback);
mBluetoothAdapter.stopLeScan(mLeScanCallback);
  • 下来就是连接设备了, 其中 import android.bluetooth.BluetoothGattCallback;是一个抽象类。与蓝牙设备的连接回调都在这个类中。分别是:

    • onConnectionStateChange 连接状态改变。当判断这个方法的入参数符合以下两个条件时,则表示已经连接成功(蓝牙设备给手机传输数据时是通过设备的通道传输数据的,但是连接上之后,设备的通道此时并没有打开,此时可进行通道打开操作)。
    status == GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED
    
    • onServicesDiscovered 发现服务
    • onCharacteristicRead 读取字符
    • onCharacteristicWrite 写入指令成功
    • onCharacteristicChanged 蓝牙回传数据处理
    • onDescriptorRead 读的描述返回
    • onDescriptorWrite 写的描述返回
    • onReliableWriteCompleted
    • onReadRemoteRssi 手机距离设备的远近
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address);
BluetoothGatt bluetoothGatt = remoteDevice.connectGatt(appContext, isAutoConnect, mBluetoothGattCallback);
  • 发送数据给蓝牙设备(其中服务通道,数据通道,指令,是根据蓝牙设备协议确定的)
        String serveGallery = "xxx-xxx-xxx-xxx";//服务通道
        String gallery = "xxx-xxx-xxx-xxx";//数据通道
        BluetoothGattCharacteristic bluetoothGattCharacteristic = bleGattCharMap.get(serveGallery).get(gallery);
        bluetoothGattCharacteristic.setValue(new byte[]{ (byte) 0XBE, 0X02,0X03, (byte) 0XED});  //byte 为指令
        bluetoothGatt.writeCharacteristic(bluetoothGattCharacteristic);
  • 发送数据成功后,返回的数据会在上面实例的BluetoothGattCallback类的回调方法中回应,再根据业务需求做相应的处理。

以上是android api中提供的蓝牙的基本使用流程。

现在已经大致知道了怎么使用android api 进行蓝牙连接,可是我们也知道一个蓝牙设备会有好多服务通道(大通道)和下面的数据通道(小通道),每个设备会有很多指令集,往往实际应用中,集成一个蓝牙硬件,光是浏览协议中提供了哪些指令和功能,就已经够花费时间了,更别说集成了。最后终于根据协议,一个一个解析接入完了。这个时候要是老板和产品过来说又要加设备,那之前的代码是不是能够满足再加一个的需求。另外,下一次要再另外一个项目中集成这个设备,是不是又要重新解析接入一遍所有的协议指令。这些问题,光想想,都脑壳疼了。最好有个蓝牙的sdk,直接导入进来可以开发使用,不用在意输入输出指令的解析,那就太好了。于是,就有了下来要讨论的问题。

具体实现

  • 因为要撸这样的一个框架,代码非常多,而且每个蓝牙设备的协议,都不尽相同,所以我尽量按照讲一下整个思路。
    • 设计一个BleManager的管理类,全权处理app中有关蓝牙操作的所有事宜。
    • BleManager与app中的activity或者service 利用java提供的Obervable观察者模式,将所有的数据返回到服务里面或者UI层(我个人的思路是在blemanager和UI层之间在有一个service做衔接,这样就可以在app运行期间,不管在哪个界面,都可以去与蓝牙交互,蓝牙设备有信息,可以直接通知到UI层。)。

连接过程

  • BleManager设计为单例。提供一个init(Context appContext)方法,用于初始化BluetoothAdapter对象,设置为manager的成员变量。

  • 初始化之后是扫描,创建一个扫描的回调,这个回调返回一个list集合或只返回一个设备。

  • BleManager中有四个主要方法,分别为开始扫描,停止扫描,连接设备,设置指令。

  • 然后是连接设备,当用户点击了一个设备条目,我们会拿到这个设备的名字,mac等信息。我们的框架在设计时,需要一个Driver的接口,表示一个所有设备的父类。

  • 创建一个driver包,在包下面有一个driverimpl的实现包。

  • 每一种设备,都有一种设备名字,根据实际情况,可能是手环,心率带等等。那么对应的就需要手环driver和心率带driver。下图中用DriverXXX表示。

  • 创建一个Driver工厂专门生成具体的driver。在连接之前,生成一个具体的driver,保存成manager的成员变量。

        //连接设备。
        public void onConnectDervice(Context appContext, boolean isAutoConnect, MBleDeviceInfo mBleDevice, BleReciveListener listener) {
        //mBleDevice是对系统driver的提取,增加了type类型。在扫描时进行的包装
        String type = mBleDevice.getType();
        driver = DriverFactory.createDriver(type);
        BleDevice remoteDevice = bleAdapter.getRemoteDevice(mBleDevice.getAddress());
        mBluetoothGattCallback = new MBluetoothGattCallback(driver, listener);
        bluetoothGatt = remoteDevice.connectGatt(appContext, isAutoConnect, mBluetoothGattCallback);
    }
    
    • 需要解释一下MBluetoothGattCallback这个类,他是继承系统api中的BluetoothGattCallback,而BleReciveListener 为在调用层实例化,用于返回蓝牙连接的结果。将driver传入,以方便与将蓝牙的回调分发到具体的设备drvier中进行分析处理。下面贴一个转到driverxxx的代码。这样就可根据具体的设备做具体的数据解析了。
     @Override
     public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            driver.getBleConnactListener()
            .onConnectionStateChange(gatt, status, newState, listener);
        }
    
```
BleConnactListener mBleConnactListener = new BleConnactListener() {
        //返回的接口方法实现。
     //连接状态
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState, BleManager.BleReciveListener listener) {
        Logger.i(TAG, "BleConnactListener : 连接状态" + Thread.currentThread());
        if (status == GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
            listener.onConnResult(true);
        } else {
            //连接失败。
            listener.onConnResult(false);
        }
    }   
}
```
* 注:如果要打开通道,可以在连接成功的回调之前去打开,打开完成,再刷新UI,显示连接成功。因为,有些时候,通道不打开,是没有办法与蓝牙交互数据的。

发送指令###

  • 发送指令,调用Manager的setData()方法。其中参数分别表示指令。

        //向设备传输数据。
        public void setData(Perform perfrom) {
            BleCommand command = driver.createCommand(perfrom);
            driver.sendData(bluetoothGatt, command);
        }
    
    • 交给drvier去构造一个command命令,里面封装有这条指令的服务通道,数据通道,byte数组等信息。
  • createCommand过程

    • 这个方法是传到具体的Driverxxx中实现的,方法中,对于具体指令的创建,交给了一个工厂类。
     //@param performCommand  所需要创建指令的类型
     //@param perfrom  包装参数的父类接口。此处应该传入具体的实现类。
     //@return
    @Override
    public BleCommand createCommond(Perform perfrom) {
        BleCommand cmd = null;
        switch (performCommand) {//这里要分指令进行。所以要用switch。
            case MConstant.PerformCommand.ONE_DAY://设置步距
                if (perfrom instanceof PerfromSetStepDistance) {
                    cmd = new CommandXXXOneDay(perfrom);
                }
                break;
        }
        return cmd;
    }
    
    • CommandXXXOneDay 这是一个指令类。里面封装了具体的这条指令信息:指令的服务通道,数据通道,byte数据等信息。到这里为止,指令创建完成。

    • 发送指令,还是通过driver来。具体的实现和上一篇中相似。

     @Override
    public void sendData(BleGatt bluetoothGatt, BleCommand command) {
        String serveGallery = command.getServeGallery();
        String gallery = command.getGallery();
        BluetoothGattCharacteristic bluetoothGattCharacteristic =bleGattCharMap.get(serveGallery).get(gallery);
        bluetoothGattCharacteristic.setValue(command.getBytes());
        bluetoothGatt.writeCharacteristic(bluetoothGattCharacteristic);
    }
    
  • 接收成功的返回

    • 蓝牙数据的回传,在onCharacteristicChanged回调方法中。在下面方法的switch-case中根据蓝牙协议解析具体数据。解析完成,用listener回传到调用层。
     @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, final BleManager.BleReciveListener listener) {
            Logger.i(TAG, "BleConnactListener : 蓝牙回传数据接收处理");
            //TODO 解析数据。
            byte[] data = characteristic.getValue();
            if (data != null && data.length > 0) {
                StringBuilder stringBuilder = new StringBuilder(data.length);
                for (byte byteChar : data) {
                    stringBuilder.append(String.format("%02X ", byteChar));
                }
                String lastData = stringBuilder.toString();
                String uuid = characteristic.getUuid().toString();
                switch (uuid) {
                    case MConstant.Uuid.W311N.charUuid0:
                          listener.receiveData(...);
                        break;
                }
            }
    
        }
    
    • 返回的数据,依旧是解析好,封装成具体的bean对象传出去。

    总结###

    以上,就是大致的思路。

    • 这个框架,可以让调用层不关心具体的指令构建。需要做什么事情,直接传Perform具体实现类(但是这个实现类需是sdk提供的)。
    • bleManager还可以对外提供支持那些设备的方法。

相关文章

网友评论

      本文标题:Android 蓝牙模块的封装

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