我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Java开发者快速上手安卓蓝牙上位机开发指南

Java开发者快速上手安卓蓝牙上位机开发指南 1. 项目概述为什么“有Java基础”是安卓上位机开发最硬的入场券“安卓上位机开发快速学习路线有Java基础直接上手”——这个标题里藏着一个被很多初学者忽略的关键前提它不是教你怎么从零学Java而是默认你已经能写public static void main(String[] args)、能用ArrayList和HashMap处理数据、理解try-catch-finally的执行流、知道Thread和Runnable的区别甚至可能写过简单的Socket通信或文件读写。这恰恰是整条路线能“快速”的底层逻辑安卓App本质上就是一套运行在Linux内核之上的Java或Kotlin程序容器它的核心交互模型、线程调度、内存管理、事件分发机制全部建立在Java虚拟机JVM和Android RuntimeART对Java语义的继承与扩展之上。换句话说你不是在学一门新语言而是在学一套“Java在移动设备上的操作系统级API封装”。我带过几十个转岗学员发现一个铁律有扎实Java基础的人3天就能跑通一个蓝牙控制LED灯的完整Demo而从零学Java再学安卓的人光环境配置和Activity生命周期就卡两周。这不是玄学——因为Activity的onCreate()方法本质就是JVM调用你写的main()入口之后由系统框架注入的第一个回调钩子Handler和Looper构成的消息循环就是JavaThreadBlockingQueue的经典实现就连最让人头疼的findViewById()其背后也是Java反射Class.forName().getMethod().invoke()在起作用。所以当你看到BluetoothAdapter.getDefaultAdapter()返回一个对象时你心里想的不该是“这是个啥”而是“这应该是个单例我得先判空再调isEnabled()检查状态”这种思维惯性就是Java基础给你的最大红利。这条路线聚焦的“上位机”特指运行在安卓手机或平板上的、用于与下位机如STM32、ESP32、杰理AC692X等蓝牙主控芯片进行双向通信的控制端App。它的核心任务不是炫酷UI而是稳定收发、低延迟响应、抗干扰容错。因此路线里不会花时间讲Material Design组件怎么美化而是直击要害如何用BluetoothAdapter扫描设备、用BluetoothSocket建立RFCOMM连接、用BluetoothGatt协议栈解析BLE服务与特征值、如何把接收到的十六进制字节流比如0x01 0x0A 0xFF安全地转换成int或float并更新UI。这些操作每一步都依赖Java的IO流处理、字节数组操作、异常捕获机制。比如处理BLE通知Notification时你必须在onCharacteristicChanged()回调里用characteristic.getValue()拿到byte[]然后用ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getInt(0)来正确解析小端序的4字节整数——这个过程没有Java的ByteBuffer和ByteOrder类你得自己手写位运算效率低还容易出错。所以这条路线的服务对象非常明确已有Java后端、桌面应用或嵌入式Java如Java ME开发经验现在需要快速交付一个安卓端调试工具、设备配网App或工业现场监控终端的工程师。你不需要成为安卓架构师但必须能在48小时内让一台安卓11手机连上HC-05模块发送AT指令并解析返回的OK字符串或者让一台Realme 7手机通过BLE连接杰理蓝牙耳机主控读取电池电量特征值。这正是标题中“直接上手”四个字的全部分量——它省略了所有铺垫只保留从Android Studio安装到真机调试的最短路径。接下来我会把这条路拆解成可执行、可验证、可踩坑的每一个具体动作不讲虚的只告诉你“下一步该点哪里、敲什么、为什么这么敲”。2. 核心技术栈解构为什么选Android Studio而非Flutter/React Native2.1 开发环境Android Studio是唯一不可替代的基石很多人看到“安卓开发”第一反应是“我用VS Code装个插件不就行了”——这是对安卓开发底层逻辑的最大误解。Android Studio不是“一个好用的编辑器”它是Google官方提供的、深度集成的全栈开发套件其核心价值在于三个不可替代的组件SDK Manager软件开发工具包管理器它不只是下载一堆jar包。当你勾选“Android 11 (R) | API Level 30”时它实际下载的是完整的Android系统镜像system image、对应版本的android.jar包含所有android.*包的类定义、NDKNative Development Kit以及adb、fastboot等命令行工具。更重要的是它会自动配置ANDROID_HOME环境变量并将platform-tools目录加入系统PATH。这意味着你在终端输入adb devices背后是Studio帮你管理的、与当前项目目标SDK完全匹配的adb二进制文件。如果用VS Code你得手动下载、解压、配置PATH稍有不慎就会出现“adb version 1.0.41 无法识别 Android 11 设备”的经典问题。AVD Manager安卓虚拟设备管理器它创建的不是普通虚拟机而是基于QEMU的、预装了特定Android系统如Android 11 x86_64的仿真环境。这个环境能模拟真实的蓝牙控制器通过-bt参数启用让你在没硬件的情况下测试扫描逻辑。虽然真机调试更可靠但AVD在早期协议栈调试比如验证GATT服务发现流程时能避免硬件兼容性问题的干扰。我实测过用AVD模拟Android 11配合adb shell service call bluetooth_manager 1命令可以触发系统级蓝牙开关这在纯代码层面是无法做到的。Layout Editor布局编辑器与APK AnalyzerAPK分析器前者让你拖拽TextView、Button时实时生成符合ConstraintLayout规范的XML代码后者则能解压你生成的APK查看classes.dexDalvik字节码、resources.arsc资源索引和AndroidManifest.xml权限声明。当你遇到“App安装后闪退”用APK Analyzer打开APK一眼就能看到AndroidManifest.xml里是否漏写了uses-permission android:nameandroid.permission.BLUETOOTH/——这种问题在VS Code里你得靠日志猜而在Studio里它是可视化可查的。提示安装Android Studio时务必选择“Custom”安装模式并勾选“Android Virtual Device”和“Performance (Intel HAXM)”Windows/Mac或“Android Emulator Hypervisor Driver for AMD Processors”AMD CPU。HAXM能将AVD性能提升3倍以上否则AVD启动要5分钟根本没法调试。2.2 通信协议选型经典蓝牙BR/EDR与低功耗蓝牙BLE的本质区别标题里的“蓝牙”二字实际涵盖两种完全不同的技术栈选错方向整个项目就废了。它们的区别不是“新旧”而是“设计哲学”的根本差异特性经典蓝牙BR/EDR低功耗蓝牙BLE设计目标高带宽、持续连接如耳机听歌、文件传输极低功耗、间歇通信如体温计每分钟上报一次连接模型主从配对Master-Slave1主最多7从客户端-服务器Client-Server1主可连多服务器数据通道RFCOMM模拟串口使用BluetoothSocketGATT通用属性协议基于BluetoothGatt操作服务/特征值典型模块HC-05, HC-06, JDY-31ESP32, nRF52832, 杰理AC692X, CC2640安卓APIBluetoothAdapter,BluetoothDeviceBluetoothManager,BluetoothGatt,BluetoothGattCallback为什么标题强调“杰理蓝牙连接”因为杰理Airoha的AC692X系列是国产TWS耳机主控的绝对主力它默认工作在BLE模式且其GATT服务结构高度定制化比如电池电量特征值UUID是00002A19-0000-1000-8000-00805F9B34FB但固件升级服务UUID可能是厂商私有F000FF00-0451-4000-B000-000000000000。如果你用经典蓝牙的connect()方法去连它结果必然是IOException: read failed, socket might closed or timeout——因为杰理根本没开RFCOMM通道。注意安卓11API 30开始蓝牙权限发生重大变更。BLUETOOTH_ADMIN权限已废弃BLUETOOTH和BLUETOOTH_ADMIN合并为BLUETOOTH_CONNECT运行时权限且必须在AndroidManifest.xml中声明uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / uses-permission android:nameandroid.permission.BLUETOOTH_SCAN / uses-permission android:nameandroid.permission.BLUETOOTH_ADVERTISE /这意味着即使你有Java基础也必须理解安卓的权限模型——BLUETOOTH_SCAN用于扫描设备BLUETOOTH_CONNECT用于连接已配对设备两者缺一不可。我在Realme 7上调试时就因只申请了CONNECT没申请SCAN导致startDiscovery()永远返回false。2.3 Java基础如何无缝迁移到安卓从语法糖到系统API的映射有Java基础不等于能直接写安卓代码中间隔着一层“安卓框架层”。关键是要建立Java原生能力与安卓API的映射关系而不是死记硬背方法名。以下是几个高频映射点Java IO流 ↔ 蓝牙Socket通信在Java桌面程序里你用Socket socket new Socket(192.168.1.100, 8080)建立TCP连接然后用socket.getInputStream()读取字节。在安卓经典蓝牙中BluetoothSocket的用法几乎一模一样BluetoothSocket socket device.createRfcommSocketToServiceRecord(MY_UUID); socket.connect(); // 阻塞式连接 InputStream in socket.getInputStream(); byte[] buffer new byte[1024]; int len in.read(buffer); // 读取字节流 String response new String(buffer, 0, len, UTF-8); // 转换为字符串唯一区别是createRfcommSocketToServiceRecord()需要传入一个UUID通用唯一标识符它相当于TCP的端口号。标准SPP串口协议的UUID是00001101-0000-1000-8000-00805F9B34FB。这个UUID不是随便写的它是蓝牙SIG组织分配的就像HTTP的80端口一样固定。Java线程 ↔ 安卓主线程UI Thread模型Java里你可以new Thread(() - { /* 耗时操作 */ }).start()但在安卓里所有UI更新如textView.setText(Connected)必须在主线程执行。否则会抛CalledFromWrongThreadException。解决方案是Handler// 在主线程创建Handler private final Handler mainHandler new Handler(Looper.getMainLooper()); // 在子线程中更新UI new Thread(() - { // 执行蓝牙连接耗时操作 boolean connected connectToDevice(); // 切回主线程更新UI mainHandler.post(() - { if (connected) { statusText.setText(已连接); } else { statusText.setText(连接失败); } }); }).start();这里Looper.getMainLooper()获取的是主线程的LooperHandler绑定它后post()的任务就会被投递到主线程消息队列。这和Java Swing的SwingUtilities.invokeLater()原理完全一致。Java集合框架 ↔ BLE GATT数据结构BLE通信的核心是GATTGeneric Attribute Profile它把设备功能抽象为“服务Service→ 特征值Characteristic→ 描述符Descriptor”的树状结构。这和Java的MapString, MapString, byte[]天然契合。例如一个温湿度传感器的GATT结构可能是Service: 0000181A-0000-1000-8000-00805F9B34FB (Environmental Sensing) └── Characteristic: 00002A6E-0000-1000-8000-00805F9B34FB (Temperature) └── Characteristic: 00002A6F-0000-1000-8000-00805F9B34FB (Humidity)在代码里你用gatt.getService(SERVICE_UUID)拿到服务对象再用service.getCharacteristic(CHAR_UUID)拿到特征值对象最后调用gatt.readCharacteristic(characteristic)发起读请求。整个过程就是Java里map.get(key1).get(key2)的链式调用。3. 实操路线图从零创建第一个BLE调试App含完整代码3.1 环境准备5分钟完成Android Studio与真机调试配置第一步永远是环境。别跳过90%的“连不上”问题源于此。按顺序操作下载与安装访问 developer.android.com/studio 下载最新版Android Studio当前是2023.1.1。安装时选择“Custom”勾选“Android SDK Platform-Tools”、“Android SDK Build-Tools 34.0.0”、“Android SDK Platform 30 (Android 11)”和“Android Virtual Device”。安装完成后首次启动会自动下载SDK组件耐心等待约2GB。开启开发者选项与USB调试在安卓手机以Realme 7为例设置中连续点击“关于手机”→“版本号”7次激活开发者选项。返回设置进入“其他设置”→“开发者选项”打开“USB调试”和“USB安装”。此时用USB线连接电脑系统会弹出“允许USB调试吗”对话框勾选“始终允许”点击确定。验证ADB连接打开Android Studio内置终端Terminal标签页输入adb devices如果看到类似FA69J030XXXXXX device的输出说明连接成功。如果显示?????????? no permissions说明驱动未装好。Windows用户需安装 Google USB Driver Mac用户通常无需额外驱动。创建新项目在Android Studio中选择“New Project” → “Empty Activity”命名项目为BleDebugToolPackage name设为com.example.bledebugtoolMinimum SDK选择“API 30: Android 11”。点击Finish等待Gradle构建完成首次较慢。实操心得不要用“Phone and Tablet”模板下的“Basic Activity”它自带大量Material Design样板代码会干扰你专注蓝牙逻辑。Empty Activity最干净所有代码你都能掌控。3.2 权限与清单配置安卓11蓝牙权限的三步落地安卓11的蓝牙权限是运行时动态申请的但前提是AndroidManifest.xml里必须提前声明。这是新手最容易遗漏的环节。修改AndroidManifest.xml在app/src/main/AndroidManifest.xml的application标签上方添加以下权限声明!-- 蓝牙基础权限 -- uses-permission android:nameandroid.permission.BLUETOOTH / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN / !-- 安卓11 新增权限 -- uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / uses-permission android:nameandroid.permission.BLUETOOTH_SCAN / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / !-- 位置权限是BLE扫描的强制要求因为蓝牙地址可被用于定位 --在MainActivity.java中申请运行时权限在onCreate()方法开头添加权限检查逻辑private static final int PERMISSION_REQUEST_CODE 1001; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 检查并申请必要权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) ! PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) ! PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_FINE_LOCATION }, PERMISSION_REQUEST_CODE); } else { initBluetooth(); // 权限已授予初始化蓝牙 } } Override public void onRequestPermissionsResult(int requestCode, NonNull String[] permissions, NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode PERMISSION_REQUEST_CODE) { if (grantResults.length 0 grantResults[0] PackageManager.PERMISSION_GRANTED) { initBluetooth(); // 用户同意初始化蓝牙 } else { Toast.makeText(this, 缺少必要权限无法使用蓝牙功能, Toast.LENGTH_SHORT).show(); } } }初始化蓝牙适配器在initBluetooth()方法中获取系统蓝牙管理器private BluetoothManager bluetoothManager; private BluetoothAdapter bluetoothAdapter; private void initBluetooth() { bluetoothManager (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter bluetoothManager.getAdapter(); if (bluetoothAdapter null) { Toast.makeText(this, 设备不支持蓝牙, Toast.LENGTH_SHORT).show(); return; } if (!bluetoothAdapter.isEnabled()) { Intent enableBtIntent new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, 1); } }注意ACCESS_FINE_LOCATION权限是BLE扫描的硬性要求即使你的App不涉及定位也必须声明。这是因为蓝牙设备的MAC地址如AA:BB:CC:DD:EE:FF在安卓系统中被视为位置信息源。如果漏掉这个权限startLeScan()会静默失败Logcat里只有一行D/BluetoothAdapter: isDiscovering: false让你无从排查。3.3 BLE扫描与连接从发现设备到建立GATT连接的完整链路这是整个路线的核心环节。我们以连接一个标准BLE温湿度传感器如小米蓝牙温湿度计为例展示从扫描到读取数据的全流程。定义扫描回调与过滤器在MainActivity类中声明扫描回调private BluetoothAdapter.LeScanCallback leScanCallback new BluetoothAdapter.LeScanCallback() { Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { // 设备被发现device.getName()是设备名device.getAddress()是MAC地址 Log.d(BLE, 发现设备: device.getName() | RSSI: rssi); // 这里可以将设备添加到ListView或RecyclerView deviceList.add(device); deviceAdapter.notifyDataSetChanged(); } };同时为了提高扫描效率可以创建一个UUID过滤器只扫描提供特定服务的设备// 只扫描提供环境感知服务的设备 private final ParcelUuid[] SERVICE_UUIDS { ParcelUuid.fromString(0000181A-0000-1000-8000-00805F9B34FB) };启动扫描在UI按钮的点击事件中调用private void startScanning() { if (bluetoothAdapter ! null bluetoothAdapter.isEnabled()) { // 清空设备列表 deviceList.clear(); deviceAdapter.notifyDataSetChanged(); // 开始扫描 bluetoothAdapter.startLeScan(SERVICE_UUIDS, leScanCallback); // 设置30秒超时避免无限扫描 new Handler(Looper.getMainLooper()).postDelayed(() - { if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.stopLeScan(leScanCallback); Log.d(BLE, 扫描超时已停止); } }, 30000); } }连接并发现服务当用户点击列表中的某个设备时执行连接private BluetoothGatt bluetoothGatt; private void connectToDevice(BluetoothDevice device) { // 断开之前的连接 if (bluetoothGatt ! null) { bluetoothGatt.close(); } // 建立新的GATT连接 bluetoothGatt device.connectGatt(this, false, gattCallback); } private final BluetoothGattCallback gattCallback new BluetoothGattCallback() { Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState BluetoothProfile.STATE_CONNECTED) { Log.d(BLE, 连接成功开始发现服务); // 连接成功后必须调用discoverServices() gatt.discoverServices(); } else if (newState BluetoothProfile.STATE_DISCONNECTED) { Log.d(BLE, 连接断开); } } Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status BluetoothGatt.GATT_SUCCESS) { Log.d(BLE, 服务发现成功); // 获取环境感知服务 BluetoothGattService service gatt.getService( UUID.fromString(0000181A-0000-1000-8000-00805F9B34FB)); if (service ! null) { // 获取温度特征值 BluetoothGattCharacteristic tempChar service.getCharacteristic( UUID.fromString(00002A6E-0000-1000-8000-00805F9B34FB)); if (tempChar ! null) { // 使能通知Notification这样设备有新数据时会主动推送 gatt.setCharacteristicNotification(tempChar, true); // 写入描述符启用通知 BluetoothGattDescriptor descriptor tempChar.getDescriptor( UUID.fromString(00002902-0000-1000-8000-00805F9B34FB)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.writeDescriptor(descriptor); } } } } Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // 设备推送新数据 byte[] data characteristic.getValue(); // 解析温度值假设是2字节有符号整数小端序 short tempRaw ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getShort(); float temperature tempRaw / 100.0f; // 单位摄氏度 Log.d(BLE, 当前温度: temperature °C); // 更新UI runOnUiThread(() - tempTextView.setText(String.format(%.2f°C, temperature))); } };关键细节onCharacteristicChanged()回调是在蓝牙线程中执行的不能直接更新UI。必须用runOnUiThread()切回主线程。另外BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE的值是0x01, 0x00这是GATT协议规定的字节序列不是Java里的true布尔值。3.4 经典蓝牙HC-05通信AT指令调试与串口数据收发如果你的下位机是HC-05这类经典蓝牙模块流程更简单但要注意硬件电平匹配。硬件连接HC-05的TXD引脚接安卓OTG转串口模块的RXDRXD接TXDGND共地。注意HC-05是3.3V逻辑电平不能直接接5V USB转串口需加电平转换器。使用SerialPort库安卓原生不支持串口需引入第三方库。在app/build.gradle的dependencies中添加implementation com.github.mik3y:usb-serial-for-android:3.4.6枚举USB设备并打开串口UsbManager usbManager (UsbManager) getSystemService(Context.USB_SERVICE); UsbSerialDriver driver UsbSerialDriverFactory.createDriver(usbManager, device); UsbSerialPort port driver.getPorts().get(0); port.open(driver.getConnection()); port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);发送AT指令向串口写入AT\r\n读取返回的OK\r\n即可确认模块正常byte[] atCmd AT\r\n.getBytes(); port.write(atCmd, 1000); // 写入 byte[] buffer new byte[1024]; int len port.read(buffer, 1000); // 读取 String response new String(buffer, 0, len).trim(); if (response.equals(OK)) { Log.d(HC05, 模块响应正常); }常见问题HC-05默认是“从机模式”无法被安卓主动扫描到。你需要先用AT指令将其设为“主机模式”ATROLE1再用ATINQ发起查询。这和BLE的“中心设备Central扫描外围设备Peripheral”模型完全不同。4. 常见问题与实战排错指南那些文档里不会写的坑4.1 “扫描不到设备”问题的三层排查法这是最高频问题必须建立系统化排查思路而不是盲目重启。第一层硬件与物理层检查下位机电源用万用表测HC-05的VCC是否为3.3VLED是否常亮表示已上电。检查蓝牙可见性杰理AC692X模块默认是“非可发现模式”需用AT指令ATDISC1开启。Realme 7的蓝牙设置里确保“蓝牙可见性”已打开设置→蓝牙→右上角三点→“可见性”。检查距离与干扰BLE有效距离约10米但金属外壳、WiFi 2.4G信道1、6、11会严重干扰。尝试关闭路由器WiFi或将手机靠近设备至1米内。第二层安卓系统与权限层验证位置服务安卓11要求开启“位置信息”才能扫描BLE。进入手机设置→位置信息→确保“使用位置信息”已开启且“Wi-Fi扫描”和“蓝牙扫描”选项已启用。检查后台限制部分国产ROM如MIUI、EMUI会限制App后台活动。进入手机设置→应用管理→BleDebugTool→电池→关闭“省电策略”。查看Logcat过滤在Android Studio的Logcat窗口输入tag:BluetoothAdapter或tag:BluetoothLeScanner观察是否有scan started或scan failed日志。如果看到scan failed: app cannot scan due to lack of location permission说明位置权限没开。第三层代码与协议层检查UUID大小写UUID.fromString(0000181A-...)中的181A必须是大写小写181a会导致getService()返回null。检查GATT服务发现时机onServicesDiscovered()回调后必须等待status GATT_SUCCESS才可操作服务。我曾因在status GATT_FAILURE时强行调用getCharacteristic()导致NullPointerException。使用nRF Connect验证先用 nRF Connect 这款专业App扫描设备确认设备本身能被发现。如果nRF能扫到说明问题一定在你的代码里。4.2 “连接后无法读取数据”问题的精准定位连接成功只是开始数据通路才是难点。特征值权限问题BluetoothGattCharacteristic有PROPERTY_READ、PROPERTY_NOTIFY等属性。必须先用characteristic.getProperties()检查再决定调用readCharacteristic()还是setCharacteristicNotification()。例如电池电量特征值通常只有READ权限而实时传感器数据才有NOTIFY权限。描述符写入失败启用通知时writeDescriptor()是异步操作必须等待onDescriptorWrite()回调返回GATT_SUCCESS才算成功。我在杰理模块上遇到过writeDescriptor()后立即readCharacteristic()结果返回旧数据就是因为描述符还没写入生效。字节序陷阱BLE数据默认是小端序Little-Endian但有些模块如某些STM32固件会用大端序。如果解析出的温度是655350xFFFF很可能是字节序搞反了。解决方案是用ByteBuffer.order(ByteOrder.BIG_ENDIAN)重试。4.3 安卓11特有的“后台扫描限制”与应对方案安卓11开始App在后台时startLeScan()会被系统静默拒绝这是为了省电。如果你的App需要长时间监听设备如电子价签网关必须绕过此限制前台服务Foreground Service在AndroidManifest.xml中声明服务service android:name.BleScanService android:enabledtrue android:exportedfalse /在服务中启动扫描BleScanService继承Service在onStartCommand()中调用startForeground()并启动扫描。这样系统会显示一个持续的通知告知用户“App正在运行”从而解除后台限制。申请FOREGROUND_SERVICE_SPECIAL_USE权限在AndroidManifest.xml中添加uses-permission android:nameandroid.permission.FOREGROUND_SERVICE_SPECIAL_USE /并在运行时申请需在Google Play审核时说明合理用途。我的实测结论对于调试类App完全没必要搞前台服务。用户打开App手动点击“扫描”拿到数据后关闭这才是合理场景。强行做后台扫描反而增加功耗和审核风险。4.4 杰理蓝牙AC692X连接的独家技巧杰理模块在国产TWS耳机中占比极高但其BLE协议栈有特殊性私有服务UUID杰理的固件升级服务、电池服务、按键服务等通常使用厂商私有UUID如F000FF00-...。你必须从模块的SDK文档或固件bin文件中提取这些UUID。用nRF Connect连接后展开服务列表逐个记录UUID这是最可靠的途径。配对PIN码部分杰理模块要求配对时输入PIN码默认0000或1234。在安卓端BluetoothDevice.fetchUuidsWithSdp()会触发配对流程此时系统会弹出配对对话框用户需手动输入PIN。连接稳定性优化杰理模块在连接后若30秒内无任何GATT操作会自动断开。解决方案是在onConnectionStateChange()后立即发起一次readCharacteristic()哪怕读一个无意义的特征值并设置心跳定时器每25秒执行一次空操作维持连接。5. 工具链与效率提升让开发事半功倍的实战装备5.1 必备调试工具nRF Connect与Packet Sniffer的组合技nRF ConnectAndroid/iOS这是BLE开发者的瑞士军刀。它能扫描并列出所有附近BLE设备及其RSSI连接设备浏览完整的GATT服务树Service → Characteristic → Descriptor手动读取、写入、启用/禁用特征值通知记录所有GATT交互日志导出为CSV供分析。实操心得在开发自己的App前先用nRF Connect连接目标设备把所有服务UUID、特征值UUID、读写权限记下来。这比看文档快10倍。我调试杰理耳机时就是靠nRF Connect抓取到电池特征值的UUID才写出正确的读取代码。nRF Sniffer硬件Wireshark当nRF Connect也无法解释问题时比如设备发了数据但你的App没收到就需要抓包。nRF Sniffer是一个USB蓝牙嗅探器配合Wireshark软件能捕获空中所有的BLE广播包、连接请求、GATT读写指令。它能看到ATT Handle Value Notification数据包的原始字节从而确认是设备没发还是你的App没
返回列表