Android 蓝牙模块的封装

引入

蓝牙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还可以对外提供支持那些设备的方法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342