Как заменить AsyncTask сопрограммами Kotlin

Вы все еще используете AsyncTask в своих приложениях для Android? Тебе, наверное, больше не следует быть. Вот как заменить их сопрограммами Kotlin.

В течение долгого времени в Android, если вам нужно было сделать что-то асинхронно при создании приложения, вы, вероятно, использовали AsyncTask. AsyncTask — это API в платформе Android, который позволяет легко (почти) запускать операции в фоновом режиме и возвращать значения по завершении. И это имеет смысл. В отличие от сопрограмм Kotlin, AsyncTask существует уже некоторое время и уже встроен.

Однако и философия проектирования, и реализация AsyncTask с годами несколько устарели. По этой причине у Google есть устарел API AsyncTask. Вы все равно можете использовать его, если хотите, но Google не рекомендует этого делать. К счастью, существует целая куча альтернатив AsyncTask, включая особенность языка Kotlin — сопрограммы.

API сопрограмм Kotlin — это невероятно мощный фреймворк, который позволяет вам делать множество вещей. Эта статья лишь поверхностно коснется того, что возможно. Мы рассмотрим основы, необходимые для перехода от AsyncTask к сопрограммам.

Добавление поддержки сопрограмм

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

Добавление поддержки Kotlin

Если у вас уже реализован Kotlin, перейдите к следующему разделу. В противном случае вам придется добавить поддержку Kotlin в свой проект. Для получения более подробной информации ознакомьтесь с моим руководством по добавлению Kotlin в существующий проект.

Добавление библиотек сопрограмм

На уровне вашего модуля build.gradle, включите следующие зависимости.

dependencies {
...
implementation 'org.jetbrains.kotlinx: kotlinx-coroutines-core: 1.5.0'
implementation 'org.jetbrains.kotlinx: kotlinx-coroutines-android: 1.5.0'
}

Синхронизируйте свой проект, и сопрограммы Kotlin теперь будут доступны для использования.

Использование сопрограмм

Реализация CoroutineScope

Чтобы использовать сопрограммы, вам понадобится экземпляр CoroutineScope. Самый простой способ сделать это — просто реализовать его в содержащем классе.

Например, чтобы реализовать CoroutineScope в действии:

classSomeActivity : AppCompatActivity, CoroutineScope by MainScope() {
...

override fun onDestroy(){
super.onDestroy()

cancel()
}
}

Это заставит SomeActivity реализовать интерфейс CoroutineScope посредством класса MainScope. MainScope будет обрабатывать всю логику реализации CoroutineScope, позволяя вам использовать методы CoroutineScope. Вызов cancel() в onDestroy() гарантирует, что асинхронная логика не будет продолжать работать после завершения действия.

Замена AsyncTask сопрограммами

Предположим, у вас есть AsyncTask внутри Activity, который выполняет длительную операцию в фоновом режиме и в конечном итоге возвращает строку. Что-то вроде следующего.

private inner classSomeTask : AsyncTask() {
override fun doInBackground(vararg params: Void): String {
try {
//Pretend this is an actual operation that takes 10 seconds and not just sleeping.
Thread.sleep(10000);
} catch (e: InterruptedException) {}

return"SomeString";
}

override fun onPostExecute(result: String) {
val someTextView = findViewById(R.id.some_text_view)
someTextView.text = result
}
}

Заменить это сопрограммой легко. Просто используйте async() метод. Котлин async() выполняется в любом потоке, в котором он был запущен, но делает это асинхронно. Это означает, что вы можете обновлять представления и тому подобное, не беспокоясь об использовании правильного потока.

classSomeActivity : AppCompatActivity(), CoroutineScope by MainScope() {
...

private fun doOperation(){
async {
//Inside coroutine scopes (like inside async here), delay is used instead of Thread.sleep.
delay(10000)

val someTextView = findViewById(R.id.some_text_view)
someTextView.text = "SomeString"
}
}
}

Как видите, использование сопрограмм может быть намного проще, чем использование AsyncTask. Вам не нужно просто позвонить async() и пусть оно делает свое дело. Вы можете сохранить ссылку на него и даже дождаться его завершения.

val asyncJob = async {
//Some operation
}
//Pause here until the async block is finished.
asyncJob.await()

//This won't run until asyncJob finishes, but other operations started before the job, or started from another method, can still run.
doSomethingElse()

Возврат значений с помощью async

Вы даже можете вернуть значение из async() если ты хочешь. Итак, исходный пример мог бы выглядеть примерно так.

classSomeActivity : AppCompatActivity(), CoroutineScope by MainScope() {
...
private fun doOperation(){
val asyncJob = async {
//Inside coroutine scopes (like inside async here), delay is used instead of Thread.sleep.
delay(10000)

//Whatever the type is of the last line is what async() eventually returns.
"SomeString"
}

val result = asyncJob.await()

val someTextView = findViewById(R.id.some_text_view)
someTextView.text = result
}
}

Использование withContext

Для удобства Котлин предоставляет withContext(). Это встраивает весь await() вещь и просто возвращает вам значение.

classSomeActivity : AppCompatActivity(), CoroutineScope by MainScope() {
...
private fun doOperation(){
//Run asynchronously on the main Thread.
val result = withContext(Dispatchers.Main) {
delay(10000)

"SomeResult"
}

val someTextView = findViewById(R.id.some_text_view)
someTextView.text = result
}
}

Заключение

Приведенные выше примеры — это всего лишь базовое использование сопрограмм Kotlin для начала. Вам не нужно ограничивать сопрограммы действиями или даже чем-то еще с правильным жизненным циклом. Вы можете запустить их практически где угодно. Существуют также более сложные операции, такие как выбор потока, который должен выполнять асинхронную логику. Это руководство в основном предназначено для того, чтобы показать, как заменить простую AsyncTask простой сопрограммой.

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