es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Cómo esperar adecuadamente a que un Servicio de Android se vincule a una Actividad.

Estoy desarrollando una aplicación en la cual varias actividades están vinculadas a un servicio una vez que son visibles para el usuario. Durante el inicio, cada actividad necesita:

  • Revisar alguna bandera de estado del servicio, basándose en la cual algunos elementos de la interfaz de usuario son configurados.
  • Revisar si un adaptador está habilitado, cuya referencia está dentro del servicio.
  • Ejecutar algunas funciones del servicio.

Ya que las aplicaciones no deben obtener actualizaciones en segundo plano, me vinculo al servicio en onStart() y lo desvinculo en onStop(). Es decir, tengo algo así:

override fun onStart() {
    super.onStart()
    Intent(context, MyService::class.java).also { intent ->
        bindService(intent, serviceCallback, Context.BIND_AUTO_CREATE)
    }
}

Ahora quiero realizar las acciones anteriormente mencionadas dentro de onResume.

override fun onResume() {
    super.onResume()
    //Revisar las banderas
    //Revisar el estado del adaptador
    //Ejecutar funciones del servicio
}

El problema es que la vinculación a un servicio es asíncrona y no tengo una referencia válida al vinculador del servicio dentro de onResume(). En consecuencia, la aplicación se detendrá con una excepción de puntero nulo.

Enfoque 1: Usando lateinit

Intenté resolver este problema usando la palabra clave lateinit. Es decir, defino la referencia al vinculador de la siguiente manera:

private lateinit var myBinder: MyService.LocalBinder

Problema: No puedo garantizar que el vinculador esté inicializado ya que es asíncrono. Por lo tanto, la aplicación fallará.

Enfoque 2: Esperando una devolución de llamada en un bucle while

En mi devolución de llamada de servicio, establezco una bandera de la siguiente manera:

val serviceCallback = object : ServiceConnection {

    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        myBinder = service as MyService.LocalBinder
        isServiceBounded = true
    }

    override fun onServiceDisconnected(arg0: ComponentName) {
        isServiceBounded = false
        myBinder = null
    }
}

Luego, dentro de onResume, bloqueo la actividad hasta que la bandera es verdadera:

override fun onResume() {
    super.onResume()
    while(!isServiceBounded){
        //bloquear y esperar
    }
}

Problema: Tampoco funciona. La aplicación dejará de responder y fallará.

Enfoque 3: Usando funciones suspendidas y corutinas de Kotlin

Una función suspendida no continuará a menos que haya recibido un valor de retorno. Por lo tanto, puedo usarlo para esperar un evento. Entonces probé algo como esto:

override fun onStart() {
    super.onStart()
    CoroutineScope(Dispatchers.Main).launch {
        bindServiceAndWait(this@Activityname)
    }
}

suspend fun bindServiceAndWait(context: Context): Boolean{
    Intent(context, MyService::class.java).also { intent ->
        bindService(intent, serviceCallback, Context.BIND_AUTO_CREATE)
    }
    return isServiceBounded // Esta es la bandera de la devolución de llamada.
}

Problema: Esta función suspendida no espera realmente la devolución de llamada. Simplemente devuelve el valor actual de isServiceBounded.

Encontré una solución similar aquí, pero no entiendo del todo esta solución ya que tiene una devolución de llamada de servicio global (ServiceConnection), así como una local dentro de la función suspendida. Además, no entiendo cómo podría desvincular en este ejemplo proporcionado.

¿Cuál es la forma correcta de hacer esto?

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Solución para vincular un servicio en una aplicación de Android utilizando Kotlin y coroutines

    Como experto en tecnología de la información especializado en Android, Kotlin, Servicios e Inicialización, se debe garantizar manejar la vinculación de Actividades con un servicio de forma asíncrona. El problema es que vincular un servicio es asíncrono y no se tendría una referencia válida del enlace de servicio dentro del método onResume(). En consecuencia, la aplicación se cerrará con una excepción de puntero nulo. Hay varios enfoques para resolver este problema.

    Enfoque 1: Usando lateinit

    Se puede usar la palabra clave “lateinit” para definir la referencia al enlace. Este enfoque no se recomienda ya que no se garantiza que el enlace esté inicializado, ya que es asíncrono, y la aplicación fallará.

    Enfoque 2: Esperar una devolución de llamada en un bucle while

    Se puede esperar que se ejecute la devolución de llamada de conexión de servicio bloqueando la actividad en un bucle while. Sin embargo, este enfoque no es una buena idea, ya que no funciona bien y la aplicación dejará de responder y se cerrará.

    Enfoque 3: Usando funciones suspendidas y coroutines de Kotlin

    La función suspendida puede esperar a que se finalice el evento antes de devolver un valor. Esto se puede hacer utilizando el “CoroutineScope”. Aquí se explica cómo vincular el servicio y esperar la devolución de llamada usando Kotlin y coroutines:

    override fun onStart() {
        super.onStart()
        CoroutineScope(Dispatchers.Main).launch {
            bindServiceAndWait(this@Activityname)
        }
    }
    
    suspend fun bindServiceAndWait(context: Context): Boolean {
        return suspendCancellableCoroutine { continuation ->
            val serviceConnection = object : ServiceConnection {
                override fun onServiceConnected(className: ComponentName?, service: IBinder?) {
                    val localBinder = service as MyService.LocalBinder
                    isServiceBounded = true
                    continuation.resume(true)
                }
                override fun onServiceDisconnected(className: ComponentName?) {
                    isServiceBounded = false
                    continuation.resume(false)
                }
            }
            val intent = Intent(context, MyService::class.java)
            context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
            continuation.invokeOnCancellation {
                context.unbindService(serviceConnection)
            }
        }
    }
    

    “bindServiceAndWait()” es una función suspendida, y se puede utilizar en “CoroutineScope” para vincular el servicio y esperar a que se establezca la conexión. Si la conexión al servicio tiene éxito, la función se reanudará con un valor verdadero. Si no tiene éxito, la función se reanudará con un valor falso.

    El bloque “invokeOnCancellation” desvincula el servicio si se cancela la coroutine. Esto asegura que el servicio se desvincula si la actividad se destruye o se cancela la coroutine.

    Conclusión

    El enfoque recomendado para vincular un servicio en una aplicación de Android es utilizar coroutines de Kotlin y la función suspendida para esperar el evento. El enfoque proporciona una forma segura y confiable de manejar eventos asíncronos.

Comments are closed.