Android低功耗蓝牙(BLE)开发(二)

news/2025/1/16 6:57:17/

在上一篇文章中我们了解了BLE的相关概念,这里我们来实际用代码演示安卓进行BLE连接和通讯的功能。本文代码基于Android5.0以上(API 21)

1.声明权限

在AndroidManifest.xml文件中添加BLE相关的权限声明。

<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><!-- 安卓12开始需要下列权限 compileSDK 32+ -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /><!--安卓6.0以及以上版本需要添加定位的权限 (需要在代码中动态申请)-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" /><!--如果你的app只为具有BLE的设备提供,请声明-->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

2.判断设备是否支持BLE以及蓝牙是否打开

/***  判断设备是否支持BLE*/
fun checkSupportBLE(context: Context):Boolean{val packageManager: PackageManager = context.packageManagerreturn packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
}/*** 判断蓝牙是否打开*/
fun isBluetoothEnabled(context: Context):Boolean{val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?bluetoothAdapter = bluetoothManager?.adapterreturn bluetoothAdapter == null || bluetoothAdapter?.isEnabled == false
}

3.进行扫描

val scanCallback: ScanCallback = object : ScanCallback() {override fun onScanResult(callbackType: Int, result: ScanResult?) {val device= result?.getDevice()// 处理扫描到的设备}
}val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
bluetoothAdapter.bluetoothLeScanner.startScan(scanCallback)

4.建立连接并监听

在BluetoothGattCallback进行监听相关回调

val gattCallback = object : BluetoothGattCallback() {override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {if (newState == BluetoothProfile.STATE_CONNECTED) {// 连接成功,进行服务发现gatt?.discoverServices()} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 连接断开,处理断开逻辑}}override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {if (status == BluetoothGatt.GATT_SUCCESS) {// 服务发现成功,处理服务和特征值val services = gatt?.servicesservices?.let {for (service in it) {// 处理服务和特征值}}} else {// 服务发现失败}}override fun onCharacteristicRead(gatt: BluetoothGatt,characteristic: BluetoothGattCharacteristic,value: ByteArray,status: Int) {if (status == BluetoothGatt.GATT_SUCCESS) {Log.i(TAG, "读取特征值")// 从特征值读取数据// characteristic 是特征值,而特征值是用 16bit 或者 128bit,16bit 是官方认证过的,128bit 是可以自定义的val sucString = characteristic.value}}override fun onCharacteristicWrite(gatt: BluetoothGatt?,characteristic: BluetoothGattCharacteristic?,status: Int) {super.onCharacteristicWrite(gatt, characteristic, status)if (status == BluetoothGatt.GATT_SUCCESS) {Log.i(TAG, "写入特征值")}}override fun onCharacteristicChanged(gatt: BluetoothGatt,characteristic: BluetoothGattCharacteristic,value: ByteArray) {Log.i(TAG, "特征值${characteristic.uuid.toString()}变化")}override fun onDescriptorRead(gatt: BluetoothGatt,descriptor: BluetoothGattDescriptor,status: Int,value: ByteArray) {Log.i(TAG, "描述符${descriptor.uuid.toString()}读取")Log.i(TAG, "描述符值:${String(descriptor.value)}")}override fun onDescriptorWrite(gatt: BluetoothGatt?,descriptor: BluetoothGattDescriptor?,status: Int) {Log.i(TAG, "描述符${descriptor?.uuid.toString()}写入")}override fun onReadRemoteRssi(gatt: BluetoothGatt?, rssi: Int, status: Int) {super.onReadRemoteRssi(gatt, rssi, status)//rssi值是蓝牙的信号值,离得越远信号越小Log.i(TAG, "蓝牙信号值:$rssi")}
}val gatt: BluetoothGatt = bluetoothDevice.connectGatt(context, false, gattCallback)

5.读取特征值

 /*** 读取特征值,读取成功后将回调在BluetoothGattCallback的onCharacteristicRead方法中*/
fun readCharacteristic(characteristic: BluetoothGattCharacteristic){//设置特征值变化通知,必须设置,否则无法监听特征值变化情况bluetoothGatt?.setCharacteristicNotification(characteristic, true)//读取特征值bluetoothGatt?.readCharacteristic(characteristic)
}

6.写入特征值

/***  写入特征值,完成后将回调在BluetoothGattCallback的onCharacteristicWrite方法中*/
fun writeCharacteristic(characteristic: BluetoothGattCharacteristic){bluetoothGatt?.writeCharacteristic(characteristic)
}

7.断开连接

fun disconnect(){bluetoothGatt?.disconnect()bluetoothGatt?.close()
}

8.分包

在Android BLE通信中,如果要发送的数据大小超过MTU(最大传输单元)的限制,就需要进行数据分包处理。BLE蓝牙一包数据最多为20字节,因此安卓系统下最好不要使用BLE蓝牙传输大量数据。以下是一种常见的方法来实现BLE数据分包发送:

(1)获取MTU大小:首先,通过调用BluetoothGatt对象的requestMtu()方法来请求MTU大小,例如:

val mtu = 20// 设置期望的MTU大小
bluetoothGatt.requestMtu(mtu)

注意:requestMtu不一定会成功,尽量小一点

(2)监听MTU更新:在BluetoothGattCallback中的onMtuChanged()回调方法中处理MTU更新结果:

override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {if (status == BluetoothGatt.GATT_SUCCESS) {// MTU更新成功,可以开始发送数据sendData(bluetoothGatt, data, mtu)} else {// MTU更新失败,处理失败逻辑}
}

(3)数据分包发送:根据MTU大小将要发送的数据拆分成多个分包,并通过BluetoothGattCharacteristicsetValue()writeCharacteristic()方法进行发送。以下是一个简单的示例:

/*** 往指定特征值写数据,分包*/
fun sendData(characteristic: BluetoothGattCharacteristic, data: ByteArray, mtu: Int) {Thread {val packetSize = mtu - 3 // 减去3个字节的包头// 将数据拆分为分包并发送var offset = 0while (offset < data.size) {val packet = data.sliceArray(offset until minOf(offset + packetSize, data.size))characteristic?.value = packetbleClient?.bluetoothGatt?.writeCharacteristic(characteristic)offset += packet.size}Log.d(BleClient.TAG, "发送完毕..")}.start()
}

在这个示例中,我们使用MTU大小减去3个字节(包头),得到每个分包的大小。然后,将要发送的数据按照分包大小拆分成多个分包,并通过setValue()方法设置分包数据,再通过writeCharacteristic()方法发送分包。

需要注意的是,每个分包的大小应该小于或等于MTU减去3个字节。另外,数据的接收端也需要对分包进行合并和处理,以确保正确接收和还原原始数据。


http://www.ppmy.cn/news/415956.html

相关文章

【业务理解】什么是SPU、SKU、SKC

PU、SKU、SKC SPU Standard Product Unit &#xff08;标准化产品单元&#xff09; SPU是商品信息聚合的最小单位&#xff0c;是一组可复用、易检索的标准化信息的集合&#xff0c;该集合描述了一个产品的特性。通俗点讲&#xff0c;属性值、特性相同的商品就可以称为一个SPU。…

用oFono控制Nokia手机

因为oFono的plugins中加入了对Nokia Phonet的支持&#xff0c;oFono已经可以支持市场上比较高端的N系列手机了&#xff0c;如N900&#xff0c;N97等。我手上没有支持手机的列表&#xff0c;如果你有兴趣的&#xff0c;可以把你的手机通过数据线连上PC&#xff0c;在Linux用oFon…

里约热内卢圣徒java_里约热内卢:圣徒之城

快速搜索机型: 诺基亚 N73系列(240320) N73 5320 5320XM 5320di_XM 5630XM 5700 5700XM 5710XM 5730XM 6110 6110N 6120 6120C 6120ci 6121 6122C 6124C 6210S 6210ci 6220C 6290 6650F 6700S 6702S 6710N 6720C 6730c 6788 6788I 6790 C5 C5-01 E101 E50 E51 E52 E55 E65 E66 …

【贪吃蛇—Java程序员写Android游戏】系列 2. 用J2ME实现Android的Snake Sample预览

为了让大家更好的理解J2ME和Android编程的差别&#xff0c;我用J2ME重新实现了Android的Snake Sample。 下次&#xff0c;我会详细介绍在将Snake从Android移植到J2ME上时&#xff0c;需要特别注意的问题&#xff0c;并对Android和J2ME的区别和联系进行粗略的比较。 本次&#x…

关于linux服务器上生成的图片中文字为的乱码问题

一、功能描述 linux服务器后端生成图表&#xff08;使用了canvas和echarts&#xff09;&#xff0c;并将生成的图片发送到企业微信群里。 二、出现的问题 生成的图表中文展示不出来&#xff0c;是乱码。错误图表展示如下&#xff1a; 三、 文字乱码出现的原因 linux服务器没有…

从苏宁电器到卡巴斯基第10篇:我在苏宁电器当营业员 I

毕竟应聘的是营业员,门槛还是很低的 我应聘苏宁的时候已经到了2009年的8月初,记得当时苏宁电器的长春总部还在吉林大路与东盛大街交汇处的亚泰广场,我当时的面试就是在那里。我记得很清楚,那天等待面试的人还是非常多的。这从一个侧面也就说明,苏宁的人员流动性也是很大的…

关注N97应用开发

目前关注诺基亚N97应用开发&#xff0c;期待自己有一天能够开发widget应用程序。 大众点评、电影速递、华风天气等等这些是我目前接触到的最好的widget应用程序。 谢谢你的关注&#xff0c;希望在widget开发方面得到你的提示&#xff0c;包括widget的基本概念、开发环境、网络…