Сфера перепутана в сопрограммах - PullRequest
0 голосов
/ 26 ноября 2018

У меня есть сценарий использования, который я хочу использовать сопрограмму, но немного запутался, как его реализовать.

Модель представления, которая имеет область действия и привязана к жизненному циклу пользовательского интерфейса и вызывает API из хранилища:

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()
    }
}

В репозитории используется сопрограмма для построения сетевого вызова следующим образом:

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

Вариант использования - функция хранилища должна всегда выполняться полностью после вызова API из ViewModel, так какнам нужно захватить весь сетевой ответ от сервера.

Как я могу убедиться, что сопрограмма в хранилище всегда выполняется, но сопрограммы ViewModel будут отменены, чтобы избежать утечки памяти после очистки модели представления?

Ответы [ 2 ]

0 голосов
/ 07 декабря 2018

ViewModel переживает только изменения конфигурации и не переживает разрушение активности в целом.

Если вы хотите, чтобы ваша операция продолжалась после уничтожения активности, вы должны использовать компонент с жизненным циклом, превышающим жизненный цикл.т. е. Сервис .

Кроме того, если вы хотите, чтобы ваша операция «всегда» выполнялась, вам следует использовать сервис Foreground, который должен иметь неотлучаемое уведомление во время работы сервиса.

Запущенная служба может использовать API startForeground(int, Notification), чтобы перевести службу в состояние переднего плана, где система считает, что это то, что пользователь активно знает, и, следовательно, не является кандидатом на убийство при низком уровне.в памяти.

0 голосов
/ 07 декабря 2018

В соответствии с документацией GlobalScope я думаю, что мы можем полагаться на то, что сопрограмма, запущенная с использованием глобального CoroutineScope, всегда выполняется.Документация гласит:

Глобальная область используется для запуска сопрограмм верхнего уровня, которые работают в течение всего времени жизни приложения и не отменяются преждевременно.

Я реализовалнекоторый тестовый код, и когда job был отменен внутри UserViewModel, сопрограмма в хранилище продолжала выполняться.Вот код с моими комментариями:

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"
    }
}

Дополнительно мы можем уменьшить showUser() функцию:

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

, используя функцию расширения then:

fun <T> Deferred<T>.then(scope: CoroutineScope = GlobalScope, uiFun: (T) -> Unit) {
    scope.launch { uiFun(this@then.await()) }
}

Если вы разрабатываете для Android и хотите быть уверенными, что ваша операция ввода-вывода выполнена полностью даже после очистки ViewModel, используйте WorkManager .Он предназначен для асинхронных и отложенных задач, требующих гарантии того, что система будет выполнять их, даже если приложение завершит работу.

...