Make function block in Kotlin

2019-07-13 09:06发布

问题:

I appreciate that this may have already been answered but I'm unable to find a solution that works for me.

Tl;dr: How do make a function block?

I have the following BLE-related code written in Kotlin for Android API 28.

override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {

    for (gattService: BluetoothGattService in gatt!!.services) {

        for (gattChar: BluetoothGattCharacteristic in gattService.characteristics) {

                if (gattChar.uuid.toString().contains(ADC_SAMPLESET_0) && !subscribed_0) {

                    subscribed_0 = true

                    gatt.setCharacteristicNotification(gattChar, true)                   

                    val descriptor = gattChar.getDescriptor(
                            UUID.fromString(BleNamesResolver.CLIENT_CHARACTERISTIC_CONFIG)
                    )
                    descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                    gatt.writeDescriptor(descriptor)
                }

The if-statement above is repeated multiple times to facilitate subscription to multiple BLE characteristics. Unfortunately, the gatt.writeDescriptor() function runs asynchronously. I need to wait for it to return before calling gatt.writeDescriptor() for the next characteristic. How do I achieve this?

I've tried using runBlocking and GlobalScope.launch in kotlinx.coroutines.experimental.* but I'm not entirely sure that they're the right thing.

Thanks, Adam

回答1:

The onDescriptorWrite() method might be helpful. You should already be overriding it.

Try the following:

private var canContinue = false;

override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { //gatt shouldn't be null, so the null-safe ? isn't needed
    loopAsync(gatt);
}

override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
    canContinue = true; //allow the loop to continue once a descriptor is written
}

private fun loopAsync(gatt: BluetoothGatt) {
    async { //Run it async
        gatt.services.forEach { gattService -> //Kotlin has a handy Collections.forEach() extension function
            gattService.characteristics.forEach { gattChar -> //Same for this one
                if (gattChar.uuid.toString().contains(ADC_SAMPLESET_0) && !subscribed_0) {
                    subscribed_0 = true

                    gatt.setCharacteristicNotification(gattChar, true)

                    val descriptor = gattChar.getDescriptor(
                            UUID.fromString(BleNamesResolver.CLIENT_CHARACTERISTIC_CONFIG)
                    }
                    descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                    gatt.writeDescriptor(descriptor)
                    while(!canContinue); //wait until canContinue becomes true and then continue
                }
            }
        }
    }
}

This is a little hacky. There is probably a way to do this with recursion, but the nested for loops make that tricky.



回答2:

It's not really a question of Kotlin. BluetoothGatt is an Async API with callbacks (as is often true of Bluetooth, because of the nature of it), and you can't easily use language features to hide that aspect of it.

It's probably possible to write a facade on top of BluetoothGatt that is blocking, but doing a good job of it would be a fair amount of work, and I wouldn't really recommend it.