Necesito buscar algunas imágenes de la galería, procesarlas (redimensionarlas, comprimirlas ...) y guardarlas en una ruta determinada. Sin embargo, necesito poner las llamadas en cola porque los dispositivos más antiguos no podrán procesar varias imágenes al mismo tiempo.

Estoy usando Glide, este es el código usado para procesar una imagen:

fun processImage(context: Context, sourcePath: String, destinationPath: String, quality: Int, width: Int, height: Int, deleteOriginal: Boolean, callback: ((success: Boolean) -> Unit)) {
    val sourceFile = File(sourcePath)
    val destinationFile = File(destinationPath)

    GlideApp.with(context)
            .asBitmap()
            .load(sourceFile)
            .into(object : SimpleTarget<Bitmap>(width, height) {
                override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
                    try {
                        destinationFile.writeBytes(ImageUtilities.imageToByteArray(resource, quality, Bitmap.CompressFormat.JPEG, false))
                        if (deleteOriginal) {
                            val originalFile = File(sourcePath)
                            originalFile.delete()
                        }
                        callback.invoke(true)
                    } catch (ex: Exception) {
                        callback.invoke(false)
                    }
                }
            })
}

Ahora estoy poniendo en cola las llamadas manualmente llamando a processNextImage que se llama a sí mismo de forma recursiva hasta que se procesan todas las imágenes:

private fun processImages(sourceImagePaths: List<String>) {
        processNextImage(sourceImagePaths, 0)
}

private fun processNextImage(sourceImagePaths: List<String>, index: Int) {
    val imagePath = sourceImagePaths[index]
    val destination = FileUtilities.generateImagePath()
    processImage(this, imagePath, destination, 90, 1000, 1000, false) {
        processedImagePaths.add(destination)
        if (index + 1 < sourceImagePaths.count())
            processImage(sourceImagePaths, index + 1)
        else
            success()
    }
}

Sin embargo, no creo que esta sea la mejor manera de hacerlo e intenté buscar en las corutinas de Kotlin, pero todo lo que encontré fueron ejemplos cuando el código en cola ya se está bloqueando, lo que no se ajusta a mi caso porque Glide ya maneja el cambio de tamaño de forma asincrónica y devuelve el resultado en una devolución de llamada onResourceReady

¿Alguna idea de una forma limpia de hacer esto?

3
wise.potato 21 feb. 2018 a las 16:01

2 respuestas

La mejor respuesta

Como se describe en la documentación oficial, hay un patrón simple a seguir si desea convertir una API basada en devolución de llamada en una basada en funciones suspendibles. Parafrasearé esa descripción aquí.

Su herramienta clave es la función de la biblioteca estándar llamada suspendCoroutine(). Suponga que tiene la función someLongComputation con una devolución de llamada que recibe un objeto Result:

fun someLongComputation(params: Params, callback: (Result) -> Unit)

Puede convertirlo en una función de suspensión con el siguiente código sencillo:

suspend fun someLongComputation(params: Params): Result = 
    suspendCoroutine { cont ->
        someLongComputation(params) { cont.resume(it) }
} 

Observe cómo el tipo de objeto pasado a la devolución de llamada original se convirtió simplemente en el valor de retorno de la función suspendible.

Con esto, puede ver que la magia de las corrutinas sucede justo frente a usted: aunque se ve exactamente como una llamada de bloqueo, no lo es. La corrutina se suspenderá entre bastidores y se reanudará cuando el valor de retorno esté listo, y la forma en que se reanudará está totalmente bajo su control.

1
Marko Topolnik 21 feb. 2018 a las 14:42

Pude resolver el problema usando suspendCoroutine como se sugiere en el comentario de Marko, aquí está mi código:

private fun processImages(sourceImagePaths: List<String>) {
    async(UI) {
        sourceImagePaths.forEach { path ->
            processNextImage(path)?.let {
                processedImagePaths.add(it)
            }
        }

        if (processedImagePaths.isEmpty()) finishWithFailure() else finishWithSuccess()
    }
}

private suspend fun processNextImage(sourceImagePath: String): String? = suspendCoroutine { cont ->
    val destination = FileUtilities.generateImagePath()
    processImage(this, sourceImagePath, destination, 90, 1000, 1000, false) { success ->
        if (success)
            cont.resume(destination)
        else
            cont.resume(null)

    }
}

El método processImages itera sobre la lista de rutas y llama a processNextImage para cada ruta. Dado que processNextImage contiene un suspendCoroutine, bloqueará el hilo hasta que se llame a cont.resume, lo que garantiza que la siguiente imagen no se procesará antes de que se haga la actual.

0
wise.potato 21 feb. 2018 a las 14:41