跨类不同协程作用域下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
相关产品推荐
相关产品推荐

