Hello, I would appreciate some help understanding how threads work here :)
I have a BLE scanner class. Its method scan()
runs everything with Dispatcher.IO
. I managed to confirm that its code is running NOT on the main thread. Within it I create a suspendCancellableCoroutine
and within it I create a ScanCallback
. I would assume the callback's method would run on the same thread that the callback was created. But it doesn't - it runs on the main thread and could potentially block the UI.
QUESTION: is there a way to make sure the ScanCallback'
s methods like onScanResult
and onScanFailed
run on the same thread that the rest of FakeScaner.scan()
method is running on?
Reason for that: I needed to stop the scanner in some situations, sleep for some seconds and restart it. I managed to do a workaround, but I would still like to figure out why this works as it works and how to change it if possible :)
class FakeScanner(
private val context: Context,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
private val bluetoothAdapter: BluetoothAdapter by lazy {
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
requireNotNull(bluetoothManager.adapter)
}
suspend fun scan(timeout: Duration?): Map<MyDevice, SignalStrength> =
withContext(ioDispatcher) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.d(TAG, "This is running MAIN thread")
} else {
Log.d(TAG, "This is running NOT on MAIN thread")
}
/**
* Some code here
*/
suspendCancellableCoroutine { cont ->
val bleDevices = mutableMapOf<BluetoothDevice, SignalStrength>()
val scanCallback = object : ScanCallback() {
@Synchronized
override fun onScanResult(callbackType: Int, result: ScanResult?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.d(TAG, "This is running MAIN thread")
} else {
Log.d(TAG, "This is running NOT on MAIN thread")
}
try {
result?.device?.let {
bleDevices.put(it, SignalStrength(result.rssi))
}
} catch (t: Throwable) {
if (cont.isActive) { cont.resumeWithException(t) }
}
}
@Synchronized
override fun onScanFailed(errorCode: Int) {
try {
// Some logic about scan attempt limits..
when (errorCode) {
SCAN_FAILED_ALREADY_STARTED -> { /* Restart scanner after delay */ }
else -> { /* Logic about other error codes */ }
}
} catch (t: Throwable) {
if (cont.isActive) { cont.resumeWithException(t) }
}
}
}
try {
// Start scanning
} catch (t: Throwable) {
if (cont.isActive) {
cont.resumeWithException(t)
}
}
cont.invokeOnCancellation {
try {
// Stop scanner here
} catch (t: SecurityException) {
// Nothing to do here
}
}
}
}
}
Nope, unfortunately not. It is hardcoded here that it will run on the main thread: https://android.googlesource.com/platform/packages/modules/Bluetooth/+/aa58f747f37494c619a9afae1622f56001bc07fb/framework/java/android/bluetooth/le/BluetoothLeScanner.java#543.
Previously in Android 4.4, the scan results were delivered directly on the Binder thread that internally receives the results from the Bluetooth process. I don't really know why they changed it this way.
If you want to handle the results on a different thread, you need to post the results yourself manually to that thread.