Android BLE 蓝牙开发,连接蓝牙设备进行通讯

您所在的位置:网站首页 蓝牙建立通信需要什么条件 Android BLE 蓝牙开发,连接蓝牙设备进行通讯

Android BLE 蓝牙开发,连接蓝牙设备进行通讯

2024-06-26 16:05:47| 来源: 网络整理| 查看: 265

1. 介绍

本篇主要基于 Android 官方的低功耗蓝牙连接服务。

讲解如何通过 UUID 连接蓝牙设备。如果你针对 GATT 服务不太了解。那么这篇应该能够稍微帮助到你。

官方文档地址:https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le?hl=zh_cn#connect

2. 概念

如果是老用户了,那么就应该知道曾经蓝牙设备是一个高耗电的部件。根本不可能长时间开启。而在蓝牙4.0版本之后,蓝牙的通讯,耗电,抗干扰都得到了显著提升。同时蓝牙成本也得到了降低。

然后才有了我们现在的各种穿戴设备例如手环,蓝牙耳机,蓝牙电子秤,蓝牙音箱等等的爆发。

同时,其他工业或者外置设备也都开始大量支持蓝牙通讯。因为能耗和成本降低了。

针对低功耗蓝牙通讯,Android 4.3(API 18)开始引入了 BLE 库。我们可以直接使用 Android SDK 中的蓝牙 BLE 库,而不用额外导入依赖库。

以前开发蓝牙通讯,还需要实现蓝牙配对。需要主动跳转到手机设置界面进行PIN码配对,然后配对通过之后才能进行蓝牙链接。 而使用BLE库,我们可以直接通过蓝牙设备的UUID进行连接(通过GATT服务),在当前应用内就能直接连接了。而不用通过系统设置。 市面上的各种手环的自动匹配链接,电子秤的自动连接等等都是通过GATT进行通讯和链接的。

2.1 术语GATT:全称为:Generic Attribute Profile,翻译为:通用属性配置文件。GATT 配置文件是一种通用规范,内容针对在 BLE 链路上发送和接收称为“属性ATT”的简短数据片段。目前所有低功耗应用配置文件均以 GATT 为基础。ATT:全称为:Attribute Protocol,翻译为:属性协议。它是 GATT 的构建基础,二者的关系也被称为 GATT/ATT。每个属性均由通用唯一标识符 (UUID) 进行唯一标识,后者是用于对信息进行唯一标识的字符串 ID 的 128 位标准化格式。由 ATT 传输的属性采用特征和服务格式。特征 Characteristic: 特征包含一个值和 0 至多个描述特征值的描述符。您可将特征理解为类型,后者与类类似。 描述符:描述符是描述特征值的已定义属性。例如,描述符可指定人类可读的描述、特征值的可接受范围或特定于特征值的度量单位。Service — 服务是一系列特征。例如,您可能拥有名为“心率监测器”的服务,其中包括“心率测量”等特征。

以上术语的介绍来源于Android官网

2.2 通讯过程

假如我们有一个蓝牙外置设备(Device),然后有一个支持蓝牙的移动设备(Phone)。两者之间的通讯方式步骤是:

Device 开启蓝牙。(通常这些设备都是开机之后,就默认开启蓝牙了)Phone 开启蓝牙。 Phone 发现 Device。Phone 与 Device 创建蓝牙连接。Phone 创建 Gatt 客户端,与 Device Gatt 服务端连接。Phone 通过 Gatt 服务功能获取 Device 中的消息,并发送消息给 Device 设备。

整个过程就是这样的。下面我也将按照这个通讯过程进行介绍。

3.开发

基于我的使用情况,从无到有的介绍,完整的蓝牙开发配置过程。给大家一个参考

语言主要为 Java

3.1 权限

要在应用中使用蓝牙功能,必须声明 BLUETOOTH 蓝牙权限。需要此权限才能执行任何蓝牙通信,例如请求连接、接受连接和传输数据等。

同时,还需要位置权限。因为蓝牙 LE 信标通常与位置相关联。如果不开启 ACCESS_FINE_LOCATION 权限。那么我们将会无法发现蓝牙设备。

也就是执行蓝牙扫描 API 无法得到任何结果(PS::Logcat 中的错误日志会告诉你,要开启位置权限,否则无法扫描发现蓝牙设备)。

代码语言:javascript复制

其中 android.permission.ACCESS_FINE_LOCATION 是高版本API 28 权限。如果要支持更低版本,就需要申请。

如果要执行蓝牙扫描功能,我们需要申请:权限

如果要执行蓝牙链接,开关蓝牙。需要申请:权限

而上面两个权限呢,是在 API 31 上才有效。而低版本就是申请:

权限也就够了。

权限配置完毕之后,就是代码开发了。

不管是高版本,还是低版本。将权限都申请可以说最稳妥了。

3.2 检测设备是否支持蓝牙

通常情况下,手机是有蓝牙的。而我们如果在其他 Android 系统的设备中,例如TV,平板,一体机等等。是否有蓝牙还真不能完整保证。

如果不确定的情况下,那么可以通过以下代码检查 BLE 的可用性。

代码语言:javascript复制 @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { //不支持蓝牙设备 finish(); } else { //支持蓝牙设备 }

蓝牙是否开启都不影响检查结果。它检查的是设备是否有蓝牙功能,而不是蓝牙是否启动,下面会介绍如何判断蓝牙是否启动

3.3 开启蓝牙

当我们设备也支持蓝牙了,权限也配置了。下一步就是获取 BluetoothAdapter 对象了。

代码语言:javascript复制final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

我们后续控制蓝牙的状态,都是通过该方法实现的。

首先,检测蓝牙是否开启。可以通过isEnabled()方法进行检测:

代码语言:javascript复制if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { //开启设备的蓝牙链接 bluetoothAdapter.enable();//开启蓝牙 //动态判断是否拥有位置权限ACCESS_COARSE_LOCATION 或ACCESS_FINE_LOCATION ,然后再执行蓝牙扫描 } else { //动态判断是否拥有位置权限ACCESS_COARSE_LOCATION 或ACCESS_FINE_LOCATION,然后再执行蓝牙扫描 }

我们其实可以直接使用bluetoothAdapter.enable()开启蓝牙。当蓝牙没有开启时,我们可以直接开启蓝牙。

这个方法的结果,并不是实时返回的。我们如果要知道蓝牙是否开启,需要监听蓝牙状态的广播才行。下面会介绍广播监听。

PS:这个方法需要android.Manifest.permission.BLUETOOTH_CONNECT 权限才能使用。

官方是建议我们通过Intent让系统设置进行开启蓝牙的。

代码语言:javascript复制if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }

但是现在startActivityForResult方法已经过时。我们可以使用Launcher来调用:

代码语言:javascript复制ActivityResultLauncher launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { //处理返回结果 } });

上面的 launcher需要在Activity 的 onCreate 方法中初始化。然后在需要进行蓝牙设置界面启动的地方配置:

代码语言:javascript复制Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); //创建一个蓝牙启动的意图 launcher.launch(enableBtIntent);//使用launcer启动这个意图就可以了。

我们如果使用bluetoothAdapter.enable();时Android Studio出现代码错误警告,可以在该代码使用的方法中添加:@SuppressLint("MissingPermission")注解。

3.4 广播监听

其实这个广播监听,是否需要。根据大家实际情况来定。不一定需要。

首先,创建一个动态广播对象:

代码语言:javascript复制 public class BluetoothFoundReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); //监听蓝牙状态之后,发送消息 try { if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) { //开始扫描 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { //结束扫描 } else if (BluetoothDevice.ACTION_FOUND.equals(action)) { //发现设备,每扫码到一个设备,都会触发一次 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); //我们可以得到蓝牙设备 } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { //蓝牙开关状态 int statue = bluetoothAdapter.getState(); switch (statue) { case BluetoothAdapter.STATE_OFF: Log.e(TAG, "蓝牙状态:,蓝牙关闭"); break; case BluetoothAdapter.STATE_ON: Log.e(TAG, "蓝牙状态:,蓝牙打开"); break; case BluetoothAdapter.STATE_TURNING_OFF: Log.e(TAG, "蓝牙状态:,蓝牙正在关闭"); break; case BluetoothAdapter.STATE_TURNING_ON: Log.e(TAG, "蓝牙状态:,蓝牙正在打开"); break; } } } catch (Exception e) { e.printStackTrace(); } } }

然后进行广播注册:

代码语言:javascript复制 bluetoothFoundReceiver = new BluetoothFoundReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//连接蓝牙,断开蓝牙 filter.addAction(BluetoothDevice.ACTION_FOUND);//找到设备的广播 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索完成的广播 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//状态改变 配对开始时,配对成功时 registerReceiver(bluetoothFoundReceiver, filter);

注册完毕后,在onDestroy方法中需要注销注册:

代码语言:javascript复制 @Override protected void onDestroy() { if (bluetoothFoundReceiver != null) unregisterReceiver(bluetoothFoundReceiver); //停止监听 super.onDestroy(); }

其实,我们只需要蓝牙状态的监听就可以了BluetoothAdapter.ACTION_STATE_CHANGED 其他的设备查找,配对。可以不用,因为触发到广播的设备查找效率太低,而且多次重复查找时,还会出现耗时变长。设备无法查找到的情况。

3.5 蓝牙设备查找

官方文档上推荐的查找方式是:

代码语言:javascript复制bluetoothAdapter.startLeScan(leScanCallback); //查找 bluetoothAdapter.stopLeScan(leScanCallback); //停止查找

可是现在这个方法也过时了。替换方法是:

代码语言:javascript复制BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner(); //不进行权限验证 ScanCallback callback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { BluetoothDevice device = result.getDevice();//得到设备 // Log.e(TAG, "发现设备" + device.getName()); } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); Log.e(TAG, "搜索错误" + errorCode); } }; scanner.startScan(callback);

onScanResult方法是一个在子线程触发的回调,我们不能在该方法中直接操作UI对象。

其次,扫描到一个蓝牙设备就会触发一次消息回调。我们可以得到一个BluetoothDevice对象。 也就是说这个方法中会触发多次回调,

所以建议,在扫描到我们的蓝牙设备之后,主动调用scanner.stopScan(callback);停止扫描。

PS:这种查找方式,不会触发蓝牙的遍历广播。我们如果开启广播进行监听设备扫描情况。如果通过startScan方法,广播中不会有回调。

上面是一个通用搜索模式,我们还可以配置自己的过滤条件。例如:

代码语言:javascript复制ScanFilter sn =new ScanFilter.Builder().setDeviceName("蓝牙设备的名称").setServiceUuid(ParcelUuid.fromString("我们的设备的Service UUID")).build(); List scanFilters=new ArrayList(); scanFilters.add(sn); scanner.startScan(scanFilters, new ScanSettings.Builder().build(),callback);

其中ScanFilter对象,我们可以配置我们想查找的蓝牙设备的信息。可以是setDeviceName,setServiceUuid,setDeviceAddress,setServiceSolicitationUuid等。

ScanSettings对象是可以定义我们的扫描模式,通过配置该项可以提高扫描效率。

默认情况下,执行的是:SCAN_MODE_LOW_POWER在低功耗模式下执行蓝牙LE扫描。 这是默认的扫描模式,因为它消耗最少的电量。

3.5.1 startDiscovery

如果上面的方法还不满足我们的情况,可以使用:

代码语言:javascript复制if (bluetoothAdapter.isDiscovering()) {//是否在扫描 bluetoothAdapter.cancelDiscovery(); //停止扫描 } //查找蓝牙 bluetoothAdapter.startDiscovery();

我们可以直接使用bluetoothAdapter进行扫描。这个方法触发之后是由系统进行蓝牙扫描。就和我们在手机的设置界面中点击蓝牙扫描一样。

上面的这个方法没有回调,因为所有的蓝牙设备的发现都将通过广播事件进行传递。

需要通过我上面的广播监听介绍的内容。进行实时获取到扫描到的设备。

使用上面的方法有几个缺点:

1.效率慢,耗时很长。

2.重复扫描会失败。不能说是失败了,而是系统会将重复扫描的请求进行阻止,关键的问题在于这个阻止操作是手机厂商定制的。

PS:不管是BluetoothLeScanner 还是bluetoothAdapter.startDiscovery() 去查找蓝牙设备。都不建议一直重复扫描。否则会出现无法扫描到设备,没有任何扫描结果等等情况。因为扫描是一个耗时耗电的操作。

3.6 链接Gatt

当我们扫描到了蓝牙设备之后,就会获取到BluetoothDevice对象。然后我们通过BluetoothDevice对象创建GATT服务进行后续的蓝牙通讯。

代码语言:javascript复制BluetoothDevice device;// 当我们通过扫描得到device对象之后,进行Gatt服务创建 BluetoothGatt bluetoothGatt = device.connectGatt(this, false, gattCallback);

第一个传参context没有什么可以介绍的。

第二个传参autoConnect:是一个boolean值对象,false代表直接连接到蓝牙设备。true代表在蓝牙设备可用时自动连接。

第三个参数BluetoothGattCallback 是Gatt服务的各种回调了。

我们通过gattCallback回调的内容,来得到与蓝牙设备的链接状态,数据通信内容等。

下面来详细介绍下BluetoothGattCallback对象的几个方法。

代码语言:javascript复制String SERVICE_UUID="00000-000000-000000-000000";//这个是我要链接的蓝牙设备的ServiceUUID BluetoothGattCallback gattCallback = new BluetoothGattCallback() { //GATT的链接状态回调 @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); if (newState == BluetoothProfile.STATE_CONNECTED) { gatt.discoverServices(); Log.v(TAG, "连接成功"); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.e(TAG, "连接断开"); } else if (newState == BluetoothProfile.STATE_CONNECTING) { //TODO 在实际过程中,该方法并没有调用 Log.e(TAG, "连接中...."); } } //获取GATT服务发现后的回调 @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.e(TAG, "GATT_SUCCESS"); //服务发现 for (BluetoothGattService bluetoothGattService : gatt.getServices()) { Log.e(TAG, "Service_UUID" + bluetoothGattService.getUuid()); // 我们可以遍历到该蓝牙设备的全部Service对象。然后通过比较Service的UUID,我们可以区分该服务是属于什么业务的 if (SERVICE_UUID.equals(bluetoothGattService.getUuid().toString())) { for (BluetoothGattCharacteristic characteristic : bluetoothGattService.getCharacteristics()) { prepareBroadcastDataNotify(gatt, characteristic); //给满足条件的属性配置上消息通知 } return;//结束循环操作 } } } else { Log.e(TAG, "onServicesDiscovered received: " + status); } } //蓝牙设备发送消息后的自动监听 @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // readUUID 是我要链接的蓝牙设备的消息读UUID值,跟通知的特性的UUID比较。这样可以避免其他消息的污染。 if (READ_UUID.equals(characteristic.getUuid().toString())) { try { String chara = new String(characteristic.getValue(), "UTF-8"); Log.e(TAG, "消息内容:" + chara); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } };

我们可以通过链接成功和链接断开。来判断我们当前与蓝牙设备的通讯状态。

当我们比对Service的UUID成功之后, 我们就可以获取Service的Characteristic对象。该对象也就是特征。通过注册特征来实现消息的监听和发送业务。

3.7 注册消息监听-setCharacteristicNotification代码语言:javascript复制 @SuppressLint("MissingPermission") private void prepareBroadcastDataNotify(BluetoothGatt mBluetoothGatt, BluetoothGattCharacteristic characteristic) { Log.e(TAG, "CharacteristicUUID:" + characteristic.getUuid().toString()); int charaProp = characteristic.getProperties(); //判断属性是否支持消息通知 if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(UUIDManager.READ_DEDSCRIPTION_UUID)); if (descriptor != null) { //注册消息通知 mBluetoothGatt.setCharacteristicNotification(characteristic, true); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); } } }

在上面的示例中:READ_DEDSCRIPTION_UUID = "00002902-0000-1000-8000-00805f9b34fb" 是固定的,不管你链接什么样的蓝牙设备。

在注册消息监听,都是使用UUID值是00002902-0000-1000-8000-00805f9b34fb进行的。这个是Android系统保留的。用于动态监听的。

你如果不想使用这个动态监听。就需要自己写线程主动去轮询获取到蓝牙设备发送过来的消息了。

到这里,我们其实就能够实现蓝牙设备的实时监听,并得到消息内容了。

3.8 写数据到蓝牙设备中

我们如果想将内容推送到蓝牙设备中,在发现服务的时候onServicesDiscovered 遍历特性中,确保是用于写消息的特性对象后。选择持有该特性,然后通过:

代码语言:javascript复制String data ="0x12"; BluetoothGattCharacteristic writeCharact = bluetoothGattService. getCharacteristic(UUID.fromString(WRITE_UUID)); //查找UUID是写的特性,并检测是否拥有写权限 if (writeCharact == null || writeCharact.getProperties() != BluetoothGattCharacteristic.PROPERTY_WRITE) { return ;//该特性没有写的权限。所以无法传入 } // 当数据传递到蓝牙之后 // 会回调BluetoothGattCallback里面的write方法 writeCharact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); // 将需要传递的数据转为16进制数 writeCharact.setValue(data); bluetoothGatt.writeCharacteristic(writeCharact);3.9 关闭连接

当蓝牙通讯结束,或者界面关闭时。我们需要关闭GATT服务,减少资源占用。

代码语言:javascript复制if (bluetoothGatt != null) { bluetoothGatt.close(); bluetoothGatt.disconnect(); bluetoothGatt = null; }

也可以关闭BluetoothGattCallback 的回调监听:

代码语言:javascript复制gattCallback.disConnectBlue();//关闭GATT服务回调监听4. 小结

到这里蓝牙的链接和读取就结束了。

我们通过bluetoothAdapter 查找到蓝牙设备之后,再通过GATT服务进行蓝牙设备与手机之间的配对。直接比对UUID,而不再需要PIN码进行配对了。

(PS:有些安全性要求比较高的设备,还是会需要主动进行PIN码配对。PIN配队就只能通过系统设备界面中的蓝牙功能项进行操作了。)

通过GATT服务连接成功后。就可以查询该Server下的各种特性了,不同的特性对应了一个功能。有发消息的特性,也有用于收消息的特性。

同时一个蓝牙设备对象,可能有多种服务功能。

如果不想自己写线程变量轮询设备发送过来的消息,就通过注册消息监听。让BLE框架帮我们进行轮询之后,再通知到我们。

如果觉得总结的还可以,希望能够点个赞鼓励一下,谢谢。



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭