Используйте сопрограммы Kotlin с компонентами, учитывающими жизненный цикл.

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

Компоненты с учетом жизненного цикла обеспечивают первоклассную поддержку сопрограмм для логических областей вашего приложения, а также уровень взаимодействия с LiveData . В этом разделе объясняется, как эффективно использовать сопрограммы с компонентами, учитывающими жизненный цикл.

Добавить зависимости KTX

Встроенные области сопрограмм, описанные в этом разделе, содержатся в расширениях KTX для каждого соответствующего компонента. Обязательно добавьте соответствующие зависимости при использовании этих областей.

  • Для ViewModelScope используйте androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0 или более позднюю версию.
  • Для LifecycleScope используйте androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 или более позднюю версию.
  • Для liveData используйте androidx.lifecycle:lifecycle-livedata-ktx:2.4.0 или более позднюю версию.

Области сопрограмм с учетом жизненного цикла

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

ViewModelScope

ViewModelScope определяется для каждой ViewModel в вашем приложении. Любая сопрограмма, запущенная в этой области, автоматически отменяется, если ViewModel очищается. Сопрограммы здесь полезны, когда у вас есть работа, которую необходимо выполнить, только если ViewModel активна. Например, если вы вычисляете некоторые данные для макета, вам следует ограничить работу ViewModel , чтобы в случае очистки ViewModel работа автоматически отменялась во избежание потребления ресурсов.

Вы можете получить доступ к CoroutineScope ViewModel через свойство viewModelScope ViewModel, как показано в следующем примере:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

Область жизненного цикла

LifecycleScope определяется для каждого объекта Lifecycle . Любая сопрограмма, запущенная в этой области, отменяется при уничтожении Lifecycle . Доступ к CoroutineScope Lifecycle можно получить либо через свойства lifecycle.coroutineScope или lifecycleOwner.lifecycleScope .

В приведенном ниже примере показано, как использовать lifecycleOwner.lifecycleScope для асинхронного создания предварительно вычисленного текста:

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            val params = TextViewCompat.getTextMetricsParams(textView)
            val precomputedText = withContext(Dispatchers.Default) {
                PrecomputedTextCompat.create(longTextContent, params)
            }
            TextViewCompat.setPrecomputedText(textView, precomputedText)
        }
    }
}

Перезапускаемые сопрограммы с учетом жизненного цикла

Несмотря на то, что lifecycleScope предоставляет правильный способ автоматической отмены длительных операций, когда Lifecycle DESTROYED , у вас могут быть другие случаи, когда вы хотите начать выполнение блока кода, когда Lifecycle находится в определенном состоянии, и отменить, когда он находится в определенном состоянии. находится в другом штате. Например, вы можете захотеть собрать поток, когда Lifecycle STARTED и отменить сбор, когда он STOPPED . Этот подход обрабатывает выбросы потока только тогда, когда пользовательский интерфейс виден на экране, что экономит ресурсы и потенциально позволяет избежать сбоев приложения.

Для этих случаев Lifecycle и LifecycleOwner предоставляют API приостановки repeatOnLifecycle , который делает именно это. В следующем примере содержится блок кода, который запускается каждый раз, когда соответствующий Lifecycle находится хотя бы в состоянии STARTED , и отменяется, когда Lifecycle STOPPED :

class MyFragment : Fragment() {

    val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Create a new coroutine in the lifecycleScope
        viewLifecycleOwner.lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // This happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                viewModel.someDataFlow.collect {
                    // Process item
                }
            }
        }
    }
}

Сбор данных о потоках с учетом жизненного цикла

Если вам нужно выполнить сбор данных с учетом жизненного цикла только в одном потоке, вы можете использовать метод Flow.flowWithLifecycle() чтобы упростить код:

viewLifecycleOwner.lifecycleScope.launch {
    exampleProvider.exampleFlow()
        .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
        .collect {
            // Process the value.
        }
}

Однако если вам необходимо выполнить сбор с учетом жизненного цикла для нескольких потоков параллельно, вам необходимо собирать каждый поток в разных сопрограммах. В этом случае более эффективно использовать repeatOnLifecycle() напрямую:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        // Because collect is a suspend function, if you want to
        // collect multiple flows in parallel, you need to do so in
        // different coroutines.
        launch {
            flow1.collect { /* Process the value. */ }
        }

        launch {
            flow2.collect { /* Process the value. */ }
        }
    }
}

Приостановить сопрограммы с учетом жизненного цикла

Несмотря на то, что CoroutineScope предоставляет правильный способ автоматической отмены длительных операций, у вас могут быть другие случаи, когда вы захотите приостановить выполнение блока кода, если Lifecycle не находится в определенном состоянии. Например, чтобы запустить FragmentTransaction , вам необходимо дождаться, пока Lifecycle не станет хотя бы STARTED . Для этих случаев Lifecycle предоставляет дополнительные методы: lifecycle.whenCreated , lifecycle.whenStarted и lifecycle.whenResumed . Любой запуск сопрограммы внутри этих блоков приостанавливается, если Lifecycle не находится хотя бы в минимально желаемом состоянии.

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

class MyFragment: Fragment {
    init { // Notice that we can safely launch in the constructor of the Fragment.
        lifecycleScope.launch {
            whenStarted {
                // The block inside will run only when Lifecycle is at least STARTED.
                // It will start executing when fragment is started and
                // can call other suspend methods.
                loadingView.visibility = View.VISIBLE
                val canAccess = withContext(Dispatchers.IO) {
                    checkUserAccess()
                }

                // When checkUserAccess returns, the next line is automatically
                // suspended if the Lifecycle is not *at least* STARTED.
                // We could safely run fragment transactions because we know the
                // code won't run unless the lifecycle is at least STARTED.
                loadingView.visibility = View.GONE
                if (canAccess == false) {
                    findNavController().popBackStack()
                } else {
                    showContent()
                }
            }

            // This line runs only after the whenStarted block above has completed.

        }
    }
}

Если Lifecycle уничтожается, когда сопрограмма активна с помощью одного when методов, сопрограмма автоматически отменяется. В приведенном ниже примере блок finally запускается, как только состояние Lifecycle становится DESTROYED :

class MyFragment: Fragment {
    init {
        lifecycleScope.launchWhenStarted {
            try {
                // Call some suspend functions.
            } finally {
                // This line might execute after Lifecycle is DESTROYED.
                if (lifecycle.state >= STARTED) {
                    // Here, since we've checked, it is safe to run any
                    // Fragment transactions.
                }
            }
        }
    }
}

Используйте сопрограммы с LiveData

При использовании LiveData вам может потребоваться асинхронное вычисление значений. Например, вы можете захотеть получить настройки пользователя и передать их в свой пользовательский интерфейс. В этих случаях вы можете использовать функцию построения liveData для вызова функции suspend , предоставляя результат в виде объекта LiveData .

В приведенном ниже примере loadUser() — это функция приостановки, объявленная в другом месте. Используйте функцию компоновщика liveData для асинхронного вызова loadUser() , а затем используйте emit() для выдачи результата:

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

Строительный блок liveData служит структурированным примитивом параллелизма между сопрограммами и LiveData . Блок кода начинает выполняться, когда LiveData становится активным, и автоматически отменяется по истечении настраиваемого таймаута, когда LiveData становится неактивным. Если он отменен до завершения, он перезапускается, если LiveData снова становится активным. Если предыдущий запуск завершился успешно, он не перезапускается. Обратите внимание, что он перезапускается только в случае автоматической отмены. Если блок отменяется по какой-либо другой причине (например, при возникновении CancellationException ), он не перезапускается.

Вы также можете выдать несколько значений из блока. Каждый вызов emit() приостанавливает выполнение блока до тех пор, пока значение LiveData не будет установлено в основном потоке.

val user: LiveData<Result> = liveData {
    emit(Result.loading())
    try {
        emit(Result.success(fetchUser()))
    } catch(ioException: Exception) {
        emit(Result.error(ioException))
    }
}

Вы также можете объединить liveData с Transformations , как показано в следующем примере:

class MyViewModel: ViewModel() {
    private val userId: LiveData<String> = MutableLiveData()
    val user = userId.switchMap { id ->
        liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
            emit(database.loadUserById(id))
        }
    }
}

Вы можете выдать несколько значений из LiveData , вызывая функцию emitSource() каждый раз, когда хотите выдать новое значение. Обратите внимание, что каждый вызов метода emit() или emitSource() удаляет ранее добавленный источник.

class UserDao: Dao {
    @Query("SELECT * FROM User WHERE id = :id")
    fun getUser(id: String): LiveData<User>
}

class MyRepository {
    fun getUser(id: String) = liveData<User> {
        val disposable = emitSource(
            userDao.getUser(id).map {
                Result.loading(it)
            }
        )
        try {
            val user = webservice.fetchUser(id)
            // Stop the previous emission to avoid dispatching the updated user
            // as `loading`.
            disposable.dispose()
            // Update the database.
            userDao.insert(user)
            // Re-establish the emission with success type.
            emitSource(
                userDao.getUser(id).map {
                    Result.success(it)
                }
            )
        } catch(exception: IOException) {
            // Any call to `emit` disposes the previous one automatically so we don't
            // need to dispose it here as we didn't get an updated value.
            emitSource(
                userDao.getUser(id).map {
                    Result.error(exception, it)
                }
            )
        }
    }
}

Дополнительную информацию о сопрограммах можно найти по следующим ссылкам:

Дополнительные ресурсы

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

Образцы

Блоги

{% дословно %} {% дословно %} {% дословно %} {% дословно %}