资讯动态

Android BLE开发避坑指南:从官方Demo到毕设落地的完整链路

发布时间:2026/10/17 8:15:27 来源:尧图企业网站定制
简介本资源是基于Android官方蓝牙低功耗BLE开发指南实现的完整Demo工程面向Android应用开发者及嵌入式通信初学者聚焦BLE核心流程实践设备扫描scan、连接建立与断开connection/disconnect、服务发现discover service及UUID交互等关键能力。压缩包共70个文件涵盖26个Class类文件含BLE状态管理与回调逻辑、15个XML布局与配置资源、9个PNG图标素材、7个Java源码文件主Activity与BluetoothAdapter操作封装以及APK可运行包、JAR依赖库和项目构建配置文件project.properties、.classpath等整体仅1.34MB轻量易导入。已有525人学习下载代码结构清晰、模块职责分明完整复现了Android 4.0 BLE典型通信链路附带可直接调试的APK与详细Manifest权限声明是理解Android BLE协议栈集成与调试的优质入门参考。1. 为什么官方 BLE Demo 不是“抄完就能跑”而是你毕设/课设最该啃透的硬骨头很多同学拿到 Android 官方 BLE Demo比如BluetoothLeGatt示例第一反应是「官方出的肯定开箱即用」——结果一导入 Android Studio 就报Cannot resolve symbol BluetoothLeService改完依赖又卡在扫描没回调连上设备后读特征值返回0x80错误码服务发现永远停在onServicesDiscovered不触发。这不是你环境有问题而是 BLE 协议栈在 Android 上的落地本身就带着三重「玄学门槛」系统版本对蓝牙权限的差异化处理Android 12 要求BLUETOOTH_SCAN动态授权、HCI 层与 GATT 层的状态机耦合scan → connect → discover → read/write 是强时序链断一环全崩、以及 UUID 解析的隐式陷阱16-bit 标准 UUID 被自动补全为 128-bit但自定义服务 UUID 若未按规范格式声明getUuid()返回值会错位。这份 Demo 不是教学玩具它是 Google 工程师把 BLE 开发中所有「血泪经验」压缩进一个可运行工程的黑匣子。它适合两类人一类是正在做蓝牙通信类毕设/课设、需要交一份「能连、能读、能写、能答辩」完整方案的学生另一类是刚接手 BLE 模块开发、需要快速建立「协议-代码-硬件」映射认知的工程师。下面我就带你一层层拆开这个黑匣子不讲抽象概念只告诉你每一行关键代码为什么这么写、改哪里会翻车、验收时怎么证明它真能用。2. 总体架构设计基于BluetoothLeGatt的四层职责划分与状态流闭环官方BluetoothLeGattDemo 并非单 Activity 堆砌而是一个典型的「UI-Service-BLE-Device」四层分治结构。这种设计不是为了炫技而是为了解耦 BLE 状态机与界面生命周期——因为BluetoothAdapter和BluetoothGatt对象一旦被 Activity 销毁就失效而 BLE 连接过程动辄数秒用户切后台再回来连接状态必须存活。我一般会强制要求学生在课设里保留这个分层哪怕只是做个简单读温湿度的 Demo也得先搭好骨架否则后期加通知、后台扫描、多设备管理时必然重构。2.1 四层模块职责与交互边界UI 层DeviceControlActivity只负责展示扫描列表、连接状态、服务/特征值树形结构。所有蓝牙操作startScan、connect、readCharacteristic都通过LocalBroadcastManager发送 Intent 触发 Service绝不直接调用BluetoothAdapter。Service 层BluetoothLeService核心状态中枢。持有BluetoothGatt实例封装connectGatt()、discoverServices()、readCharacteristic()等 API 调用并通过BroadcastReceiver向 UI 层广播EXTRA_DATA如ACTION_GATT_CONNECTED。关键点在于它用startForegroundService()启动避免 Android 8.0 后台服务被杀。BLE 协议层BluetoothGattCallback子类真正的协议解析器。onConnectionStateChange()处理连接成败onServicesDiscovered()解析服务树onCharacteristicRead()拿到原始字节数组。这里最容易被忽略的是status参数——它不是连接成功与否的标志而是 GATT 操作的底层返回码BluetoothGatt.GATT_SUCCESS才是成功0x80是GATT_INVALID_HANDLE说明你读的 characteristic handle 不存在。Device 层模拟外设或真实硬件Demo 中用nRF ConnectApp 模拟即可但必须确认其广播包含0x1809Temperature等标准服务 UUID且特征值支持READ属性。若用自定义固件需确保onServicesDiscovered()返回的服务数量 ≥ 1否则流程直接中断。提示不要在onServicesDiscovered()里直接调用readCharacteristic()必须先用gatt.getService(uuid).getCharacteristic(uuid)获取有效实例否则readCharacteristic(null)会静默失败。2.2 状态机流转图从扫描到数据读取的七步关键节点整个 BLE 交互不是线性流程而是一个带异常分支的状态机。官方 Demo 用mConnectionState枚举STATE_DISCONNECTED/STATE_CONNECTING/STATE_CONNECTED/STATE_DISCONNECTING标记当前阶段但真正驱动流转的是BluetoothGattCallback的回调链startScan()→ 触发onScanResult()扫描到设备connectGatt()→ 触发onConnectionStateChange()status2 表示连接中status0 表示成功discoverServices()→ 触发onServicesDiscovered()status0 才继续getService().getCharacteristic()→ 获取有效 Characteristic 实例setCharacteristicNotification()→ 开启通知需同时写CLIENT_CHARACTERISTIC_CONFIGdescriptorreadCharacteristic()或writeCharacteristic()→ 主动读写close()disconnect()→ 清理资源必须成对调用否则设备端可能卡死这七步里第 2 步和第 3 步耗时最长尤其在低端手机上必须给 UI 加 loading 提示第 5 步若漏写 descriptoronCharacteristicChanged()永远不会触发——这是答辩时老师最爱问的「为什么通知收不到」问题。2.3 权限与兼容性配置AndroidManifest.xml 与 runtime check 的双保险官方 Demo 在AndroidManifest.xml中声明了全部必要权限但实际运行时仍需动态申请Android 12 强制!-- 必须声明否则编译报错 -- uses-permission android:nameandroid.permission.BLUETOOTH / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / !-- Android 12 新增 -- uses-permission android:nameandroid.permission.BLUETOOTH_SCAN / uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION /但光有声明不够。DeviceControlActivity的onCreate()中必须插入 runtime check// Android 12 需要 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) ! PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT}, PERMISSION_REQUEST_CODE); return; } } else { // Android 11 及以下只需 ACCESS_FINE_LOCATION因扫描需定位 if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_CODE); return; } }注意ACCESS_FINE_LOCATION在 Android 12 已被弃用但某些旧设备仍需它来启用扫描所以建议if-else分支保留。如果只申请BLUETOOTH_SCAN而不申请BLUETOOTH_CONNECTconnectGatt()会直接抛SecurityExceptionLogcat 显示Permission Denial: is not exported from uid—— 这个错误码非常误导人实际就是权限没给全。3. 核心模块实现扫描、连接、服务发现与特征值读写的四段关键代码官方 Demo 的价值不在 UI而在BluetoothLeService.java里那四段被反复调用的核心逻辑。我把它们拆解成可独立复用的代码块并标注每个参数的「为什么必须这样设」。3.1 扫描启动startScan()的过滤器与超时控制// BluetoothLeService.java 中 startScan() 方法 private void scanLeDevice(final boolean enable) { final BluetoothAdapter adapter mBluetoothAdapter; if (enable) { // Step 1: 清空旧扫描结果避免重复添加 mLeDevices.clear(); // Step 2: 创建扫描过滤器可选但强烈建议 ListScanFilter filters new ArrayList(); ScanFilter.Builder builder new ScanFilter.Builder(); // 过滤特定服务 UUID如体温计常用 0x1809 builder.setServiceUuid(ParcelUuid.fromString(00001809-0000-1000-8000-00805f9b34fb)); filters.add(builder.build()); // Step 3: 设置扫描设置重点SCAN_MODE_LOW_LATENCY 保证响应速度 ScanSettings settings new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // 关键默认 SCAN_MODE_BALANCED 响应慢 .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) .build(); // Step 4: 启动扫描带超时防止无限扫 mScanner.startScan(filters, settings, mScanCallback); mHandler.postDelayed(() - { if (mScanning) { mScanner.stopScan(mScanCallback); // 超时主动停 broadcastUpdate(ACTION_SCAN_TIMEOUT); } }, SCAN_PERIOD); // SCAN_PERIOD 10000ms } else { mScanner.stopScan(mScanCallback); } }设计说明ScanFilter不是必须项但加了它能大幅减少无效设备干扰。若你的硬件广播包不含该 UUID过滤器会让它彻底消失——所以调试初期建议先注释掉filters确认能扫到设备后再加。SCAN_MODE_LOW_LATENCY是关键参数。官方文档说它「消耗更多电量但扫描更快」实测在小米 12 上BALANCED模式下扫描延迟达 2~3 秒而LOW_LATENCY可压到 300ms 内这对需要快速配网的课设场景至关重要。mHandler.postDelayed()是防呆设计。很多学生忘记停扫描导致mLeDevices越积越多列表卡顿甚至onScanResult()回调堆积引发 ANR。3.2 连接建立connectGatt()的 autoConnect 陷阱与重连策略// BluetoothLeService.java 中 connect() 方法 public boolean connect(final String address) { if (mBluetoothAdapter null || address null) { return false; } // Step 1: 先尝试断开旧连接避免 handle 冲突 if (mBluetoothDeviceAddress ! null address.equals(mBluetoothDeviceAddress) mBluetoothGatt ! null) { mBluetoothGatt.disconnect(); // 注意disconnect() 不等于 close() return true; } // Step 2: 获取设备对象address 是 MAC 地址字符串 final BluetoothDevice device mBluetoothAdapter.getRemoteDevice(address); if (device null) { return false; } // Step 3: 关键autoConnect false官方 Demo 默认值 mBluetoothGatt device.connectGatt(this, false, mGattCallback); mBluetoothDeviceAddress address; return true; }设计说明autoConnect false是硬性要求。设为true时系统会在设备进入范围时自动重连但mGattCallback的onConnectionStateChange()会多次触发且status可能为GATT_FAILURE导致状态混乱。课设答辩时老师若问「如何保证连接稳定性」你就答「禁用 autoConnect由 UI 层显式控制连接时机并在onConnectionStateChange()中做重试逻辑」。disconnect()和close()必须区分disconnect()是发送断开请求给外设close()是释放本地BluetoothGatt资源。正确顺序是disconnect()→ 等待onConnectionStateChange()返回STATE_DISCONNECTED→ 再调用close()。漏掉close()会导致内存泄漏多次连接后 App 崩溃。3.3 服务发现discoverServices()的异步阻塞与 UUID 解析坑// BluetoothLeService.java 中 initialize() 方法连接成功后调用 public boolean initialize() { if (mBluetoothAdapter null || mBluetoothGatt null) { return false; } // Step 1: 请求 MTU可选但大包传输必须 mBluetoothGatt.requestMtu(512); // 最大 512 字节需外设支持 // Step 2: 发起服务发现异步结果在 onServicesDiscovered() boolean result mBluetoothGatt.discoverServices(); Log.d(TAG, discoverServices() returned: result); // 注意这里永远返回 true return result; } // onServicesDiscovered() 回调中解析服务 Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status BluetoothGatt.GATT_SUCCESS) { // Step 1: 遍历所有服务 ListBluetoothGattService services gatt.getServices(); for (BluetoothGattService service : services) { Log.d(TAG, Found service: service.getUuid().toString()); // Step 2: 关键获取 16-bit UUID 的正确方式 // 错误写法service.getUuid().toString().substring(0, 4) → 可能截断 // 正确写法使用 UUID 的 getMostSignificantBits() long msb service.getUuid().getMostSignificantBits(); if ((msb 0xFFFF000000000000L) 0x0000180900000000L) { // 匹配到 0x1809 服务 mTemperatureService service; } } // Step 3: 只有找到目标服务后才去获取其特征值 if (mTemperatureService ! null) { BluetoothGattCharacteristic tempChar mTemperatureService.getCharacteristic( UUID.fromString(00002a1c-0000-1000-8000-00805f9b34fb)); // Temperature Measurement if (tempChar ! null) { mTemperatureCharacteristic tempChar; broadcastUpdate(ACTION_DATA_AVAILABLE); } } } else { Log.w(TAG, onServicesDiscovered received: status); // status133 是常见错误 } }设计说明discoverServices()的返回值boolean毫无意义永远true成败全看onServicesDiscovered()的status。status133GATT_ERROR是最常见错误原因通常是外设未响应或连接不稳定此时必须重连不能停留在界面上干等。UUID 解析是高频翻车点。toString()返回的是 128-bit 格式如00001809-0000-1000-8000-00805f9b34fb但getUuid()返回的对象内部存储是long类型直接toString().substring(0,4)会因大小端问题错位。正确做法是用getMostSignificantBits()提取高位再与标准值比对。requestMtu(512)是为后续大包传输铺路。若不调用writeCharacteristic()默认最大 20 字节传一张图片或固件包直接失败。3.4 特征值读写readCharacteristic()的字节解析与通知开启// BluetoothLeService.java 中 readCharacteristic() 方法 public void readCharacteristic(BluetoothGattCharacteristic characteristic) { if (mBluetoothAdapter null || mBluetoothGatt null) { return; } // Step 1: 必须先 check characteristic 是否可读 if ((characteristic.getProperties() BluetoothGattCharacteristic.PROPERTY_READ) 0) { Log.e(TAG, Characteristic not readable: characteristic.getUuid()); return; } // Step 2: 执行读操作异步 boolean result mBluetoothGatt.readCharacteristic(characteristic); Log.d(TAG, readCharacteristic() returned: result); } // onCharacteristicRead() 回调中解析数据 Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status BluetoothGatt.GATT_SUCCESS) { byte[] data characteristic.getValue(); if (data ! null data.length 0) { // Step 1: 温度值通常为 IEEE-11073 FLOAT2字节或 uint162字节 // 假设是 uint16 小端序data[0] data[1]*256 int tempRaw (data[0] 0xFF) | ((data[1] 0xFF) 8); float temperature tempRaw / 100.0f; // 某些传感器单位为 0.01℃ Log.d(TAG, Temperature: temperature ℃); Intent intent new Intent(ACTION_DATA_AVAILABLE); intent.putExtra(EXTRA_DATA, String.valueOf(temperature)); localBroadcastManager.sendBroadcast(intent); } } else { Log.w(TAG, onCharacteristicRead failed: status); // status133 再次出现 } } // 开启通知用于实时接收数据 public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) { if (mBluetoothAdapter null || mBluetoothGatt null) { return; } // Step 1: 先启用特征值通知 mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); // Step 2: 关键必须写 CLIENT_CHARACTERISTIC_CONFIG descriptor BluetoothGattDescriptor descriptor characteristic.getDescriptor( UUID.fromString(00002902-0000-1000-8000-00805f9b34fb)); // CCCD UUID if (descriptor ! null) { descriptor.setValue(enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); } }设计说明readCharacteristic()前必须检查PROPERTIES否则onCharacteristicRead()的status会是GATT_READ_NOT_PERMITTED0x87但 Logcat 不会明确提示只能靠characteristic.getProperties()调试。字节解析必须匹配外设文档。nRF Connect里看到的0x02 0x00在 Java 里是data[0]2, data[1]0小端序组合为0x00022若误当大端序会算成0x0200512温度显示错乱。开启通知的两步缺一不可setCharacteristicNotification(true)只是告诉 Android 系统「我要收通知」writeDescriptor()才是真正向外设发送指令。漏掉第二步onCharacteristicChanged()永远不触发。4. 常见问题排查BLE 开发中五个必踩的「静默翻车点」BLE 开发最折磨人的不是报错而是「没报错但功能不生效」。以下是我在带某高校课程设计时学生提交的 37 份 BLE 课设中出现频率最高的五个静默问题。每个都附带现象、根因和可立即验证的解决步骤。4.1 现象onScanResult()完全不回调Logcat 无任何 BLE 日志原因Android 12 缺少BLUETOOTH_SCAN权限或AndroidManifest.xml中未声明uses-feature android:nameandroid.hardware.bluetooth_le android:requiredtrue /解决检查AndroidManifest.xml确认uses-feature存在且requiredtrue若设为false部分设备会禁用 BLE在DeviceControlActivity的onCreate()中用PackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)判断硬件支持若返回false弹 Toast 提示「设备不支持 BLE」而非静默失败4.2 现象connectGatt()后onConnectionStateChange()的status133反复重试无效原因外设处于「不可连接」广播状态如刚上电未进入可连接模式或手机蓝牙芯片与外设协议栈不兼容常见于 Realtek 芯片手机连 Nordic nRF52解决用nRF ConnectApp 单独连接该设备确认是否能连——若nRF Connect也失败则是外设问题在onConnectionStateChange()中若status133且newStateSTATE_DISCONNECTED等待 2 秒后调用connect()重试最多 3 次在重试前强制调用mBluetoothAdapter.disable()→mBluetoothAdapter.enable()重启蓝牙仅调试用正式版删掉4.3 现象onServicesDiscovered()调用但gatt.getServices()返回空列表原因discoverServices()调用过早在onConnectionStateChange()的status0之前就执行即连接尚未完全建立解决在onConnectionStateChange()中严格判断status BluetoothGatt.GATT_SUCCESS newState BluetoothProfile.STATE_CONNECTED只在此条件下调用initialize()内含discoverServices()在initialize()开头加日志Log.d(TAG, Starting service discovery...)确认执行时机4.4 现象readCharacteristic()后onCharacteristicRead()的status0但characteristic.getValue()返回null原因外设未在onReadRequest()中调用sendResponse()或characteristic.setValue(byte[])未被正确赋值解决在onCharacteristicRead()中先打印characteristic.toString()确认getValue()是否为null若为null检查外设固件在onReadRequest()回调中必须调用gattServer.sendResponse(device, requestId, GATT_SUCCESS, offset, value)其中value不能为空数组在 Android 端readCharacteristic()前加Log.d(TAG, Reading: characteristic.getUuid())与外设日志比对请求 UUID4.5 现象开启通知后onCharacteristicChanged()从不触发但writeDescriptor()返回true原因writeDescriptor()是异步操作onDescriptorWrite()回调未收到GATT_SUCCESS但代码已继续执行解决在setCharacteristicNotification()中writeDescriptor()后不立即返回而是等待onDescriptorWrite()在onDescriptorWrite()中检查statusGATT_SUCCESS再广播ACTION_NOTIFY_ENABLEDUI 层监听此广播而非在点击「开启通知」按钮后立刻认为已生效5. 验收与答辩用三张表、两个现场演示、一套答辩话术拿下高分毕设/课设验收不是看你代码能不能跑而是考你是否真正理解 BLE 的「协议-实现-硬件」三角关系。我带过的某跨平台系统课设学生用这套方法让答辩老师当场追问「你们怎么解决 Android 12 权限降级的」最后给了 95 分。下面是我总结的硬核验收清单。5.1 验收必备三张表覆盖协议、代码、硬件三层证据表类型内容要点为什么必须有答辩时怎么用BLE 协议对照表列出本次 Demo 涉及的 GATT 操作Scan/Connect/Discover/Read/Notify、对应 Android API、标准返回码GATT_SUCCESS0, GATT_INVALID_HANDLE0x80、外设需支持的 ATT 属性如 READ/WRITE/NOTIFY证明你不是盲目调 API而是按蓝牙 SIG 规范开发答辩 PPT 第一页放此表老师问「为什么用 discoverServices() 而不用 fetchUuidsWithSdp()」你指表说「后者是旧版 SDP 协议Android 4.3 已废弃且不支持 BLE」关键代码参数表ScanSettings.SCAN_MODE_LOW_LATENCY、connectGatt(autoConnectfalse)、requestMtu(512)、BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE等参数的取值依据引用 Android SDK 文档章节证明参数选择有据可查非凭感觉当老师质疑「为什么 MTU 设 512」你打开表格指「Android 5.0 支持最大 512且外设 spec sheet 第 3.2 节明确支持」硬件兼容性表测试过的手机型号华为 Mate 40 Pro/小米 12/三星 S22、系统版本Android 11~14、外设型号nRF52832/nRF52840、固件版本以及各组合下的成功率如「小米 12 Android 13扫描成功率 98%连接成功率 92%」证明你做了真实测试不是只在模拟器跑通老师若说「我们实验室用的是 OPPO Reno8」你立刻翻表「OPPO Reno8 测试记录在 Appendix AAndroid 13 下连接成功率 89%失败主因是蓝牙驱动 Bug我们加了 3 次重试后提升至 97%」5.2 现场演示的两个必做动作让老师亲眼看到「协议在跑」很多学生演示只点「连接」→「读温度」老师看不到底层交互。必须加两个动作把黑匣子打开动作一Logcat 实时投屏聚焦三个关键日志段扫描阶段onScanResult()输出设备名 MAC RSSI证明扫描真实发生连接阶段onConnectionStateChange()的status0和newState2STATE_CONNECTED证明连接成功读取阶段onCharacteristicRead()的status0和getValue()的十六进制字节数组如02 00证明数据真实到达提示提前在 Logcat 过滤BluetoothLeService和DeviceControlActivity避免刷屏。老师若问「怎么知道数据没被篡改」你指02 00说「这是外设固件 raw output我们未做任何转换解析逻辑在onCharacteristicRead()注释第 12 行」动作二用nRF Connect同步连接同一外设对比行为在演示机上运行你的 App同时用另一台手机开nRF Connect连同一设备当你的 App 点击「开启通知」立刻在nRF Connect里观察该 characteristic 的 Notify 开关是否同步点亮当你的 App 读到25.5℃在nRF Connect里手动读同一 characteristic确认值一致这一步直接证明你的 App 与标准工具行为一致协议栈工作正常。老师若质疑「你们是不是伪造了数据」这个对比就是铁证。5.3 答辩话术用「问题-解法-依据」三段式回应所有刁难老师最爱问「如果……怎么办」别背答案用结构化话术接招问题「如果外设不支持requestMtu(512)你们怎么适配」解法「我们做了 fallback 机制先requestMtu(512)若onMtuChanged()返回mtu512则自动切到mtu值分包传输。比如mtu23时512 字节数据拆成 23 包每包加 sequence number外设端重组。」依据「参考蓝牙 SIG ATT 协议 Section 4.1.1MTU negotiation 是标准流程Android 5.0 全支持。」问题「Android 14 权限模型又有变化你们的方案还适用吗」解法「我们预留了权限抽象层PermissionManager类封装所有权限检查新增权限只需在checkPermission()方法里加if (Build.VERSION.SDK_INT 34) { ... }分支不改动业务逻辑。」依据「Google 官方《Permissions best practices》明确建议『Use permission groups and abstract checks』我们已在BluetoothLeService的initPermission()方法里实现。」问题「为什么不用第三方库如 RxAndroidBle」解法「课设要求『掌握原生 BLE API』且 RxAndroidBle 会隐藏状态机细节。比如它的establishConnection()自动重连但我们发现这会导致onConnectionStateChange()多次触发与外设状态不同步。」依据「我们对比了 RxAndroidBle v3.0.1 源码其ConnectionModule的retryWhen()逻辑与BluetoothGattCallback的status解析存在时序冲突详见我们实验报告 Appendix B 的时序图。」从那以后我每次带课设都强制学生在答辩前三天用真机跑通这三张表、两个演示、三段话术。不是为了应付老师而是逼自己把 BLE 从「能跑」变成「懂为什么能跑」。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑