Пора: здесь сопрограммы

1. Зачем нужны сопрограммы?

Допустим, вам нужно получить группу клиентов из списка с вашего сервера. Вы должны позвонить в службу, чтобы получить данные и отобразить их в RecyclerView.

Допустим, у вас есть функция fetchCustomers() , в которой вы получаете данные с сервера, как показано ниже.

fun fetchCustomers(){
     val array = api.fetchCustomersList()
     updateUI(array)
}

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

fun onDestroy(){
    subscription1.cancel()
    subscription2.cancel()
    subscription3.cancel()
    subscription4.cancel()
}

Сопрограммы помогут вам выбраться из этого ада обратных вызовов. Вы можете решить эту проблему, используя одноразовые предметы в RxJava, но это может усложнить ситуацию. RxJava - это не только головная боль, но и польза.

Так какое же решение? Вот и сопрограммы, простые, но исчерпывающие.

2. Основы сопрограмм

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

Давайте напишем выше fetchCustomers(), используя сопрограмму Kotlin.

suspend fun fetchCustomers(){
     val array = api.fetchCustomersList()
     updateUI(array)
}

Чтобы показать, что эта функция работает в сопрограмме, мы используем модификатор suspend. Он работает аналогично стилю обратного вызова, но использует меньше кода без подписок. Его легко реализовать и просто использовать. Ничего нового по стилю исполнения не нужно учить. Сопрограммы обеспечивают async последовательное выполнение.

Что он делает, так это то, что когда вы начинаете выполнять сопрограмму, он приостанавливает выполнение, а когда он получает ответ, он возобновляет выполнение с того места, где оно было приостановлено. Таким образом suspend и resume заменяют обратный вызов.

Прелесть сопрограмм в том, что они хорошо зарекомендовали себя во многих продвинутых библиотеках, таких как Retrofit, Room и т. Д. Прямо сейчас вы можете использовать сопрограммы в своем проекте Android и упростить все сетевые вызовы, как показано выше, с помощью Retrofit 2.6 и выше.

Вернуться к основам. Мы можем вызвать функцию suspend только из другой функции suspend. Означает ли это, что вам нужно написать каждую функцию в вашем проекте как suspend функцию? Нет, в этом нет необходимости.

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

Диспетчеры

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

По умолчанию: все, что слишком долго выполняется в основном потоке, должно выполняться в диспетчере по умолчанию, например выполнение DiffUtil.

Ввод-вывод: такие задачи, как запись файла или вызов API, должны выполняться диспетчером ввода-вывода, который блокирует пользовательский интерфейс.

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

Использование withContext - это основной безопасный вызов. Теперь вы можете писать async работу в любом месте Android, используя withContext, а с помощью Dispatchers вы можете контролировать, с каким потоком она должна работать.

3. Терминология

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

CoroutineScope: определяет область действия новых сопрограмм.

Job: фоновое задание, которое можно организовать в иерархии родитель-потомок. Отмена любого родителя приведет к немедленной отмене всех его дочерних элементов , а сбой или отмена любых дочерних заданий, кроме CancellationException , приведет к отмене своего родительского .

SupervisorJob: Должность супервизора - это просто работа, но ее дочерние элементы могут потерпеть неудачу независимо, не уничтожая родителя.

Suspend: Suspend - это модификатор, используемый в функциях, чтобы указать, что это функция сопрограммы.

Launch: Launch - это метод в CoroutineScope, который запускает сопрограмму без блокировки текущего потока и возвращает ссылку на сопрограмму как Job (который можно отменить, чтобы остановить выполнение этой сопрограммы).

Async: Async - это метод в CoroutineScope, который запускает сопрограмму и возвращает ее будущий результат как реализацию [Deferred].

4. Упрощение сложных задач с помощью сопрограмм

Поскольку сопрограммы намного проще реализовать и понять, многие из расширенных библиотек, таких как Retrofit, Room, WorkManager, уже поддерживают их.

Дооснащение

Retrofit - это известная библиотека для выполнения сетевых вызовов с использованием клиента OkHttp. Давайте посмотрим, как использовать Retrofit с сопрограммами, и сравним работу, которую мы можем сократить, если реализуем ее с другими библиотеками, такими как RxJava.

Все, что вам нужно сделать, это изменить функции службы репо на suspend и вызывать их из withContext.

Сначала измените функции вызова службы Retrofit annotate на suspend функции, как показано ниже.

Теперь вызовите его из ViewModel, чтобы получить данные с сервера. Здесь мы можем использовать два шаблона. Первый - пометить функцию в ViewModel как suspend ,, а другой - запустить сопрограмму с использованием withContext, как показано выше.

Я объясню, как использовать withContext , потому что пометить функцию с помощью suspend - это самый простой способ, но где-то мы должны начать использовать withContext , чтобы запустить сопрограмму с нормальная функция.

Вот как просто сделать вызов службы в Android с помощью сопрограмм. Теперь давайте сравним тот же сервис с RxJava и посмотрим, как далеко мы продвинулись с сопрограммами.

Количество строк, написанных с помощью RxJava, больше, но это еще не все. Вы должны знать больше, чтобы реализовать RxJava, например, что такое observeOn и SubscribeOn. Вы также должны знать о disposables: почему они используются, что происходит, когда мы забываем их утилизировать, и о сотне других вещей.

Но с сопрограммами вам нужно только знать, что такое Dispatchers и scope. Об остальном позаботимся.

Комната

Room - это библиотека Jetpack, способная выполнять сложные операции SQLite без стандартного кода. Он действует как абстрактный слой над SQLite и экономит время на написание всего стандартного кода.

Давайте посмотрим, как упростить использование SQL-запросов с помощью Room и RxJava с помощью сопрограмм.

Как и при использовании Retrofit, вам нужно пометить свои Dao функции как suspend функции и использовать их в viewModel, используя suspend функции или запустив область действия сопрограмм.

А теперь позвоним из viewModel

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

WorkManager

Вы также можете использовать сопрограммы в WorkManager.

5. Политика отмены бронирования

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

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

Во-первых, давайте посмотрим на жизненный цикл сопрограммы.

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

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

val viewModelJob = SupervisorJob()                       
val viewmodelCoroutineScope = CoroutineScope(Dispatchers.IO + viewModelJob)

Каждый раз, когда вы вызываете launch функцию на viewmodelCoroutineScope, она возвращает задание. Это как родительский и дочерний: здесь viewModelJob - это родительское задание, а задания, которые создаются при вызове launch, являются дочерними заданиями.

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

Здесь у нас есть три флага для проверки статуса сопрограммы:

  • isActive - будет истинным в активном и завершающем состояниях сопрограммы.
  • isCompleted - по умолчанию false. Значение меняется на true после завершения всех родительских и дочерних заданий.
  • isCancelled - по умолчанию false. Если возникает какое-либо исключение или вы отменяете сопрограмму, значение изменяется на true.

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

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

6. Эффективное использование сопрограмм

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

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

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

Мы знаем, как отменить ту или иную работу. Пора понять, в чем разница между Job и SupervisorJob .

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

val scope = CoroutineScope(parentJob)
val job1 = scope.launch{ ... }
val job2 = scope.launch{ ... }

Мы можем отменять задания по отдельности, когда они не требуются. Если parentJob является заданием супервизора, то только конкретный Job будет отменен. В противном случае все задания будут отменены.

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

scope.cancel()
or
scope.coroutineContext.cancelChildren()

7. Обработка ошибок и исключений

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

Совместное аннулирование

Отметьте isActive или ensureActive в своих заданиях, чтобы убедиться, что текущее задание не отменено в результате исключения или намеренно.

viewmodelCoroutineScope.launch {
     ensureActive()
         or
     if(isActive){ ... }
}

Если задание отменено, оно не будет соответствовать этим условиям.

Если вам неудобно использовать isActive в каждой работе , тогда вы можете заключить код в _76 _ / _ 77_, а в последнем блоке вы можете выполнять любую работу, которая вам нужна, например, очистить при отмене сопрограммы.

Отмененная CoroutineScope

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

Чтобы преодолеть это, мы можем использовать SupervisorJob для создания CoroutineScope, который не повлияет на родительское задание или область действия при отмене любого из его дочерних элементов.

Попробовать / поймать с сопрограммами

В отличие от RxJava, мы можем обрабатывать ошибки и исключения, окружая код блоком _83 _ / _ 84_. Вам не нужно отдельно реализовывать блок ошибок и успешный блок, как это делается в RxJava.

runCatching

Наряду с обычным _85 _ / _ 86_, Kotlin также предоставляет runCatching , который возвращает результат, содержащий информацию о выходе вместе со статусом выполнения, например isSucess. Если это неудачный случай, в результате есть атрибут exceptionOrNull, с помощью которого мы можем получить подробную информацию об исключении. На мой взгляд, он более гибкий и предоставляет более чистый интерфейс для работы с исключениями и ошибками. Посмотри.

viewmodelCoroutineScope.launch {
    val result = kotlin.runCatching {
        repository.getData(inputs)
    }
}

8. Полезные ссылки

Если вы хотите узнать больше о расширенной разработке Kotlin, прочтите следующие статьи:





Спасибо, что прочитали мою статью.