Шаг 264.
Основы Kotlin.
Знакомство с сопрограммами. Определение сопрограммы с помощью async()

    На этом шаге мы рассмотрим обращение к службе.

    Один из способов создать сопрограмму - использовать функцию async() из библиотеки поддержки сопрограмм. Функция async() принимает один аргумент: лямбду, которая определяет операции для выполнения в фоновом режиме.

    Функцию fetchCharacterData() переименуйте в fetchCharacterDataAsync() и перенесите вызов блокирующей функции readText() в лямбду, после чего передайте ее в функцию async(). Также замените тип возвращаемого значения на Deferred<CharacterGenerator.CharacterData>, тип результата функции async().

.   .   .   .
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import java.io.Serializable
import java.net.URL
.   .   .   .
fun fetchCharacterDataAsync(): Deferred<CharacterGenerator.CharacterData> {
    return GlobalScope.async {
        val apiData = URL(CHARACTER_DATA_API).readText()
        CharacterGenerator.fromApiData(apiData)
    }
}


Рис.1. Применение к fetchCharacterDataAsync() async() (CharacterGenerator.kt)

    Теперь вместо CharacterData функция fetchCharacterDataAsync() возвращает Deferred<CharacterGenerator.CharacterData>. Deferred - это как обещание на будущее: никакие данные не возвращаются, пока не выполнится запрос.

    Вернемся в MainActivity.kt и добавим код, преобразующий отложенные результаты веб-службы в CharacterData и отображающий их (разберем этот код после того, как вы его введете).

.   .   .   .
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
.   .   .   .
class MainActivity : AppCompatActivity() {
    .   .   .   .
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // setContentView(R.layout.activity_main)

        val binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        characterData = savedInstanceState?.characterData ?:
                CharacterGenerator.generate()

        binding.generateButton.setOnClickListener {
//            characterData = CharacterGenerator.//generate()
//                    fromApiData("halfling,Lars Kizzy,14,13,8")

            GlobalScope.launch()  {
                characterData = fetchCharacterDataAsync().await()
            }
            displayCharacterData(binding)
        }

        displayCharacterData(binding)

    }

    .   .   .   .
}


Рис.2. Ожидание API результатов (MainActivity.kt)

    Запустите ваше новое улучшенное приложение и нажмите несколько раз кнопку GENERATE. В этот раз данные, которые вы видите, получены из веб-службы. Давайте рассмотрим подробнее, что происходит.

    Прежде всего, вызовом функции launch() вы создали новую сопрограмму. Функция launch() запускает новую сопрограмму немедленно.

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

    Как мы сказали выше, сетевые взаимодействия запрещены в главном потоке. Аргумент по умолчанию, определяющий контекст сопрограммы, - это CommonPool, пул потоков фонового режима, доступных для выполнения сопрограмм. Это аргумент, который был использован по умолчанию для функции async() в fetchCharacterDataAsync(), поэтому при вызове await() запрос к веб-службе выполнится с использованием потока из пула, а не в главном потоке Android.


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

    Мы решили воспользоваться службой http://numbersapi.com/<число>, посзволяющей получить краткую характеристику заданного числа (на английском языке). Например, следующей вызов http://numbersapi.com/60 вернет характеристику числа 60:

60 is the total number of cards in the game Racko.

    Внесем следующие изменения.

    Изменим константу с адресом API, не указывая интересующее наас число. Его мы будем выбирать случайным образом:

private const val CHARACTER_DATA_API = "http//numbersapi.com/"

    Функция fetchCharacterDataAsync() имеет следующий вид:

fun fetchCharacterDataAsync(): Deferred<CharacterGenerator.CharacterData> {
    return GlobalScope.async {
        val tmpData = URL(CHARACTER_DATA_API + (1..99).random()).readText().split(" ")
        val tmpName = tmpData[2] + " " + tmpData[3]
        val apiData = "${tmpData[4]},${tmpData},${tmpData[0]},${tmpData[2].length},
                 ${tmpData[3].length}"
        CharacterGenerator.fromApiData(apiData)
    }
}

    Здесь tmpData представляет собой список слов, возвращаемый API-службой, характеризующий выбранное число из промежутка от 1 до 99. Переменная tmpName будет содержать "имя", собранное из двух слов списка. Значение переменной apiData формируется из значений и длин слов списка.


Файл с проектом можно взять здесь.

    На следующем шаге мы рассмотрим launch() против async()/await().




Предыдущий шаг Содержание Следующий шаг