Vorhandener 3-Funktions-Callback zu Kotlin-Coroutinen

Lesezeit: 2 Minuten

Benutzer-Avatar
Benjamin h

Ich habe eine allgemeine Frage mit einem konkreten Beispiel: Ich möchte Kotlin Coroutine Magic anstelle von Callback Hell in Android verwenden, wenn ich ein Bild mache.

manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
    override fun onOpened(openedCameraDevice: CameraDevice) {
        println("Camera onOpened")
        // even more callbacks with openedCameraDevice.createCaptureRequest()....
    }

    override fun onDisconnected(cameraDevice: CameraDevice) {
        println("Camera onDisconnected")
        cameraDevice.close()
    }
    ...

Wie würde ich das in etwas weniger Hässliches umwandeln? Ist es möglich, einen durchschnittlichen Rückruf mit drei oder mehr Funktionen zu nehmen und ihn in eine Promise-Kette umzuwandeln, indem der primäre Fluss als Promise-Result-Pfad festgelegt wird? Und wenn ja, sollte/muss ich Coroutinen verwenden, um es asynchron zu machen?

Ich würde etwas mit async und .await lieben, was dazu führen würde

manager.open(cameraId).await().createCaptureRequest()

Ich versuche es durch etwas wie das Folgende, aber ich glaube nicht, dass ich es benutze CompletableDeferred Rechts!

suspend fun CameraManager.open(cameraId:String): CameraDevice {
    val response = CompletableDeferred<CameraDevice>()
    this.openCamera(cameraId, object : CameraDevice.StateCallback() {
        override fun onOpened(cameraDevice: CameraDevice) {
            println("camera onOpened $cameraDevice")
            response.complete(cameraDevice)
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
            response.completeExceptionally(Exception("Camera onDisconnected $cameraDevice"))
            cameraDevice.close()
        }

        override fun onError(cameraDevice: CameraDevice, error: Int) {
            response.completeExceptionally(Exception("Camera onError $cameraDevice $error"))
            cameraDevice.close()
        }
    }, Handler())
    return response.await()
}

  • Das Verketten von Rückrufen funktioniert, wenn mehrere Rückrufe vorhanden sind der Reihe nach, die jeweils ein Ergebnis oder einen Fehler liefern. Hier sind zwei Rückrufe parallel zu, wie stellst du dir vor, zwei Rückrufe gleichzeitig zu verketten? Welchen nimmt Ihre Probe? Oh, primärer Fluss. Aber Sie müssen es immer noch onDisconnected schließen, wie verketten Sie es?

    – Eugen Pechanec

    1. Februar 2018 um 0:05 Uhr


In diesem speziellen Fall können Sie einen allgemeinen Ansatz verwenden, um eine Callback-basierte API in eine Aussetzungsfunktion über umzuwandeln suspendCoroutine Funktion:

suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
    suspendCoroutine { cont ->
        val callback = object : CameraDevice.StateCallback() {
            override fun onOpened(camera: CameraDevice) {
                cont.resume(camera)
            }

            override fun onDisconnected(camera: CameraDevice) {
                cont.resume(null)
            }

            override fun onError(camera: CameraDevice, error: Int) {
                // assuming that we don't care about the error in this example
                cont.resume(null) 
            }
        }
        openCamera(cameraId, callback, null)
    }

Jetzt können Sie in Ihrem Anwendungscode einfach tun manager.openCamera(cameraId) und erhalten Sie einen Verweis auf CameraDevice wenn es erfolgreich geöffnet wurde oder null wenn nicht.

  • Wie gehen Sie in diesem Fall mit der Coroutine-Absage um?

    – Bolein95

    18. Juni 2018 um 20:20 Uhr

  • @ Bolein95: durch die Verwendung suspendCancellableCoroutine stattdessen.

    – Gabor

    28. Juni 2019 um 23:38 Uhr

  • Sie können wählen, ob Sie Ihren Fehler mit Ausnahme darstellen und tun möchten cont.resumeWithException(CameraException(error)) oder stellen Sie Ihren Fehler mit einem speziellen Ergebnistyp dar und tun Sie es cont.resume(CameraErrorResult(error)).

    – Roman Elizarov

    28. Februar 2020 um 10:25 Uhr

  • das ist einfach perfekt! Ich <3 Coroutinen! :)

    – Willi Mentzel

    10. April 2020 um 14:41 Uhr

  • Beachten Sie, dass dies abstürzt, wenn der Callback mehr als einmal ausgelöst wird, z. B. in einigen Fällen beim Drehen des Geräts

    – A. Steenbergen

    18. Mai 2020 um 13:35 Uhr

Verwenden Sie suspendCancellableCoroutine anstelle von suspendCoroutine mit der richtigen Ausnahmebehandlung

suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
        suspendCancellableCoroutine { cont ->
             val callback = object : CameraDevice.StateCallback() {
                 override fun onOpened(camera: CameraDevice) {
                   cont.resume(camera)
               }

               override fun onDisconnected(camera: CameraDevice) {
                   cont.resume(null)
               }

               override fun onError(camera: CameraDevice, error: Int) {
                   // Resume the coroutine by throwing an exception or resume with null
                   cont.resumeWithException(/* Insert a custom exception */) 
               }
        }
        openCamera(cameraId, callback, null)
    }

Es ist vorzuziehen, immer suspendCancellableCoroutine auszuwählen, um den Abbruch des Coroutinenbereichs zu handhaben oder den Abbruch von der zugrunde liegenden API zu propagieren.
Quelle mit anderen großartigen Beispielen

Ich habe 2 Lösungen für diese Art von Sache verwendet.

1: Wickeln Sie die Schnittstelle in eine Erweiterung ein

CameraDevice.openCamera(cameraId: Integer, 
                onOpenedCallback: (CameraDevice) -> (), 
          onDisconnectedCallback: (CameraDevice) ->()) {

    manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
        override fun onOpened(openedCameraDevice: CameraDevice) {
            onOpenedCallback(openedCameraDevice)
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
            onDisconnectedCallback(cameraDevice)
        }
   })
}

2: Erstellen Sie eine einfache Containerklasse mit einer funktionaleren Schnittstelle:

class StateCallbackWrapper(val onOpened: (CameraDevice) -> (), val onClosed: (CameraDevice) ->()): CameraDevice.StateCallback() {
    override fun onOpened(openedCameraDevice: CameraDevice) {
        onOpened(openedCameraDevice)
    }

    override fun onDisconnected(cameraDevice: CameraDevice) {
        onClosed(cameraDevice)
    }
}

Persönlich würde ich mit so etwas beginnen und dann irgendwelche Threading-Unterschiede darauf aufbauen.

  • Ich bin mir nicht klar: Was bringt mir das? Es ruft die Rückrufe auf, also muss ich noch eine Art Rückruf bereitstellen, also wurde der einzelne Rückruf (der 2 Funktionen enthält) in das Aufrufen einer Methode und das Übergeben von 2 verschiedenen Funktionen aufgeteilt. Es sei denn, das würde es ermöglichen .await()?

    – Benjamin h

    31. Januar 2018 um 23:39 Uhr

  • An der Verwendungsstelle übergeben Sie Lambdas oder Funktionsreferenzen, anstatt ein ganzes Objekt erstellen zu müssen. Wahrscheinlich sind die Rückrufe bereits asynchron. Möchten Sie nur eine Klasse erstellen, die den gesamten Prozess verwaltet?

    – GetSwifty

    31. Januar 2018 um 23:57 Uhr

  • Die Callbacks sind asynchron, aber ich versuche, mich aus der Callback-Hölle heraus und in etwas zu schlängeln, wo ich Funktionen für den Standardpfad verketten kann. Wie manager.open(cameraId).capture().saveFile() Dazu muss ich den Aufruf von manager.open(cameraId) blockieren, wenn ein Wert gefüllt wird (was meiner Meinung nach mit CompletableDeferred sein sollte?), Um alles zusammenzufügen.

    – Benjamin h

    1. Februar 2018 um 0:53 Uhr

  • Klassisches Beispiel für Indirektion, bei der das Problem nur unter dem Deckmantel der Behebung verschoben wird. Wir alle tun das von Zeit zu Zeit. Zumindest ich.

    – DarkNeuron

    13. November 2020 um 11:13 Uhr


1013940cookie-checkVorhandener 3-Funktions-Callback zu Kotlin-Coroutinen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy