Android BLE广告问题:蓝牙切换后回调重复触发及重启异常
Android BLE广告蓝牙切换后重启及回调重复触发问题
问题描述
- 蓝牙关闭时,广告正常停止并触发
onAdvertisingSetStopped回调,但蓝牙重新开启后广告无法自动重启。 - 手动调用
startAdvertising可以启动广告,但再次关闭蓝牙时,onAdvertisingSetStopped会被触发两次,原因是未调用stopAdvertising解绑旧回调。 - 蓝牙关闭时
BluetoothAdapter不可用,无法在stopAdvertising中调用stopAdvertisingSet;尝试调用现有currentAdvertisingSet的enableAdvertising(true, 0, 0)也无法解决问题。
相关代码片段
private var currentAdvertisingSet: AdvertisingSet? = null private lateinit var parameters: AdvertisingSetParameters // called from broadcastreceiver @RequiresPermission(Manifest.permission.BLUETOOTH_ADVERTISE) private fun restartAdvertising() { currentAdvertisingSet?.let { it.enableAdvertising(true,0,0) } } @RequiresPermission(Manifest.permission.BLUETOOTH_ADVERTISE) override fun onDestroy() { super.onDestroy() stopAdvertising() } @RequiresPermission(Manifest.permission.BLUETOOTH_ADVERTISE) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) startAdvertising() return START_NOT_STICKY } @RequiresPermission(Manifest.permission.BLUETOOTH_ADVERTISE) private fun startAdvertising(){ val advertiser = (this.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager) .adapter .bluetoothLeAdvertiser parameters = AdvertisingSetParameters.Builder() .setLegacyMode(false) .setInterval(AdvertisingSetParameters.INTERVAL_MEDIUM) .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_HIGH) .setPrimaryPhy(BluetoothDevice.PHY_LE_1M) .setSecondaryPhy(BluetoothDevice.PHY_LE_CODED) .build() val sendingData = AdvertiseData.Builder() .setIncludeDeviceName(false) .addServiceUuid(ParcelUuid(UUID.fromString(this.resources.getString(R.string.service_uuid)))) .addServiceData( ParcelUuid(UUID.fromString(this.resources.getString(R.string.service_uuid))), message.toByteArray() ) .build() advertiser.startAdvertisingSet( parameters, sendingData, null, null, null, advertiseCallback ) } private val advertiseCallback = object : AdvertisingSetCallback() { override fun onAdvertisingSetStarted( advertisingSet: AdvertisingSet?, txPower: Int, status: Int ) { super.onAdvertisingSetStarted(advertisingSet, txPower, status) when (status) { ADVERTISE_SUCCESS -> { currentAdvertisingSet = advertisingSet } else -> { } } } override fun onAdvertisingEnabled( advertisingSet: AdvertisingSet?, enable: Boolean, status: Int ) { super.onAdvertisingEnabled(advertisingSet, enable, status) Log.i( LOG_TAG, "onAdvertisingEnabled(): enable:$enable , status: $status" ) } override fun onAdvertisingSetStopped(advertisingSet: AdvertisingSet?) { super.onAdvertisingSetStopped(advertisingSet) Log.w( LOG_TAG, "onAdvertisingSetStopped()" ) } } @RequiresPermission(Manifest.permission.BLUETOOTH_ADVERTISE) private fun stopAdvertising() { try { val advertiser = (this.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager) .adapter .bluetoothLeAdvertiser advertiser.stopAdvertisingSet(advertiseCallback) } catch (e: Exception){ Log.e(LOG_TAG, "stopAdvertising(): ${e.message}") } }
解决方案
1. 管理回调实例,避免重复注册
每次启动广告前强制清理旧的回调与广告实例,且每次启动都创建新的回调对象,防止旧回调被重复触发:
private var currentAdvertisingSet: AdvertisingSet? = null private var currentAdvertiseCallback: AdvertisingSetCallback? = null // 新增变量管理当前回调 @RequiresPermission(Manifest.permission.BLUETOOTH_ADVERTISE) private fun startAdvertising(){ // 先停止旧广告,清理关联资源 stopAdvertising() val advertiser = (this.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager) .adapter .bluetoothLeAdvertiser parameters = AdvertisingSetParameters.Builder() .setLegacyMode(false) .setInterval(AdvertisingSetParameters.INTERVAL_MEDIUM) .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_HIGH) .setPrimaryPhy(BluetoothDevice.PHY_LE_1M) .setSecondaryPhy(BluetoothDevice.PHY_LE_CODED) .build() val sendingData = AdvertiseData.Builder() .setIncludeDeviceName(false) .addServiceUuid(ParcelUuid(UUID.fromString(this.resources.getString(R.string.service_uuid)))) .addServiceData( ParcelUuid(UUID.fromString(this.resources.getString(R.string.service_uuid))), message.toByteArray() ) .build() // 创建新的回调实例 currentAdvertiseCallback = object : AdvertisingSetCallback() { override fun onAdvertisingSetStarted( advertisingSet: AdvertisingSet?, txPower: Int, status: Int ) { super.onAdvertisingSetStarted(advertisingSet, txPower, status) when (status) { ADVERTISE_SUCCESS -> { currentAdvertisingSet = advertisingSet } else -> { } } } override fun onAdvertisingEnabled( advertisingSet: AdvertisingSet?, enable: Boolean, status: Int ) { super.onAdvertisingEnabled(advertisingSet, enable, status) Log.i( LOG_TAG, "onAdvertisingEnabled(): enable:$enable , status: $status" ) } override fun onAdvertisingSetStopped(advertisingSet: AdvertisingSet?) { super.onAdvertisingSetStopped(advertisingSet) Log.w( LOG_TAG, "onAdvertisingSetStopped()" ) // 回调触发后清空本地实例 currentAdvertisingSet = null currentAdvertiseCallback = null } } advertiser.startAdvertisingSet( parameters, sendingData, null, null, null, currentAdvertiseCallback!! ) } @RequiresPermission(Manifest.permission.BLUETOOTH_ADVERTISE) private fun stopAdvertising() { try { currentAdvertiseCallback?.let { callback -> val advertiser = (this.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager) .adapter .bluetoothLeAdvertiser advertiser.stopAdvertisingSet(callback) } } catch (e: Exception){ Log.e(LOG_TAG, "stopAdvertising(): ${e.message}") } finally { // 无论是否成功,都清空本地实例 currentAdvertisingSet = null currentAdvertiseCallback = null } }
2. 优化蓝牙状态监听逻辑
蓝牙关闭后旧的AdvertisingSet实例已失效,无需复用,直接在蓝牙开启时启动新广告:
// 蓝牙状态广播接收器的处理方法 fun onBluetoothStateChanged(state: Int) { when(state) { BluetoothAdapter.STATE_ON -> { startAdvertising() // 直接启动新广告 } BluetoothAdapter.STATE_OFF -> { // 清空本地实例,避免后续误操作 currentAdvertisingSet = null currentAdvertiseCallback = null } } }
3. 完善服务生命周期内的广告启动逻辑
在服务启动时先检查蓝牙状态,未开启则等待广播触发,避免无效启动:
@RequiresPermission(Manifest.permission.BLUETOOTH_ADVERTISE) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) val bluetoothAdapter = (getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter if (bluetoothAdapter.isEnabled) { startAdvertising() } return START_NOT_STICKY }
关键原理
- 蓝牙关闭后,原
AdvertisingSet实例与系统蓝牙服务的绑定已断开,无法通过enableAdvertising重新激活,必须创建新实例。 - 复用同一个回调会导致系统在多个广告实例中注册该回调,从而触发多次
onAdvertisingSetStopped,每次启动创建新回调并管理其生命周期可避免该问题。
内容的提问来源于stack exchange,提问作者Tak
相关产品推荐
相关产品推荐

