Пора: здесь сопрограммы
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, прочтите следующие статьи:
Спасибо, что прочитали мою статью.