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

跨类不同协程作用域下SharedFlow发射的数据无法被UseCase收集的问题

跨类不同协程作用域下SharedFlow发射的数据无法被UseCase收集的问题

看起来你碰到了SharedFlow跨组件收集不到数据的坑,我来帮你一步步排查和解决这个问题:

1. 最可能的元凶:Hilt注入的实例不唯一

你用Hilt注入BluetoothDeviceTracker,但如果没给这个类设置正确的作用域,很可能ViewModel里拿到的实例和UseCase里的不是同一个!比如ViewModel用的是实例A,发射数据到A的SharedFlow;而UseCase收集的是实例B的SharedFlow,自然收不到任何数据。

解决办法很简单,给你的BluetoothDeviceTrackerImpl配置单例作用域,确保整个应用里只有一个实例:

@Module
@InstallIn(SingletonComponent::class)
object BluetoothModule {
    @Provides
    fun provideBluetoothAdapter(): BluetoothAdapter? {
        val bluetoothManager = ContextCompat.getSystemService(
            ApplicationProvider.getApplicationContext(),
            BluetoothManager::class.java
        )
        return bluetoothManager?.adapter
    }

    @Provides
    fun provideBluetoothDeviceTracker(
        @ApplicationScope appScope: CoroutineScope,
        bluetoothAdapter: BluetoothAdapter?,
        scannedDeviceRepository: ScannedDeviceRepository
    ): BluetoothDeviceTracker = BluetoothDeviceTrackerImpl(appScope, bluetoothAdapter, scannedDeviceRepository)
}

2. 检查收集协程的活跃性

你的UseCase的invoke是挂起函数,调用它的时候必须放在活跃的协程作用域里。比如在ViewModel中,你需要用viewModelScope.launch来启动收集逻辑,而不是直接调用挂起函数:

@HiltViewModel
class ScanViewModel @Inject constructor(
    private val scanAndInsertUseCase: ScanAndInsertUseCase
) : ViewModel() {
    init {
        // 用viewModelScope启动收集协程,确保作用域活跃
        viewModelScope.launch {
            scanAndInsertUseCase()
        }
    }
}

如果直接调用scanAndInsertUseCase()而不包裹在协程里,会导致收集逻辑被挂起,无法响应SharedFlow的发射事件。

3. 验证SharedFlow的配置和实例一致性

  • 你当前的MutableSharedFlow配置(replay = 1, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)是合理的,能确保新的收集者订阅时拿到最近一次的扫描结果。
  • 你可以在BluetoothDeviceTrackerImpl的构造函数、ViewModel、UseCase中分别打印实例的hashCode,确认三者拿到的是同一个实例:
    // 在BluetoothDeviceTrackerImpl的构造函数里加日志
    init {
        Log.d("BluetoothTracker", "Instance created: ${this.hashCode()}")
    }
    
    // 在ViewModel里打印
    Log.d("ViewModel", "Bluetooth repo hash: ${bluetoothRepository.hashCode()}")
    
    // 在UseCase里打印
    Log.d("UseCase", "Bluetooth repo hash: ${bluetoothRepository.hashCode()}")
    
    如果三个hashCode不一样,就回到第一点,确认Hilt的单例配置是否正确。

4. 小细节:确认扫描回调的正确性

虽然你说在Impl内部能收集到数据,说明扫描回调没问题,但还是可以再确认下scanCallback是不是正确把扫描结果存入了scannedDeviceCache:

private val scanCallback = object : ScanCallback() {
    @SuppressLint("MissingPermission")
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        super.onScanResult(callbackType, result)
        // 转换为你的BluetoothScanResult并存入缓存
        val bluetoothScanResult = BluetoothScanResult(...)
        scannedDeviceCache[result.device.address] = bluetoothScanResult
    }

    override fun onScanFailed(errorCode: Int) {
        super.onScanFailed(errorCode)
        Log.e("ScanError", "Scan failed with code: $errorCode")
    }
}

按照上面的步骤排查,大概率能解决你收集不到数据的问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 12:09:33