1.1 声明权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
android.permission.BLUETOOTH : 这个权限允许程序连接到已配对的蓝牙设备, 请求连接/接收连接/传输数据需要改权限, 主要用于对配对后进行操作;
android.permission.BLUETOOTH_ADMIN : 这个权限允许程序发现和配对蓝牙设备, 该权限用来管理蓝牙设备, 有了这个权限, 应用才能使用本机的蓝牙设备, 主要用于对配对前的操作;
android.permission.ACCESS_COARSE_LOCATION和android.permission.ACCESS_FINE_LOCATION:Android 6.0以后,这两个权限是必须的,蓝牙扫描周围的设备需要获取模糊的位置信息。这两个权限属于同一组危险权限,在清单文件中声明之后,还需要再运行时动态获取。
1.2. 初始化及配置
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BleManager.getInstance().init(getApplication());
BleManager.getInstance()
.enableLog(true)
.setReConnectCount(1, 5000)
.setOperateTimeout(5000);
}
在使用之前,需要事先调用初始化init(Application app)方法。此外,可以进行一些自定义的配置,比如是否显示框架内部日志,重连次数和重连时间间隔,以及操作超时时间。
1.3. 扫描外围设备
APP作为中心设备,想要与外围硬件设备建立蓝牙通信的前提是首先得到设备对象,途径是扫描。在调用扫描方法之前,你首先应该先处理下面的准备工作。
判断当前Android设备是否支持BLE。
Android 4.3以后系统中加入了蓝牙BLE的功能。
BleManager.getInstance().isSupportBle();
判断当前Android设备的蓝牙是否已经打开。
可以直接调用下面的判断方法来判断本机是否已经打开了蓝牙,如果没有,向用户抛出提示。
BleManager.getInstance().isBlueEnable();
主动打开蓝牙。
除了判断蓝牙是否打开给以用户提示之外,我们也可以通过程序直接帮助用户打开蓝牙开关,打开方式有这几种:
方法1:通过蓝牙适配器直接打开蓝牙。
BleManager.getInstance().enableBluetooth();
方法2:通过startActivityForResult引导界面引导用户打开蓝牙。
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 0x01);
需要注意的是,第一种方法是异步的,打开蓝牙需要一段时间,调用此方法后,蓝牙不会立刻就处于开启状态。如果使用此方法后紧接者就需要进行扫描,建议维护一个阻塞线程,内部每隔一段时间查询蓝牙是否处于开启状态,外部显示等待UI引导用户等待,直至开启成功。使用第二种方法,会通过系统弹出框的形式引导用户开启,最终通过onActivityResult的形式回调通知是否开启成功。
6.0及以上机型动态获取位置权限。
蓝牙打开之后,进行扫描之前,需要判断下当前设备是否是6.0及以上,如果是,需要动态获取之前在Manifest中声明的位置权限。
配置扫描规则
扫描规则可以配置1个或多个,也可以不配置使用默认(扫描10秒)。扫描的时候,会根据配置的过滤选项,对扫描到的设备进行过滤,结果返回过滤后的设备。扫描时间配置为小于等于0,会实现无限扫描,直至调用BleManger.getInstance().cancelScan()来中止扫描。
BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
.setServiceUuids(serviceUuids) // 只扫描指定的服务的设备,可选
.setDeviceName(true, names) // 只扫描指定广播名的设备,可选
.setDeviceMac(mac) // 只扫描指定mac的设备,可选
.setAutoConnect(isAutoConnect) // 连接时的autoConnect参数,可选,默认false
.setScanTimeOut(10000) // 扫描超时时间,可选,默认10秒
.build();
BleManager.getInstance().initScanRule(scanRuleConfig);
以上准备工作完成后,就可以开始进行扫描。
BleManager.getInstance().scan(new BleScanCallback() {
@Override
public void onScanStarted(boolean success) {
}
@Override
public void onLeScan(BleDevice bleDevice) {
}
@Override
public void onScanning(BleDevice bleDevice) {
}
@Override
public void onScanFinished(List<BleDevice> scanResultList) {
}
});
onScanStarted(boolean success): 会回到主线程,参数表示本次扫描动作是否开启成功。由于蓝牙没有打开,上一次扫描没有结束等原因,会造成扫描开启失败。
onLeScan(BleDevice bleDevice):扫描过程中所有被扫描到的结果回调。由于扫描及过滤的过程是在工作线程中的,此方法也处于工作线程中。同一个设备会在不同的时间,携带自身不同的状态(比如信号强度等),出现在这个回调方法中,出现次数取决于周围的设备量及外围设备的广播间隔。
onScanning(BleDevice bleDevice):扫描过程中的所有过滤后的结果回调。与onLeScan区别之处在于:它会回到主线程;同一个设备只会出现一次;出现的设备是经过扫描过滤规则过滤后的设备。
onScanFinished(List<BleDevice> scanResultList):本次扫描时段内所有被扫描且过滤后的设备集合。它会回到主线程,相当于onScanning设备之和。
1.4. 设备信息
扫描得到的BLE外围设备,会以BleDevice对象的形式,作为后续操作的最小单元对象。它本身含有这些信息:
String getName():蓝牙广播名
String getMac():蓝牙Mac地址
byte[] getScanRecord(): 被扫描到时候携带的广播数据
int getRssi() :被扫描到时候的信号强度
后续进行设备连接、断开、判断设备状态,读写操作等时候,都会用到这个对象。可以把它理解为外围蓝牙设备的载体,所有对外围蓝牙设备的操作,都通过这个对象来传导。
1.5. 连接、断连、监控连接状态
拿到设备对象之后,可以进行连接操作。
BleManager.getInstance().connect(bleDevice, new BleGattCallback() {
@Override
public void onStartConnect() {
}
@Override
public void onConnectFail(BleException exception) {
}
@Override
public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
}
@Override
public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {
}
});
onStartConnect():开始进行连接。
onConnectFail(BleException exception):连接不成功。
onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status):连接成功并发现服务。
onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status):连接断开,特指连接后再断开的情况。在这里可以监控设备的连接状态,一旦连接断开,可以根据自身情况考虑对BleDevice对象进行重连操作。需要注意的是,断开和重连之间最好间隔一段时间,否则可能会出现长时间连接不上的情况。此外,如果通过调用disconnect(BleDevice bleDevice)方法,主动断开蓝牙连接的结果也会在这个方法中回调,此时isActiveDisConnected将会是true。
1.6. GATT协议
BLE连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。它定义两个 BLE 设备通过Service 和 Characteristic 进行通信。GATT 就是使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service, Characteristic以及对应的数据保存在一个查找表中,次查找表使用 16 bit ID 作为每一项的索引。
关于GATT这部分内容会在下面重点讲解。总之,中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。当连接成功之后,外围设备与中心设备之间就建立起了GATT连接。
上面讲到的connect(BleDevice bleDevice, BleGattCallback bleGattCallback)方法其实是有返回值的,这个返回值就是BluetoothGatt。当然还有其他方式可以获取BluetoothGatt对象,连接成功后,调用:
BluetoothGatt gatt = BleManager.getInstance().getBluetoothGatt(BleDevice bleDevice);
通过BluetoothGatt对象作为连接桥梁,中心设备可以获取外围设备的很多信息,以及双向通信。
首先,就可以获取这个蓝牙设备所拥有的Service和Characteristic。每一个属性都可以被定义作不同的用途,通过它们来进行协议通信。下面的方法,就是通过BluetoothGatt,查找出所有的Service和Characteristic的UUID:
List<BluetoothGattService> serviceList = bluetoothGatt.getServices();
for (BluetoothGattService service : serviceList) {
UUID uuid_service = service.getUuid();
List<BluetoothGattCharacteristic> characteristicList= service.getCharacteristics();
for(BluetoothGattCharacteristic characteristic : characteristicList) {
UUID uuid_chara = characteristic.getUuid();
}
}
1.7. 协议通信
APP与设备建立了连接,并且知道了Service和Characteristic(需要与硬件协议沟通确认)之后,我们就可以通过BLE协议进行通信了。通信的桥梁,主要就是是通过 标准的或者自定义的Characteristic,中文我们称之为“特征”。我们可以从 Characteristic 读数据和写数据。这样就实现了双向的通信。站在APP作为中心设备的角度,常用于数据交互的通信方式主要有3种:接收通知、写、读,此外还有设置最大传输单元,获取实时信号强度等通信操作。
接收通知
有两种方式可以接收通知,indicate和notify。indicate和notify的区别就在于,indicate是一定会收到数据,notify有可能会丢失数据。indicate底层封装了应答机制,如果没有收到中央设备的回应,会再次发送直至成功;而notify不会有central收到数据的回应,可能无法保证数据到达的准确性,优势是速度快。通常情况下,当外围设备需要不断地发送数据给APP的时候,比如血压计在测量过程中的压力变化,胎心仪在监护过程中的实时数据传输,这种频繁的情况下,优先考虑notify形式。当只需要发送很少且很重要的一条数据给APP的时候,优先考虑indicate形式。当然,从Android开发角度的出发,如果硬件放已经考虑了成熟的协议和发送方式,我们需要做的仅仅是根据其配置的数据发送方式进行相应的对接即可。
打开notify
BleManager.getInstance().notify(
bleDevice,
uuid_service,
uuid_characteristic_notify,
new BleNotifyCallback() {
@Override
public void onNotifySuccess() {
// 打开通知操作成功
}
@Override
public void onNotifyFailure(BleException exception) {
// 打开通知操作失败
}
@Override
public void onCharacteristicChanged(byte[] data) {
// 打开通知后,设备发过来的数据将在这里出现
}
});
关闭notify
BleManager.getInstance().stopNotify(uuid_service, uuid_characteristic_notify);
打开indicate
BleManager.getInstance().indicate(
bleDevice,
uuid_service,
uuid_characteristic_indicate,
new BleIndicateCallback() {
@Override
public void onIndicateSuccess() {
// 打开通知操作成功
}
@Override
public void onIndicateFailure(BleException exception) {
// 打开通知操作失败
}
@Override
public void onCharacteristicChanged(byte[] data) {
// 打开通知后,设备发过来的数据将在这里出现
}
});
关闭indicate
BleManager.getInstance().stopIndicate(uuid_service, uuid_characteristic_indicate);
这里的通知操作用到了两个关键的参数,uuid_service和uuid_characteristic_notify(或uuid_characteristic_indicate),就是上面提到的Service和Characteristic,此处以字符串的形式体现,不区分大小写。
读写
BleManager.getInstance().read(
bleDevice,
uuid_service,
uuid_characteristic_read,
new BleReadCallback() {
@Override
public void onReadSuccess(byte[] data) {
// 读特征值数据成功
}
@Override
public void onReadFailure(BleException exception) {
// 读特征值数据失败
}
});
BleManager.getInstance().write(
bleDevice,
uuid_service,
uuid_characteristic_write,
data,
new BleWriteCallback() {
@Override
public void onWriteSuccess(int current, int total, byte[] justWrite) {
// 发送数据到设备成功(分包发送的情况下,可以通过方法中返回的参数可以查看发送进度)
}
@Override
public void onWriteFailure(BleException exception) {
// 发送数据到设备失败
}
});
进行BLE数据相互发送的时候,一次最多能发送20个字节。如果需要发送的数据超过20个字节,有两种方法,一种是主动尝试拓宽MTU,另一种是采用分包传输的方式。框架中的write方法,当遇到数据超过20字节的情况时,默认是进行分包发送的。
BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {
@Override
public void onSetMTUFailure(BleException exception) {
// 设置MTU失败
}
@Override
public void onMtuChanged(int mtu) {
// 设置MTU成功,并获得当前设备传输支持的MTU值
}
});
获取设备的实时信号强度Rssi
BleManager.getInstance().readRssi(
bleDevice,
new BleRssiCallback() {
@Override
public void onRssiFailure(BleException exception) {
// 读取设备的信号强度失败
}
@Override
public void onRssiSuccess(int rssi) {
// 读取设备的信号强度成功
}
});