web-dev-qa-db-fra.com

Portée confondue dans les coroutines

J'ai un cas d'utilisation que je veux utiliser coroutine mais un peu confus comment le mettre en application.

Un ViewModel qui a une portée et une liaison au cycle de vie de l'interface utilisateur et appelle une API à partir du référentiel:

class UserViewModel(): CoroutineScope {

    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    fun showUser() { 
       launch {
          val user = repo.getUser() 
          livedata = user
       }
    }

    fun onClean() {
       job.cancel()
    }
}

Le référentiel utilise coroutine pour construire l'appel réseau comme ceci:

suspend fun getUser() = GlobalScope { ... }

Le cas d'utilisation est que la fonction de référentiel doit toujours être complètement exécutée une fois que l'API est appelée à partir de ViewModel, car nous devons capturer toute la réponse réseau du serveur.

Comment puis-je m'assurer que la coroutine du référentiel est toujours exécutée mais que les routines ViewModel seront annulées pour éviter une fuite de mémoire une fois le modèle de vue effacé?

7
iammini

D'après la documentation de la GlobalScope, je pense que nous pouvons nous fier à la coroutine, lancée à l'aide de la CoroutineScope globale, toujours exécutée. La documentation dit:

La portée globale est utilisée pour lancer des coroutines de niveau supérieur qui fonctionnent sur toute la durée de vie de l'application et ne sont pas annulées prématurément.

J'ai implémenté du code de test et, lorsque la job a été annulée à l'intérieur de la UserViewModel, la coroutine a continué à s'exécuter. Voici le code avec mes commentaires:

class UserViewModel(): CoroutineScope {
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    fun showUser() {
        launch {
            val repo = Repository()
            val userDeferred = repo.getUser()
            // if onClean() is called before the coroutine in Repository finishes,
            // this line will not be called, but coroutine in Repository will continue executing
            val result = userDeferred.await() // wait for result of I/O operation without blocking the main thread
        }
    }

    fun onClean() {
        job.cancel()
    }
}

class Repository {
    fun getUser() = GlobalScope.async {
        delay(4000)
        // this line is executed no matter whether the job in UserViewModel was canceled or not
        "User returned"
    }
}

De plus, nous pouvons réduire la fonction showUser():

fun showUser() = repo.getUser().then(this) {
    // `it` contains the result
    // here is the main thread, use `it` to update UI
}

en utilisant la fonction d'extension then:

fun <T> Deferred<T>.then(scope: CoroutineScope = GlobalScope, uiFun: (T) -> Unit) {
    scope.launch { uiFun([email protected]()) }
}

Si vous développez pour Android et souhaitez vous assurer que votre opération IO est complètement exécutée, même après le nettoyage du ViewModel, utilisez WorkManager . Il est destiné aux tâches asynchrones et différables qui nécessitent une garantie que le système les exécutera même si l'application se ferme.

3
Sergey

ViewModel survit seulement aux changements de configuration et ne survit pas à la destruction de l'activité en général.

Si vous souhaitez que votre activité continue au-delà de la destruction d'activité, vous devez utiliser un composant ayant un cycle de vie supérieur à celui de l'activité i.e Service .

De plus, si vous voulez vous assurer que votre opération est "toujours" exécutée, vous devez utiliser un service de premier plan qui nécessitera une notification irréalisable pendant l'exécution du service.

Un service démarré peut utiliser l'API startForeground(int, Notification) pour placer le service dans un état de premier plan, où le système considère que c'est quelque chose que l'utilisateur connaît activement et qu'il n'est donc pas un candidat à la suppression lorsqu'il manque de mémoire.

1
saiedmomen