Android BLE基础框架全新改版

Android BLE基础操作框架,基于回调,操作简单。包含扫描、多连接、广播包解析、服务读写及通知等功能。

功能

  • 支持多设备连接管理;

  • 支持广播包解析;

  • 支持自定义扫描过滤条件;

  • 支持根据设备名称正则表达式过滤扫描设备;

  • 支持根据设备信号最小值过滤扫描设备;

  • 支持根据设备名称或 MAC 地址列表过滤扫描设备;

  • 支持根据设备 UUID 过滤扫描设备;

  • 支持根据指定设备名称或 MAC 地址搜索指定设备;

  • 支持连接设备失败重试;

  • 支持操作设备数据失败重试;

  • 支持绑定数据收发通道,同一种能力可绑定多个通道;

  • 支持注册和取消通知监听;

  • 支持配置最大连接数,超过最大连接数时会依据 Lru 算法自动断开最近最久未使用设备;

  • 支持配置扫描、连接和操作数据超时时间;

  • 支持配置连接和操作数据重试次数以及重试间隔时间。

简介

打造该库的目的是为了简化蓝牙设备接入的流程。该库是 BLE 操作的基础框架,只处理 BLE 设备通信逻辑,不包含具体的数据处理,如数据的分包与组包等。该库提供了多设备连接管理,可配置最大连接数量,并在超过最大连接数时会依据 Lru 算法自动断开最近最久未使用设备。该库还定制了常用的扫描设备过滤规则,也支持自定义过滤规则。该库所有操作都采用回调机制告知上层调用的结果,操作简单,接入方便。

效果展示

BLE效果

使用介绍

权限配置

6.0 以下系统不需要配置权限,库中已经配置了如下权限:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

而如果手机系统在 6.0 以上则需要配置如下权限:

<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>

因为蓝牙在 6.0 以上手机使用了模糊定位功能,所以需要添加模糊定位权限。

引入 SDK

在工程 module 的 build.gradle 文件中的 dependencies 中添加如下依赖:

compile 'com.vise.xiaoyaoyou:baseble:2.0.0'

构建完后就可以直接使用该库的功能了。

初始化

在使用该库前需要进行初始化,初始化代码如下所示:

//蓝牙相关配置修改
ViseBle.config()
        .setScanTimeout(-1)//扫描超时时间,这里设置为永久扫描
        .setConnectTimeout(10 * 1000)//连接超时时间
        .setOperateTimeout(5 * 1000)//设置数据操作超时时间
        .setConnectRetryCount(3)//设置连接失败重试次数
        .setConnectRetryInterval(1000)//设置连接失败重试间隔时间
        .setOperateRetryCount(3)//设置数据操作失败重试次数
        .setOperateRetryInterval(1000)//设置数据操作失败重试间隔时间
        .setMaxConnectCount(3);//设置最大连接设备数量
//蓝牙信息初始化,全局唯一,必须在应用初始化时调用
ViseBle.getInstance().init(this);

初始化可以是在 Application 中也可以是在 MainActivity 中,只需要是在使用蓝牙功能前就行。还有需要注意的是,蓝牙配置必须在蓝牙初始化前进行修改,如果默认配置满足要求也可以不修改配置。

设备扫描

库中针对设备扫描定义了几种常用过滤规则,如果不满足要求也可以自己定义过滤规则,下面针对库中提供的过滤规则使用方式一一介绍:

  • 扫描所有设备
ViseBle.getInstance().startScan(new ScanCallback(new IScanCallback() {
    @Override
    public void onDeviceFound(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanTimeout() {

    }
}));
  • 扫描指定设备 MAC 的设备
//该方式是扫到指定设备就停止扫描
ViseBle.getInstance().startScan(new SingleFilterScanCallback(new IScanCallback() {
    @Override
    public void onDeviceFound(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanTimeout() {

    }
}).setDeviceMac(deviceMac));
  • 扫描指定设备名称的设备
//该方式是扫到指定设备就停止扫描
ViseBle.getInstance().startScan(new SingleFilterScanCallback(new IScanCallback() {
    @Override
    public void onDeviceFound(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanTimeout() {

    }
}).setDeviceName(deviceName));
  • 扫描指定 UUID 的设备
ViseBle.getInstance().startScan(new UuidFilterScanCallback(new IScanCallback() {
    @Override
    public void onDeviceFound(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanTimeout() {

    }
}).setUuid(uuid));
  • 扫描指定设备 MAC 或名称集合的设备
ViseBle.getInstance().startScan(new ListFilterScanCallback(new IScanCallback() {
    @Override
    public void onDeviceFound(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanTimeout() {

    }
}).setDeviceMacList(deviceMacList).setDeviceNameList(deviceNameList));
  • 扫描指定信号范围或设备正则名称的设备
ViseBle.getInstance().startScan(new RegularFilterScanCallback(new IScanCallback() {
    @Override
    public void onDeviceFound(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore) {

    }

    @Override
    public void onScanTimeout() {

    }
}).setDeviceRssi(rssi).setRegularDeviceName(regularDeviceName));

其中扫描到的设备列表由 BluetoothLeDeviceStore 管理,而单个设备信息都统一放到BluetoothLeDevice中,其中包含了设备的所有信息,如设备名称、设备地址、广播包解析信息等,设备的相关信息会在设备详情中进行介绍。

设备连接

设备连接有三种方式,一种是根据设备信息直接进行连接,另外两种是在没扫描的情况下直接通过设备名称或设备 MAC 进行扫描连接。三种连接方式使用如下:

  • 根据设备信息连接设备
ViseBle.getInstance().connect(bluetoothLeDevice, new IConnectCallback() {
    @Override
    public void onConnectSuccess(DeviceMirror deviceMirror) {

    }

    @Override
    public void onConnectFailure(BleException exception) {

    }

    @Override
    public void onDisconnect(boolean isActive) {

    }
});
  • 根据设备 MAC 直接扫描并连接
ViseBle.getInstance().connectByMac(deviceMac, new IConnectCallback() {
    @Override
    public void onConnectSuccess(DeviceMirror deviceMirror) {

    }

    @Override
    public void onConnectFailure(BleException exception) {

    }

    @Override
    public void onDisconnect(boolean isActive) {

    }
});
  • 根据设备名称直接扫描并连接
ViseBle.getInstance().connectByName(deviceName, new IConnectCallback() {
    @Override
    public void onConnectSuccess(DeviceMirror deviceMirror) {

    }

    @Override
    public void onConnectFailure(BleException exception) {

    }

    @Override
    public void onDisconnect(boolean isActive) {

    }
});

设备详情

DEVICE INFO(设备信息)

  • 获取设备名称(Device Name):bluetoothLeDevice.getName()
  • 获取设备地址(Device Address):bluetoothLeDevice.getAddress()
  • 获取设备类别(Device Class):bluetoothLeDevice.getBluetoothDeviceClassName()
  • 获取主要设备类别(Major Class):bluetoothLeDevice.getBluetoothDeviceMajorClassName()
  • 获取服务类别(Service Class):bluetoothLeDevice.getBluetoothDeviceKnownSupportedServices()
  • 获取配对状态(Bonding State):bluetoothLeDevice.getBluetoothDeviceBondState()

RSSI INFO(信号信息)

  • 获取第一次信号时间戳(First Timestamp):bluetoothLeDevice.getFirstTimestamp()
  • 获取第一次信号强度(First RSSI):bluetoothLeDevice.getFirstRssi()
  • 获取最后一次信号时间戳(Last Timestamp):bluetoothLeDevice.getTimestamp()
  • 获取最后一次信号强度(Last RSSI):bluetoothLeDevice.getRssi()
  • 获取平均信号强度(Running Average RSSI):bluetoothLeDevice.getRunningAverageRssi()

SCAN RECORD INFO(广播信息)

根据扫描到的广播包AdRecordStore获取某个广播数据单元AdRecord的类型编号record.getType(),再根据编号获取广播数据单元的类型描述record.getHumanReadableType()以及该广播数据单元的长度及数据内容,最后通过AdRecordUtil.getRecordDataAsString(record)将数据内容转换成具体字符串。更多关于广播包解析可以参考Android BLE学习笔记中数据解析部分。

发送数据

在发送数据前需要先绑定写入数据通道,绑定通道的同时需要设置写入数据的回调监听,具体代码示例如下:

BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
        .setBluetoothGatt(deviceMirror.getBluetoothGatt())
        .setPropertyType(PropertyType.PROPERTY_WRITE)
        .setServiceUUID(serviceUUID)
        .setCharacteristicUUID(characteristicUUID)
        .setDescriptorUUID(descriptorUUID)
        .builder();
deviceMirror.bindChannel(new IBleCallback() {
    @Override
    public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {

    }

    @Override
    public void onFailure(BleException exception) {

    }
}, bluetoothGattChannel);
deviceMirror.writeData(data);

这里的 deviceMirror 在设备连接成功后就可以获取到,需要注意的是,服务一样的情况下写入数据的通道只需要注册一次,如果写入数据的通道有多个则可以绑定多个。写入数据必须要在绑定写入数据通道后进行,可以在不同的地方多次写入。

接收数据

与发送数据一样,接收设备发送的数据也需要绑定接收数据通道,这里有两种方式,一种是可通知方式、一种是指示器方式,使用方式如下:

  • 可通知方式
BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
        .setBluetoothGatt(deviceMirror.getBluetoothGatt())
        .setPropertyType(PropertyType.PROPERTY_NOTIFY)
        .setServiceUUID(serviceUUID)
        .setCharacteristicUUID(characteristicUUID)
        .setDescriptorUUID(descriptorUUID)
        .builder();
deviceMirror.bindChannel(new IBleCallback() {
    @Override
    public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {

    }

    @Override
    public void onFailure(BleException exception) {

    }
}, bluetoothGattChannel);
deviceMirror.registerNotify(false);
  • 指示器方式
BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
        .setBluetoothGatt(deviceMirror.getBluetoothGatt())
        .setPropertyType(PropertyType.PROPERTY_INDICATE)
        .setServiceUUID(serviceUUID)
        .setCharacteristicUUID(characteristicUUID)
        .setDescriptorUUID(descriptorUUID)
        .builder();
deviceMirror.bindChannel(new IBleCallback() {
    @Override
    public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {

    }

    @Override
    public void onFailure(BleException exception) {

    }
}, bluetoothGattChannel);
deviceMirror.registerNotify(true);

在绑定通道后需要注册通知,并需要在收到注册成功的回调时调用如下代码设置监听:

deviceMirror.setNotifyListener(bluetoothGattInfo.getGattInfoKey(), new IBleCallback() {
    @Override
    public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {

    }

    @Override
    public void onFailure(BleException exception) {

    }
});

所有设备发送过来的数据都会通过上面的监听得到,如果不想监听也可以取消注册,使用方式如下:

deviceMirror.unregisterNotify(isIndicate);

isIndicate 表示是否是指示器方式。

读取数据

由于读取设备信息基本每次的通道都不一样,所以这里与上面收发数据有点不一样,每次读取数据都需要绑定一次通道,使用示例如下:

BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
        .setBluetoothGatt(deviceMirror.getBluetoothGatt())
        .setPropertyType(PropertyType.PROPERTY_READ)
        .setServiceUUID(serviceUUID)
        .setCharacteristicUUID(characteristicUUID)
        .setDescriptorUUID(descriptorUUID)
        .builder();
deviceMirror.bindChannel(new IBleCallback() {
    @Override
    public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {

    }

    @Override
    public void onFailure(BleException exception) {

    }
}, bluetoothGattChannel);
deviceMirror.readData();

总结

从以上的描述中可以知道,设备相关的所有操作都统一交给 ViseBle 进行处理,并且该类是单例模式,全局只有一个,管理很方便。使用该库提供的功能前必须要调用 ViseBle.getInstance().init(context); 进行初始化。每连接成功一款设备都会在设备镜像池中添加一款设备镜像,该设备镜像是维护设备连接成功后所有操作的核心类,在断开连接时会将该设备镜像从镜像池中移除,如果连接设备数量超过配置的最大连接数,那么设备镜像池会依据 Lru 算法自动移除最近最久未使用设备并断开连接。ViseBle 中封装了几个常用的 API,如:开始扫描与停止扫描、连接与断开连接、清除资源等,该库提供的功能尽量简单易用,这也正是该项目的宗旨。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 蓝牙 注:本文翻译自https://developer.android.com/guide/topics/conn...
    RxCode阅读 8,613评论 11 99
  • 左手倒影,右手寂寞,澄净的水面,晶莹剔透,盛满了月光雪。只是,我记得,那一波水里,我颤抖的手指触摸到的,是一丝丝的...
    门前折柳阅读 222评论 0 0