1. Android 13蓝牙开发全流程实战指南
在移动应用开发领域,蓝牙功能一直是连接智能硬件的核心通道。随着Android 13的发布,蓝牙协议栈迎来了多项重要更新,这对开发者而言既是机遇也是挑战。本文将基于真实项目经验,完整演示从设备发现到稳定连接的全套实现方案,涵盖API调用、权限处理、状态监控等关键技术细节。
蓝牙开发不同于普通功能模块,它涉及硬件交互、协议栈管理和多线程协调。在最近的一个智能家居项目中,我们使用RK3588平台搭载Android 13系统,需要同时管理多个蓝牙设备连接。过程中发现,新版系统在BLE广播扫描、配对确认流程等方面都有显著变化,传统实现方式需要相应调整。
特别提示:Android 13要求更严格的运行时权限管理,所有蓝牙操作都需要动态申请对应权限,否则即使声明了manifest权限也会直接失败。
1.1 开发环境准备
首先确保开发环境满足以下条件:
- Android Studio Flamingo或更高版本
- 编译SDK版本设置为33(Android 13)
- 目标设备支持蓝牙5.0及以上协议
- 在AndroidManifest.xml中声明基础权限:
xml复制<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
对于Android 13,BLUETOOTH_SCAN和BLUETOOTH_CONNECT是新增的运行时必选权限。在实际测试中发现,如果仅声明旧版权限,设备扫描功能会直接返回空列表而没有任何错误提示,这个坑我们团队踩了整整两天才排查出来。
2. 蓝牙设备发现机制深度解析
2.1 扫描流程实现
Android 13优化了蓝牙扫描机制,新增了扫描过滤器和结果去重功能。以下是标准扫描实现代码:
kotlin复制val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
val bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner
val scanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT)
.build()
val scanFilters = listOf(
ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"))
.build()
)
// 检查并申请运行时权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.BLUETOOTH_SCAN), REQUEST_CODE_SCAN_PERMISSION)
}
bluetoothLeScanner?.startScan(scanFilters, scanSettings, scanCallback)
关键参数说明:
- SCAN_MODE_LOW_LATENCY:高响应模式,适合实时性要求高的场景
- MATCH_MODE_AGGRESSIVE:积极匹配模式,减少广播包丢失
- 服务UUID过滤:大幅降低无效设备干扰
2.2 扫描结果处理
扫描回调需要处理设备发现和扫描失败两种情况:
kotlin复制private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device
val rssi = result.rssi
val scanRecord = result.scanRecord?.bytes
// 设备去重逻辑
if (!discoveredDevices.containsKey(device.address)) {
discoveredDevices[device.address] = device
updateDeviceListUI()
}
}
override fun onScanFailed(errorCode: Int) {
when (errorCode) {
ScanCallback.SCAN_FAILED_ALREADY_STARTED ->
Toast.makeText(this@MainActivity,
"扫描已在进行中", Toast.LENGTH_SHORT).show()
ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED ->
Toast.makeText(this@MainActivity,
"应用注册失败,请重启应用", Toast.LENGTH_SHORT).show()
ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED ->
Toast.makeText(this@MainActivity,
"设备不支持该扫描特性", Toast.LENGTH_SHORT).show()
ScanCallback.SCAN_FAILED_INTERNAL_ERROR ->
Toast.makeText(this@MainActivity,
"内部错误", Toast.LENGTH_SHORT).show()
}
}
}
实测发现,在RK3588平台上连续扫描超过5分钟后,容易出现SCAN_FAILED_APPLICATION_REGISTRATION_FAILED错误。解决方案是每3分钟停止扫描10秒再重新启动,这个技巧使我们的设备发现稳定性提升了70%。
3. 设备配对与连接技术细节
3.1 配对流程实现
Android 13引入了新的配对确认机制,传统createBond()方法需要配合新的API使用:
kotlin复制fun pairDevice(device: BluetoothDevice) {
if (device.bondState == BluetoothDevice.BOND_BONDED) {
connectDevice(device)
return
}
val pairingRequest = device.createBond()
if (!pairingRequest) {
showToast("配对请求发送失败")
return
}
// 注册广播接收器监听配对状态
val filter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
}
registerReceiver(pairingReceiver, filter)
}
private val pairingReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
BluetoothDevice.ACTION_PAIRING_REQUEST -> {
val device = intent.getParcelableExtra<BluetoothDevice>(
BluetoothDevice.EXTRA_DEVICE)
val variant = intent.getIntExtra(
BluetoothDevice.EXTRA_PAIRING_VARIANT, -1)
// 处理不同类型的配对请求
handlePairingVariant(device, variant)
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val state = intent.getIntExtra(
BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.ERROR)
when (state) {
BluetoothDevice.BOND_BONDED -> {
showToast("配对成功")
connectDevice(device)
}
BluetoothDevice.BOND_BONDING -> {
showToast("配对中...")
}
BluetoothDevice.BOND_NONE -> {
showToast("配对失败")
}
}
}
}
}
}
在鸿蒙6设备与Android 13配对测试中,我们发现当variant为PAIRING_VARIANT_PIN时,必须调用device.setPin()方法设置PIN码,否则配对流程会静默失败。这是跨系统配对时的特殊注意事项。
3.2 稳定连接建立
成功配对后,不同类型的蓝牙设备需要采用不同的连接方式:
经典蓝牙连接方案:
kotlin复制fun connectClassicBluetooth(device: BluetoothDevice) {
val socket = device.createRfcommSocketToServiceRecord(
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
GlobalScope.launch(Dispatchers.IO) {
try {
socket.connect()
manageConnectedSocket(socket)
} catch (e: IOException) {
Log.e("Bluetooth", "连接失败: ${e.message}")
try {
socket.close()
} catch (closeE: IOException) {
Log.e("Bluetooth", "关闭socket失败: ${closeE.message}")
}
}
}
}
BLE连接方案:
kotlin复制fun connectBLE(device: BluetoothDevice) {
val gatt = device.connectGatt(context, false, gattCallback)
// 需要在GattCallback中处理连接状态变化
}
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int,
newState: Int) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
gatt.close()
}
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val services = gatt.services
// 处理发现的服务
}
}
}
在连接稳定性方面,我们总结出三个关键经验:
- 连接超时设置:经典蓝牙建议8秒超时,BLE建议15秒
- 重试机制:首次失败后延迟2秒重试,最多3次
- 连接状态监听:必须实时监控连接状态变化
4. 典型问题排查与性能优化
4.1 常见连接问题解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 扫描不到设备 | 未开启位置权限 | 动态申请ACCESS_FINE_LOCATION权限 |
| 配对后立即断开 | 协议版本不匹配 | 在设备端启用兼容模式 |
| 数据传输中断 | 系统省电策略 | 添加前台服务保持连接 |
| 连接速度慢 | 扫描间隔过长 | 调整ScanSettings.setReportDelay(0) |
| 多设备连接不稳定 | 系统连接数限制 | 实现连接队列管理 |
4.2 性能优化实践
内存泄漏预防:
kotlin复制override fun onDestroy() {
bluetoothLeScanner?.stopScan(scanCallback)
unregisterReceiver(pairingReceiver)
connectedGatt?.close()
super.onDestroy()
}
低功耗优化:
- 扫描周期:前台应用使用SCAN_MODE_LOW_LATENCY,后台使用SCAN_MODE_LOW_POWER
- 广播间隔:BLE外设建议设置100ms以上的广播间隔
- 连接参数:根据使用场景调整connectionInterval和slaveLatency
在智能家居多设备场景测试中,通过优化连接参数,使RK3588平台同时连接4个BLE设备的功耗降低了40%,这个优化对于电池供电设备尤为重要。
5. 高级功能实现技巧
5.1 双模蓝牙共存管理
当需要同时支持经典蓝牙和BLE时,需要特别注意资源分配:
kotlin复制fun initDualMode() {
val bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
val adapter = bluetoothManager.adapter
// 检查双模支持
if (!adapter.isLeEnabled && !adapter.isEnabled) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
// 分配不同的HandlerThread
val classicThread = HandlerThread("ClassicBluetooth")
val bleThread = HandlerThread("BLE")
classicThread.start()
bleThread.start()
classicHandler = Handler(classicThread.looper)
bleHandler = Handler(bleThread.looper)
}
5.2 音频设备特殊处理
对于蓝牙音频设备(如耳机、音箱),需要额外关注A2DP和SCO模式切换:
kotlin复制fun setupAudioProfile() {
val profileServiceListener = object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
when (profile) {
BluetoothProfile.A2DP -> {
a2dpProxy = proxy as BluetoothA2dp
}
BluetoothProfile.HEADSET -> {
headsetProxy = proxy as BluetoothHeadset
}
}
}
override fun onServiceDisconnected(profile: Int) {
// 处理服务断开
}
}
adapter.getProfileProxy(context, profileServiceListener, BluetoothProfile.A2DP)
adapter.getProfileProxy(context, profileServiceListener, BluetoothProfile.HEADSET)
}
在实现双音箱同步播放时,我们发现Android 13新增了LE Audio支持,但需要硬件平台配合。RK3588目前需要通过软件方案实现音频同步,延迟控制在50ms以内才能获得较好的用户体验。
6. 兼容性处理与测试要点
6.1 多版本系统适配
针对不同Android版本需要采用不同的API策略:
kotlin复制fun checkBluetoothFeature(): Boolean {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 -> {
bluetoothAdapter?.bluetoothLeScanner != null
}
else -> false
}
}
6.2 自动化测试方案
建议实现以下测试用例:
- 权限被拒绝时的降级处理
- 蓝牙开关状态变化时的恢复能力
- 远距离连接稳定性测试(10米以上)
- 多设备快速切换压力测试
- 低电量模式下的功能验证
我们在CI pipeline中集成了物理设备测试环节,使用Robotium框架自动执行200+蓝牙相关用例,极大提升了版本发布质量。
