You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 15:37:03