На этом шаге мы рассмотрим обращение к службе.
Один из способов создать сопрограмму - использовать функцию 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().