<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2023-01-08T13:02:50+00:00</updated><id>/feed.xml</id><title type="html">Записки мобильного разраба</title><subtitle>Пишу разные штуки по разработке iOS/Android, которые реально пригодились.</subtitle><entry><title type="html">Почему исключения в Kotlin Coroutines это сложно и как с этим жить?</title><link href="/android/kotlin/coroutines/error/exception/2022/09/19/why-exception-handling-with-kotlin-coroutines-is-so-hard-and-how-to-successfully-master-it.html" rel="alternate" type="text/html" title="Почему исключения в Kotlin Coroutines это сложно и как с этим жить?" /><published>2022-09-19T12:02:00+00:00</published><updated>2022-09-19T12:02:00+00:00</updated><id>/android/kotlin/coroutines/error/exception/2022/09/19/why-exception-handling-with-kotlin-coroutines-is-so-hard-and-how-to-successfully-master-it</id><content type="html" xml:base="/android/kotlin/coroutines/error/exception/2022/09/19/why-exception-handling-with-kotlin-coroutines-is-so-hard-and-how-to-successfully-master-it.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://www.lukaslechner.com/why-exception-handling-with-kotlin-coroutines-is-so-hard-and-how-to-successfully-master-it/&quot; target=&quot;_blank&quot;&gt;Why exception handling with Kotlin Coroutines is so hard and how to successfully master it!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/why-exception-handling-with/1.webp&quot; alt=&quot;Header&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Обработка исключений, вероятно одна из самых сложных частей, когда вы изучаете корутины в Kotlin. В этой статье, я расскажу о причинах такой сложности и объясню некоторые ключевые моменты для хорошего понимания темы. После этого вы сможете реализовать правильную инфраструктуру для обработки ошибок в своем собственном приложении.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Информация: вы можете посмотреть более полное видео об обработке исключений &lt;a href=&quot;https://www.youtube.com/watch?v=Pgek3_3vPU8&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;обработка-исключений-в-чистом-kotlin&quot;&gt;Обработка исключений в “чистом” Kotlin&lt;/h2&gt;

&lt;p&gt;В Kotlin без корутин обрабатывать исключения достаточно просто. Для этого используются блоки обработки &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// some code&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in 'some code'&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Handle java.lang.RuntimeException: RuntimeException in 'some code'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В обычной функции исключение пробрасывается (&lt;em&gt;re-thrown&lt;/em&gt;). Это значит, что мы можем перехватить исключения блоком &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; для их обработки в месте вызова такой функции:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;functionThatThrows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;functionThatThrows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// some code&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in regular function&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Handle java.lang.RuntimeException: RuntimeException in regular function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;try-catch-в-корутинах&quot;&gt;try-catch в корутинах&lt;/h2&gt;

&lt;p&gt;Теперь давайте посмотрим, как использовать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; в котлиновских корутинах. Внутри корутины (которая стартует при помощи функции &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; в примере ниже) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; работает штатно, исключение перехватывается:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in coroutine&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Handle java.lang.RuntimeException: RuntimeException in coroutine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Но если запустить другую корутину внутри блока &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;…&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in nested coroutine&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Exception in thread &quot;main&quot; java.lang.RuntimeException: RuntimeException in nested coroutine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… видно, что исключение больше не обрабатывается и приложение падает. Это весьма неожиданно и сбивает с толку. Если основываться на наших знаниях и опыте работы с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;, мы ожидаем, что каждое исключение обернутое блоком &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; перехватывается и передается в ветку &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;catch&lt;/code&gt;. Почему здесь это не срабатывает?&lt;/p&gt;

&lt;p&gt;Хорошо, если корутина не перехватывает исключение самостоятельно через блок &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;, она “завершается по необработанному исключению” или, простыми словами, она падает. В примере выше, внутренняя корутина запущенная через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; не перехватывает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RuntimeException&lt;/code&gt; сама и поэтому падает.&lt;/p&gt;

&lt;p&gt;Как мы увидели в начале, не перехваченное исключение “пробрасывается” выше в обычной функции. В случае корутины это не работает. Иначае, мы бы могли обработать исключение во внешнем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; блоке и приложение с примером выше не падало.&lt;/p&gt;

&lt;p&gt;Тогда, что происходит с не перехваченным исключением в корутине? Вероятно вы знаете, одна из самых крутых штук в корутинах это &lt;em&gt;Структурная Конкурентность&lt;/em&gt; (&lt;a href=&quot;https://kotlinlang.org/docs/reference/coroutines/basics.html#structured-concurrency&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;Structured Concurrency&lt;/em&gt;&lt;/a&gt;). Для работы всех возможностей &lt;em&gt;Structured Concurrency&lt;/em&gt;, объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineScope&lt;/code&gt; и объекты &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; в корутинах и дочерние корутины образуют иерархию вида “родитель-ребенок”. Любое не перехваченное исключение, вместо того, чтобы быть проброшенным, распространяется вверх по иерарахии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; объектов. Такое распространение приводит к падению родительской &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; и ведет к отказу всех дочерних &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Для примера выше иерархия &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; выглядит так:
&lt;img src=&quot;/images/why-exception-handling-with/2.webp&quot; alt=&quot;coroutines hierarchy&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Исключение из дочерней корутины распространяется выше до объекта &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; корутины верхнего уровня (1) и затем дальше до объекта &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topLevelScope&lt;/code&gt; (2).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/why-exception-handling-with/3.webp&quot; alt=&quot;coroutines exceptions propagation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Исключения могут быть перехвачены через установку обработчика &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;. Если не установленного ни одного, вызывается обработчик не перехваченных исключений конкретного потока, который зависит от платформы и скорее всего напечатает исключения в стандартный вывод и затем завершит приложение.&lt;/p&gt;

&lt;p&gt;По-моему мнению, у нас фактически два разных механизма для обработки исключений – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandlers&lt;/code&gt;, это один из главных факторов, почему обработка исключений в корутинах такая сложная.&lt;/p&gt;

&lt;h2 id=&quot;ключевая-особенность-1&quot;&gt;Ключевая особенность #1&lt;/h2&gt;
&lt;p&gt;Если корутина не обрабатывает исключения сама блоком &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;, исключение не пробрасывается и таким образом не сможет быть обработана внешним &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;. Вместо этого, исключение “распространяется по иерархии корутин (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt;)” и может быть обработана специально установленным &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;. Если таковых нет, необработанное исключение попадает в обработчик не перехваченных исключений потока.&lt;/p&gt;

&lt;h2 id=&quot;обработчик-coroutineexceptionhandler&quot;&gt;Обработчик CoroutineExceptionHandler&lt;/h2&gt;

&lt;p&gt;Ладно, сейчас мы знаем, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; блок бесполезен если мы стартуем корутину с исключением в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try&lt;/code&gt; ветке. Давайте вместо этого установим &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;! Мы можем передать контекст в функцию-билдер корутин &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt;. Так как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ContextElement&lt;/code&gt;, его можно установить как единственный аргумент в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt;, при запуске нашей дочерней корутины:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;coroutineExceptionHandler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineExceptionHandler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coroutineContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception in CoroutineExceptionHandler&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutineExceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in nested coroutine&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Exception in thread &quot;DefaultDispatcher-worker-2&quot; java.lang.RuntimeException: RuntimeException in nested coroutine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
Хм, тем не менее, наше исключение не обрабатывается в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineExceptionHandler&lt;/code&gt; и приложение падает! Это потому что установка &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; в дочерние корутины не имеет никакого эффекта. Мы должны установить обработчик в &lt;em&gt;scope&lt;/em&gt; или в корутину верхнего уровня, таким образом:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coroutineExceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;или что-то похожее на:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutineExceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И только тогда, наш обработчик ошибок сработает:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// ..&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Output: &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Handle java.lang.RuntimeException: RuntimeException in nested coroutine in CoroutineExceptionHandler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;ключевая-особенность-2&quot;&gt;Ключевая особенность #2&lt;/h2&gt;

&lt;p&gt;Чтобы &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; сработал, надо его устанавливать или в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineScope&lt;/code&gt; или в корутинах верхнего уровня.&lt;/p&gt;

&lt;h2 id=&quot;try-catch-vs-coroutineexceptionhandler&quot;&gt;try-catch VS CoroutineExceptionHandler&lt;/h2&gt;

&lt;p&gt;Как вы уже поняли, у нас два варианта для обработки исключений:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;оборачиваем код внутри корутины блоком &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;,&lt;/li&gt;
  &lt;li&gt;установка обработчика &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Какой вариант следует выбирать?&lt;/p&gt;

&lt;p&gt;У официальной документации &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;&lt;/a&gt;) есть хорошие ответы на это:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; это последнее средство для глобального перехвата всех исключений. Нельзя восстановится после обработки исключения в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;. Корутина к этому времени уже завершилась с соответствующим исключением (когда вызвался обработчик &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;). Как правило, такой обработчик используется для логгирования исключений, сообщений об ошибках, рестарах приложения.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Если вам надо обрабатывать исключение в отдельной части кода, рекомендуется использовать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; блок вокруг вашего кода внутри корутины. Таким образом, вы можете предотвратить завершение корутины через ошибку (теперь исключение обрабатывается), повторить операцию, и/или предпринять любые другие действия.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Здесь есть еще один аспект - если обрабатывать исключения напрямую в корутине с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;, то это ломает концепцию отмены корутин в &lt;em&gt;Structured Concurrency&lt;/em&gt;. К примеру, давайте представим, что мы запустили две корутины параллельно. Они обе как-то зависят друг от друга таким образом, что завершение одной не имеет смысла, если другая падает. При использовании &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; для обработки исключений в каждой корутине, сами исключения не будут распространяться выше к родителю и поэтому другая корутина не будет отменяться. Это тратит ресурсы впустую. В таких ситуациях необходимо использовать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;ключевая-особенность-3&quot;&gt;Ключевая особенность #3&lt;/h2&gt;

&lt;p&gt;Используйте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; если вы хотите как-то восстановиться (повтор или другие операции), до того как корутина закончится. Помните, что перехваченное исключение не распространяется выше по иерархии корутин и функционал отмены для &lt;em&gt;Structured Concurrency&lt;/em&gt; не работает в этом случае. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; применяйте для логики работающей после того, как корутина завершена.&lt;/p&gt;

&lt;h2 id=&quot;launch-vs-async&quot;&gt;launch{} vs async{}&lt;/h2&gt;

&lt;p&gt;До этого момента, мы использовали только билдер-функцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; для запуска новых корутин. Однако, обработка исключений немного отличается между корутинами запущенными через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;. Посмотрите на следующий пример:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SupervisorJob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in async coroutine&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// No output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В этом примере воообще ничего не выводится. Что здесь происходит с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RuntimeException&lt;/code&gt;? Исключение игнорируется? Нет. В корутинах запущенных через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; , необработанные исключения также немедленно распространяются вверх по иерархии корутин. Но в отличие от &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt;, исключения не обрабатываются установленным &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; и не передаются в обработчик потока для необработанных исключений.&lt;/p&gt;

&lt;p&gt;Функция &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; для запуска корутин возвращает экземпляр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt;, это простое представление корутины которая не возвращает значение. В случае если мы хотим чтобы корутина что-то возвращала, мы должны использовать функцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;, которая возвращает объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deferred&lt;/code&gt;, специальный подтип &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; с результатом. Если &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; корутина падает, то исключение оборачивается в возвращаемое значение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deferred&lt;/code&gt; и пробрасывается, когда мы вызываем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await&lt;/code&gt; у корутины для получения результата.&lt;/p&gt;

&lt;p&gt;Поэтому, мы можем обернуть &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.await()&lt;/code&gt; в блок &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;. Так как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.await()&lt;/code&gt; это &lt;em&gt;suspend&lt;/em&gt; функция, мы должны запустить новую корутину, чтобы можно было ее вызвать:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SupervisorJob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;deferredResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in async coroutine&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;deferredResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception in try/catch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output: &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Handle java.lang.RuntimeException: RuntimeException in async coroutine in try/catch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
Внимание: исключение оборачивается в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deferred&lt;/code&gt;, только для &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; корутин верхнего уровня. В противном случае оно немедленно распространяется вверх по иерархии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; и перехватывается или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; или передается в обработчик необработанных ошибок в потоке, это происходит даже без вызова метода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.await()&lt;/code&gt;, как в примере ниже:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;coroutineExceptionHandler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineExceptionHandler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coroutineContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception in CoroutineExceptionHandler&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SupervisorJob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coroutineExceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in async coroutine&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Handle java.lang.RuntimeException: RuntimeException in async coroutine in CoroutineExceptionHandler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;ключевая-особенность-4&quot;&gt;Ключевая особенность #4&lt;/h2&gt;
&lt;p&gt;Необработанные исключения для &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; мгновенно распростаняются по иерархии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt;. Однако, если верхнеуровневая корутина была запущена через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt;, то исключение обрабатывается &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; или передается в обработчик необработанных исключений в потоке. С другой стороны, при запуске верхнеуровневой корутины через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;, исключение оборачивается в возвращаемый объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deferred&lt;/code&gt; и пробрасывается, когда вызывается его метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.await()&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;особенности-обработки-исключений-в-coroutinescope&quot;&gt;Особенности обработки исключений в coroutineScope{}&lt;/h2&gt;

&lt;p&gt;Когда в начале статьи мы разговаривали об использовании &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; в корутинах, я сказал вам, что падающая корутина распространяет свое исключение по иерархии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; вместо проброса и таким образом, блок &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; не работает.&lt;/p&gt;

&lt;p&gt;Однако, когда мы оборачиваем падающую корутину в &lt;em&gt;scope&lt;/em&gt; функцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineScope{}&lt;/code&gt;, происходит нечто интересное:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    
  &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;coroutineScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RuntimeException in nested coroutine&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception in try/catch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Handle java.lang.RuntimeException: RuntimeException in nested coroutine in try/catch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
Теперь мы можем обрабатывать исключения в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;. &lt;em&gt;Scope&lt;/em&gt; функция &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineScope{}&lt;/code&gt; пробрасывается исключения из своих дочерних корутин вместо распространения по иерарахии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineScope{}&lt;/code&gt; используется в основном в &lt;em&gt;suspend&lt;/em&gt; функциях для “параллельной декомпозиции”. Эти &lt;em&gt;suspend&lt;/em&gt; функции будут пробрасывать исключения из своих корутин и таким образом можно будет организовать обработку исключений.&lt;/p&gt;

&lt;h2 id=&quot;ключевая-особенность-5&quot;&gt;Ключевая особенность #5&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;scope&lt;/em&gt; функция &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineScope{}&lt;/code&gt; пробрасывает исключения от дочерних корутин, вместо распространия по иерархии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt;. Это дает нам обработку ошибок через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;.&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;обработка-исключений-в-supervisorscope&quot;&gt;Обработка исключений в supervisorScope{}&lt;/h2&gt;

&lt;p&gt;Когда мы используем &lt;em&gt;scope&lt;/em&gt; функцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope{}&lt;/code&gt;, устанавливается новый, отдельный, вложенный &lt;em&gt;scope&lt;/em&gt; с типом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SupervisorJob&lt;/code&gt; в нашей Job иерархии.
Что-то вроде этого…&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;job1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;starting Coroutine 1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;supervisorScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;job2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;starting Coroutine 2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;job3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;starting Coroutine 3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;… создает иерархию корутин:
&lt;img src=&quot;/images/why-exception-handling-with/4.webp&quot; alt=&quot;coroutines&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Здесь важно понимать, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt; это новый, отдельный вложенный &lt;em&gt;scope&lt;/em&gt;, который должен обрабатывать исключения сам. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt; не пробрасывает исключения своих внутренних корутин (как это делает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineScope&lt;/code&gt;), и не передает исключения родительской &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; (в примере это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topLevelScope&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Еще одна важная вещь - исключения распространяются вверх по иерархии, пока не дойдут до &lt;em&gt;scope&lt;/em&gt; верхнего уровня или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SupervisorJob&lt;/code&gt;. Это значит, что в примере выше: “Coroutine 2” и “Coroutine 3”, это корутины верхнего уровня.&lt;/p&gt;

&lt;p&gt;Также мы можем установить &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; отдельно для “Coroutine 2” (или “Coroutine 3”):&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;coroutineExceptionHandler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineExceptionHandler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coroutineContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Handle $exception in CoroutineExceptionHandler&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;topLevelScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;topLevelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;job1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;starting Coroutine 1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;supervisorScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;job2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutineExceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;starting Coroutine 2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Exception in Coroutine 2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;job3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;starting Coroutine 3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// starting Coroutine 1&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// starting Coroutine 2&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Handle java.lang.RuntimeException: Exception in Coroutine 2 in CoroutineExceptionHandler&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// starting Coroutine 3&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
Так как корутины в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt; это корутины верхнего уровня, это значит, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; корутины теперь оборачивают свои исключения в Deferred объекты…&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// ... other code is identical to example above&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;supervisorScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;job2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;starting Coroutine 2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Exception in Coroutine 2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Output: &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// starting Coroutine 1&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// starting Coroutine 2&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// starting Coroutine 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;… и будут проброшены при вызове &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.await()&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;ключевая-особенность-6&quot;&gt;Ключевая особенность #6&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;scope&lt;/em&gt; функция &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope{}&lt;/code&gt; создает новый независимый &lt;em&gt;scope&lt;/em&gt; с типом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SupervisorJob&lt;/code&gt; в иерархии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt;. Этот новый &lt;em&gt;scope&lt;/em&gt; не распространяет свои исключения “вверх по иерархии”, обработку ошибок он должен выполнять самостоятельно. Корутины запускаемые из &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt; являются корутинами верхнего уровня. Корутины верхнего уровня ведут себя иначе, чем дочерние корутины, при запуске через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch()&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async()&lt;/code&gt;. Кроме того, в корутины верхнего уровня можно установить обработчики исключений &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandlers&lt;/code&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;все&quot;&gt;Все!&lt;/h2&gt;
&lt;p&gt;Я вывел эти ключевые моменты, пытаясь понять обработку исключений в корутинах.&lt;/p&gt;</content><author><name></name></author><category term="Android" /><category term="Kotlin" /><category term="Coroutines" /><category term="Error" /><category term="Exception" /><summary type="html">Перевод статьи Why exception handling with Kotlin Coroutines is so hard and how to successfully master it!</summary></entry><entry><title type="html">Kotlin, обрабатываем исключения в корутинах правильно.</title><link href="/android/kotlin/coroutines/suspend/error/2022/05/05/coroutines-errors-handling.html" rel="alternate" type="text/html" title="Kotlin, обрабатываем исключения в корутинах правильно." /><published>2022-05-05T08:02:00+00:00</published><updated>2022-05-05T08:02:00+00:00</updated><id>/android/kotlin/coroutines/suspend/error/2022/05/05/coroutines-errors-handling</id><content type="html" xml:base="/android/kotlin/coroutines/suspend/error/2022/05/05/coroutines-errors-handling.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://www.netguru.com/blog/exceptions-in-kotlin-coroutines&quot; target=&quot;_blank&quot;&gt;Are You Handling Exceptions in Kotlin Coroutines Properly?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022-05-05-coroutines-errors-handling/1.webp&quot; alt=&quot;coding image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;как-kotlin-разработчик-вы-скорее-всего-знаете-что-корутины-в-случае-ошибки-выкидывают-исключения&quot;&gt;Как Kotlin разработчик, вы скорее всего знаете, что корутины в случае ошибки, выкидывают исключения.&lt;/h2&gt;

&lt;p&gt;Возможно вы думаете обработка таких исключений происходит как обычно в Kotlin/Java коде. К сожалению, при использовании вложенных корутин, все может работать не так как ожидается.&lt;/p&gt;

&lt;p&gt;В этой статье я попробую показать ситуации, в которых требуется осторожность и расскажу про лучшие практики в обработке ошибок.&lt;/p&gt;

&lt;h2 id=&quot;работа-вложенных-корутин&quot;&gt;Работа вложенных корутин&lt;/h2&gt;

&lt;p&gt;Давайте начнем с примера, где все вроде бы выглядит нормально.
Пример показывает сценарий, когда нужно обновить View, данные для которого комбинируются из двух разных источников и один из источников падает с ошибкой. Функция-билдер для корутин &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; будет использоваться в слое &lt;em&gt;Repository&lt;/em&gt; для выполнения двух запросов паралелльно. Билдер требует &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineScope&lt;/code&gt;, обычно он берется из &lt;em&gt;ViewModel&lt;/em&gt; в которой запускается выполнение корутины.&lt;/p&gt;

&lt;p&gt;Метод в &lt;em&gt;Repository&lt;/em&gt; будет выглядеть так:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getNecessaryData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;failingDataDeferred&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getFailingData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;successDataDeferred&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;failingDataDeferred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;successDataDeferred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fromResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Запрос с ошибкой просто пробрасывает исключение в своем теле через небольшой таймаут:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getFailingData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResponseModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Request Failed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Во &lt;em&gt;ViewModel&lt;/em&gt; данные запрашиваются:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewModelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kotlin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;runCatching&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getNecessaryData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onSuccess&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onFailure&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Я использую удобный &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kotlin.Result&lt;/code&gt; функционал здесь, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runCatching&lt;/code&gt; оборачивает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; блок, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewState&lt;/code&gt; – класс-обертка для состояний UI.
При запуске этого кода, приложение &lt;strong&gt;падает&lt;/strong&gt; с нашим созданным &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RuntimeException&lt;/code&gt;. Это кажется странными, ведь мы используем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; блок для перехвата любых исключений.
Чтобы понять, что здесь происходит, давайте повторим, как исключения обрабатываются в Kotlin и Java.&lt;/p&gt;

&lt;h2 id=&quot;повторный-проброс-исключений&quot;&gt;Повторный проброс исключений&lt;/h2&gt;

&lt;p&gt;Простой пример:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-koltin&quot;&gt;fun someMethod() {
   try {
      val failingData = failingMethod()
   } catch (e: Exception) {
      // handle exception
   }
}

fun failingMethod() {
   throw RuntimeException()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Исключение возникает в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;failingMethod&lt;/code&gt;. И в Kotlin и в Java, функции по умолчанию &lt;strong&gt;пробрасывают&lt;/strong&gt; все исключения, которые не были обработаны внутри них. Благодаря этому механизму, исключения из функции &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;failingMethod&lt;/code&gt; можно поймать в родительском коде через блок &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;распространение-исключений&quot;&gt;Распространение исключений&lt;/h2&gt;

&lt;p&gt;Давайте перенесем логику предыдущего примера во &lt;em&gt;ViewModel&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewModelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;failingData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Request Failed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;failingData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fromResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Можно заметить некоторое сходство. Первая функция &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; выглядит как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;failingMethod&lt;/code&gt; выше, но так как исключение не перехватывается похоже, что этот блок не пробрасывает исключение дальше!&lt;/p&gt;

&lt;p&gt;Это первый ключевой момент в этой истории:&lt;/p&gt;

&lt;p&gt;И &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; функции не пробрасывают исключения, которые возникают внутри. Вместо этого, они РАСПРОСТРАНЯЮТ их вверх по иерархии корутины.
Поведение верхнего уровня &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; объясняется ниже.&lt;/p&gt;

&lt;h2 id=&quot;иерархия-корутины-и-coroutineexceptionhandler&quot;&gt;Иерархия корутины и CoroutineExceptionHandler&lt;/h2&gt;

&lt;p&gt;Наша иерархия корутин выглядит таким образом:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022-05-05-coroutines-errors-handling/2.png&quot; alt=&quot;Coroutine image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;На самом верху у нас находится область видимости (scope) от &lt;em&gt;ViewModel&lt;/em&gt;, где мы создаем корутину &lt;strong&gt;верхнего уровня&lt;/strong&gt; при помощи функции &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt;. Внутри этой корутины мы добавляем 2 &lt;strong&gt;дочерние&lt;/strong&gt; корутины через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;.
Когда исключение возникает в любой дочерней корутине, исключение не пробрасывается, а немедленно передается вверх по иерархии пока не достигнет объекта области видимости (scope).&lt;/p&gt;

&lt;p&gt;Далее &lt;em&gt;scope&lt;/em&gt; передает исключение в обработчик &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;. Он может быть установлен или в самом &lt;em&gt;scope&lt;/em&gt; через конструктор или в корутине верхнего уровня, как параметр в функциях &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Имейте в виду, установка обработчика в любой &lt;strong&gt;дочерней&lt;/strong&gt; корутине не будет работать.&lt;/p&gt;

&lt;p&gt;Такой механизм распространиения исключений это часть &lt;strong&gt;Структурной Конкурентности (Structured Concurrency)&lt;/strong&gt;, принцип проектирования корутин, введеный авторами для правильного выполнения и отмены иерархии корутин без утечек памяти.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://elizarov.medium.com/structured-concurrency-722d765aa952&quot; target=&quot;_blank&quot;&gt;Больше деталей здесь.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ок, но почему наше приложение падает, когда есть такой механизм? Потому что мы не установили никакого обработчика &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;Давайте передадим обработчик в функцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; (сделать это через scope здесь невозможно, так как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewModelScope&lt;/code&gt; создается не нами):&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;exceptionHandler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineExceptionHandler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coroutineContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throwable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;throwable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;viewModelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// content unchanged&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Теперь мы будем правильно получать ошибку в наше &lt;em&gt;view&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;дополнительные-возможности&quot;&gt;Дополнительные возможности&lt;/h2&gt;

&lt;p&gt;После наших правок, блок &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; больше не нужен, исключения больше не будут перехватываться там.
Мы можем удалить его и полностью полагаться на установленный обработчик ошибок. Однако, это может быть не самым лучшим решением, если нам нужен больший контроль над выполнением корутин, так как этот обработчик собирает все исключения и не дает механизмов повтора или альтернативного исполнения.&lt;/p&gt;

&lt;p&gt;Вторая возможность – удалить обработчик исключений, оставить логику блока &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt;, но изменить слой репозитория данных (&lt;em&gt;Repository&lt;/em&gt;) с использованием специальных билдеров корутин для вложенного исполнения: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineScope&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt;. Таким образом у нас будет больше контроля над потоком исполнения и, к примеру, можно использовать метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;recoverCatching&lt;/code&gt; из &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kotlin.Result&lt;/code&gt; если требуется операция восстановления после ошибки.&lt;/p&gt;

&lt;p&gt;Давайте взглянем, что эти билдеры предлагают.&lt;/p&gt;

&lt;h2 id=&quot;coroutinescope-билдер&quot;&gt;coroutineScope билдер&lt;/h2&gt;

&lt;p&gt;Этот билдер создает дочерний &lt;em&gt;scope&lt;/em&gt; в иерархии корутины. Ключевые особенности:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;наследует контекст из вызывающей корутины и поддерживает структурную конкурентность&lt;/li&gt;
  &lt;li&gt;не распространяет исключения из дочерних корутин, вместо этого пробрасывает (re-throw) исключения&lt;/li&gt;
  &lt;li&gt;отменяет все дочерние корутины, если хотя бы одна из них падает с ошибкой&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Теперь больше не надо передавать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewModelScope&lt;/code&gt; в метод:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getNecessaryData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;coroutineScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;failingDataDeferred&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getFailingData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;successDataDeferred&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
     
     &lt;span class=&quot;n&quot;&gt;failingDataDeferred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;successDataDeferred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fromResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;После этих правок, исключение из первой функции &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; попадет в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try-catch&lt;/code&gt; блок во &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewModel&lt;/code&gt;, потому что исключение повторно пробрасывается из функции билдера.&lt;/p&gt;

&lt;h2 id=&quot;supervisorscope-билдер&quot;&gt;supervisorScope билдер&lt;/h2&gt;

&lt;p&gt;Билдер создает новый &lt;em&gt;scope&lt;/em&gt; с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SupervisorJob&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ключевые особенности:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;если одна из дочерних корутин падает с исключением, другие корутины не отменяются&lt;/li&gt;
  &lt;li&gt;дочерние корутины становятся верхнеуровневыми (можно настроить &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;наследует контекст из вызывающей корутины и поддерживает структурную конкурентность (также как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineScope&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;не распространяет исключения из дочерних корутин, вместо этого пробрасывает (re-throw) исключения (также как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coroutineScope&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Соответственно, если наш первый запрос падает, мы все еще можем получить данные из второго &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; запроса, так как он не будет отменен.&lt;/p&gt;

&lt;p&gt;Эта особенность требует обработчика &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineExceptionHandler&lt;/code&gt; в верхнеуровневой корутине, иначе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt; все равно упадет.
Причина этого в механизме, который обсуждали выше - &lt;em&gt;scope&lt;/em&gt; всегда проверяет установлен ли обработчик ошибок. Если обработчика нет - будет падение.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getNecessaryData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;supervisorScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;failingDataDeferred&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getFailingData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;successDataDeferred&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
     
     &lt;span class=&quot;n&quot;&gt;failingDataDeferred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;successDataDeferred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fromResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;К сожалению, если запустить этот код, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewModel&lt;/code&gt; все еще перехватывает исключение.
Почему так?&lt;/p&gt;

&lt;h2 id=&quot;верхнеуровневый-async&quot;&gt;Верхнеуровневый async&lt;/h2&gt;

&lt;p&gt;В соответствии со второй особенностью &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt;, обе корутины запускаемые через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; становятся верхнеуровневыми, которые обрабатывают исключения по-другому, чем вложенные &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; верхнего уровня скрывает обработку исключения в объекте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deffered&lt;/code&gt;, который возвращает билдер. Объект выбрасывает нормальное исключение только при вызове метода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await()&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;обычные-исключения-в-supervisorscope&quot;&gt;Обычные исключения в supervisorScope&lt;/h2&gt;

&lt;p&gt;В документации есть следующее:
“Сбой в &lt;em&gt;scope&lt;/em&gt; (исключение выбрасывается в блоке или на этапе отмены) приводит к сбою всего &lt;em&gt;scope&lt;/em&gt; со всеми дочерними корутинами”.&lt;/p&gt;

&lt;p&gt;В нашем сценарии, исключение выбрасывается при вызове &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ailingDataDeffered.await()&lt;/code&gt;. Это происходит вне билдера &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;, таким образом это не распространяется в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt;, а выбрасывается как обычное исключение. Весь &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt; немедленно падает и пробрасывает это исключение.&lt;/p&gt;

&lt;p&gt;Чтобы избежать этой проблемы, мы можем использовать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; решение, которое будет правильно распространять исключение в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisorScope&lt;/code&gt; и вторая кортуна будет оставаться живой.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getNecessaryData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;supervisorScope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nf&quot;&gt;buildList&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getFailingData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apiService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DisplayModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fromResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Короткая заметка: вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join()&lt;/code&gt; тут нужен, потому что он приостанавливает корутину в которой работает. Благодаря этому метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getNecessaryData&lt;/code&gt; не вернет управление пока обе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; не будут завершены. В противном случае метод вернет управление сразу без всяких данных.&lt;/p&gt;

&lt;h2 id=&quot;cancellationexception&quot;&gt;CancellationException&lt;/h2&gt;

&lt;p&gt;В последней части статьи, я бы хотел рассказать об исключении &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CancellationException&lt;/code&gt;, которое используется в механизме &lt;em&gt;Structured Concurrency&lt;/em&gt; как сигнал отмены в корутинах. Это исключение передается всем корутинам внутри &lt;em&gt;scope&lt;/em&gt; при его отмене (к примеру, если пользователь ушел с экрана) или если другая корутина падает.&lt;/p&gt;

&lt;p&gt;Очень часто мы ненароком ломаем этот механизм, используя такой подход, чтобы обернуть корутины:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Это работает ок, если есть такой вызов для каждой корутины (Я также использовал подобный подход выше).&lt;/p&gt;

&lt;p&gt;Однако, если есть несколько корутин внутри другой, у нас могут быть неприятности, потому что мы 
перехватываем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CancellationException&lt;/code&gt; самостоятельно и тем самым ломаем внутренний механизм отмены корутин.&lt;/p&gt;

&lt;p&gt;Например, мы вызываем для загрузки данных следующее:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewModelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;fetchData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;someApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;fetchData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;someApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch&lt;/code&gt; запускает задачи последовательно по своей природе, это значит, что второй вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetchData&lt;/code&gt; ждет, пока не выполнится первый вызов.&lt;/p&gt;

&lt;p&gt;Если пользователь уходит с экрана до того момента, когда &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;request1&lt;/code&gt; заканчивается, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewModelScope&lt;/code&gt; будет отменен и первый вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetchData&lt;/code&gt; перехватит и обработает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CancellationException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;После этого, корутина продолжит свою работу и запустит &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;request2&lt;/code&gt; как ни в чем не бывало, потому что мы скрыли &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CancellationException&lt;/code&gt; от нее.&lt;/p&gt;

&lt;p&gt;Это лишняя трата ресурсов, которая может привести также к утечкам памяти или даже падению приложения.&lt;/p&gt;

&lt;p&gt;Чтобы это предотвратить, мы можем улучшить метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetchData&lt;/code&gt;, чтобы повторно пробрасывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CancellationException&lt;/code&gt;. Таким образом вся родительская корутина будет отменена правильно:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CancellationException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Также помним об удобном операторе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kotlin.Result&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;runDataFetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kotlin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;runCatching&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onSuccess&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onFailure&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;liveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;postValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ViewState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CancellationException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Это безопасный проброс конкретного исключения - мы можем быть уверены, корутина обработает его правильно.&lt;/p&gt;</content><author><name></name></author><category term="Android" /><category term="Kotlin" /><category term="Coroutines" /><category term="Suspend" /><category term="Error" /><summary type="html">Перевод статьи Are You Handling Exceptions in Kotlin Coroutines Properly?</summary></entry><entry><title type="html">Kotlin, как работает SUSPEND под капотом.</title><link href="/android/kotlin/coroutines/suspend/2022/04/06/kotlin-suspend.html" rel="alternate" type="text/html" title="Kotlin, как работает SUSPEND под капотом." /><published>2022-04-06T12:02:00+00:00</published><updated>2022-04-06T12:02:00+00:00</updated><id>/android/kotlin/coroutines/suspend/2022/04/06/kotlin-suspend</id><content type="html" xml:base="/android/kotlin/coroutines/suspend/2022/04/06/kotlin-suspend.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://manuelvivo.dev/suspend-modifier&quot; target=&quot;_blank&quot;&gt;The suspend modifier — under the hood&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Как компилятор преобразует &lt;em&gt;suspend&lt;/em&gt; код, чтобы корутины можно было приостанавливать и возобновлять?&lt;/p&gt;

&lt;p&gt;Корутины в Kotlin представлены ключевым словом &lt;strong&gt;&lt;em&gt;suspend&lt;/em&gt;&lt;/strong&gt;. Интересно, что там происходит внутри? Как компилятор преобразует &lt;em&gt;suspend&lt;/em&gt; блоки в код, поддерживающий приостановку и возобновление работы корутины?&lt;/p&gt;

&lt;p&gt;Знание этого поможет понимать, почему suspend функция не возвращает управление, пока не завершится вся запущенная работа и как код может приостановить выполнение без блокировки потоков.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;TL;DR; Компилятор Kotlin создает специальную машину состояний для каждой suspend функции, эта машина берет управление корутиной на себя!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Новенький в Android? Взгляни на эти полезные ресурсы по корутинам:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#0&quot; target=&quot;_blank&quot;&gt;Using coroutines in your Android app&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.android.com/codelabs/advanced-kotlin-coroutines#0&quot; target=&quot;_blank&quot;&gt;Advanced Coroutines with Kotlin Flow and Live Data&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Для тех, кто предпочитает видео:&lt;/p&gt;

&lt;iframe width=&quot;840&quot; height=&quot;473&quot; src=&quot;https://www.youtube.com/embed/IQf-vtIC-Uc&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h1 id=&quot;корутины-краткое-введение&quot;&gt;Корутины, краткое введение&lt;/h1&gt;
&lt;p&gt;Говоря по-простому, корутины это асинхронные операции в Android. Как описано в &lt;a href=&quot;https://developer.android.com/kotlin/coroutines&quot; target=&quot;_blank&quot;&gt;документации&lt;/a&gt;, мы можем использовать корутины для управления асинхронными задачами, которые иначе могут блокировать основной поток и приводить к зависанию UI приложения.&lt;/p&gt;

&lt;p&gt;Также корутины удобно использовать для замены callback-кода на императивный код. Например, посмотрите на этот код с использованием колбеков:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Simplified code that only considers the happy path&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Async callbacks&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Successful network request&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Result saved in DB&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;userResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Заменяем эти колбеки на последовательные вызовы функций с использованием корутин:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;userDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Для функций, которые вызываются в корутинах, мы добавили ключевое слово &lt;strong&gt;suspend&lt;/strong&gt;. Так компилятор знает, что эти функции для корутин. С точки зрения разработчика, рассматривайте suspend функцию как обычную, выполнение которой может быть приостановлено и возобновлено в определенный момент.&lt;/p&gt;

&lt;p&gt;В отличие от колбеков, корутины предлагают простой способ переключения между потоками и обработки исключений.&lt;/p&gt;

&lt;p&gt;Но что в действительности делает компилятор внутри, когда мы отмечаем функцию как &lt;em&gt;suspend&lt;/em&gt;?&lt;/p&gt;

&lt;h2 id=&quot;suspend-под-капотом&quot;&gt;Suspend под капотом&lt;/h2&gt;
&lt;p&gt;Давайте вернемся к suspend функции &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loginUser&lt;/code&gt;, посмотрите, другие функции которые она вызывает это тоже suspend функции:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;userDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// UserRemoteDataSource.kt&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// UserLocalDataSource.kt&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Кратко говоря, компилятор Kotlin берет suspend функции и преобразовывает их в оптимизированную версию колбеков с использованием &lt;a href=&quot;https://en.wikipedia.org/wiki/Finite-state_machine&quot; target=&quot;_blank&quot;&gt;конечной машины состояний&lt;/a&gt; (о которой мы поговорим позже).&lt;/p&gt;

&lt;h1 id=&quot;интерфейс-continuation&quot;&gt;Интерфейс Continuation&lt;/h1&gt;
&lt;p&gt;Suspend функции взаимодействуют друг с другом с помощью &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt; объектов. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt; объект - это простой generic интерфейс с дополнительными данными. Позже мы увидим, что сгенерированная машина состояний для suspend функции будет реализовать этот интерфейс.&lt;/p&gt;

&lt;p&gt;Сам интерфейс выглядит так:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineContext&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;resumeWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context&lt;/code&gt; это экземпляр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineContext&lt;/code&gt;, который будет использоваться при возобновлении.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resumeWith&lt;/code&gt; возобновляет выполнение корутины с &lt;a href=&quot;https://github.com/Kotlin/kotlinx.coroutines/blob/master/stdlib-stubs/src/Result.kt&quot; target=&quot;_blank&quot;&gt;Result&lt;/a&gt;, он может либо содержать результат вычисления, либо исключение.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;С Kotlin 1.3 и далее, вы можете использовать extensions функции &lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/resume.html&quot; target=&quot;_blank&quot;&gt;resume(value: T)&lt;/a&gt; и &lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/resume-with-exception.html&quot; target=&quot;_blank&quot;&gt;resumeWithException(exception: Throwable)&lt;/a&gt;, это специализированные версии метода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resumeWith&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Компилятор заменяет ключевое слово suspend на дополнительный аргумент &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completion&lt;/code&gt; (тип &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt;) в функции, аргумент используется для передачи результата suspend функции в вызывающую корутину:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;userDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Для упрощения, наш пример возвращает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Unit&lt;/code&gt; вместо объекта &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Байткод suspend функций фактически возвращает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any?&lt;/code&gt; так как это объединение (union) типов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T | COROUTINE_SUSPENDED&lt;/code&gt;. Что позволяет функции возвращать результат синхронно, когда это возможно.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Если suspend функция не вызывает другие suspend функции, компилятор добавляет аргумент Continuation, но не будет с ним ничего делать, байткод функции будет выглядеть как обычная функция.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Кроме того, интерфейс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt; можно увидеть в:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;При конвертации колбек-API в корутины с использованием &lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/suspend-coroutine.html&quot; target=&quot;_blank&quot;&gt;suspendCoroutine&lt;/a&gt; или &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html&quot; target=&quot;_blank&quot;&gt;suspendCancellableCoroutine&lt;/a&gt; (предпочтительнее использовать в большинстве случаев). Вы напрямую взаимодействуете с 
экземпляром &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt;, чтобы возобновить корутину, приостановленную после выполнения блока кода из аргументов suspend функции.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Вы можете запустить корутину при помощи &lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/start-coroutine.html&quot; target=&quot;_blank&quot;&gt;startCoroutine&lt;/a&gt; extension функции в suspend методе. Она принимает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt; как аргумент, который будет вызван, когда новая корутина завершится либо с результатом, либо с исключением.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;используем-dispatchers&quot;&gt;Используем Dispatchers&lt;/h1&gt;
&lt;p&gt;Вы можете переключаться между разными диспетчерами для запуска вычислений на разных потоках. Как Kotlin знает, где возобновить suspend вычисления?&lt;/p&gt;

&lt;p&gt;Есть подтип &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt;, он называется &lt;a href=&quot;https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt&quot; target=&quot;_blank&quot;&gt;DispatchedContinuation&lt;/a&gt;, где его метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resume&lt;/code&gt; делает вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dispatcher&lt;/code&gt; доступного в контексте корутины &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoroutineContext&lt;/code&gt;. Все диспетчеры (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dispatchers&lt;/code&gt;) будут вызывать метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;, кроме типа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dispatchers.Unconfined&lt;/code&gt;, он переопределяет метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isDispatchNeeded&lt;/code&gt; (он вызывается перед вызовом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;), который возвращает &lt;em&gt;false&lt;/em&gt; в этом случае.&lt;/p&gt;

&lt;h2 id=&quot;сгенерированная-машина-состояний&quot;&gt;Сгенерированная машина состояний&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Уточнение: Приведенный код не полностью соответствует байткоду сгенерированному компилятором. Это будет код на Kotlin, достаточно точный, для понимания того, что в действительности происходит внутри. Это представление сгенерировано корутинами версии 1.3.3 и может поменяться в следующих версиях библиотеки.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Компилятор Kotlin определяет, когда функция может остановится внутри. Каждая точка останова представляется как отдельное состояние в конечной машине состояний. Такие состояния компилятор помечает метками:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  
  &lt;span class=&quot;c1&quot;&gt;// Label 0 -&amp;gt; first execution&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  
  &lt;span class=&quot;c1&quot;&gt;// Label 1 -&amp;gt; resumes from userRemoteDataSource&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;userDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  
  &lt;span class=&quot;c1&quot;&gt;// Label 2 -&amp;gt; resumes from userLocalDataSource&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Компилятор использует &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;when&lt;/code&gt; для состояний:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Label 0 -&amp;gt; first execution&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Label 1 -&amp;gt; resumes from userRemoteDataSource&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Label 2 -&amp;gt; resumes from userLocalDataSource&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Этот код неполный, так как различные состояния не могут обмениваться информацией. Компилятор использует для обмена тот же самый объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt;. Вот почему родительски тип в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt; это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any?&lt;/code&gt; вместо ожидаемого возвращаемого типа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;При этом компилятор создает приватный класс, который:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;хранит нужные данные&lt;/li&gt;
  &lt;li&gt;вызывает функцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loginUser&lt;/code&gt; рекурсивно для возобновления вычисления&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ниже представлен примерный вид такого сгенерированного класса:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Комментарии в коде были добавлены вручную для объяснения действий&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  
  &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// completion parameter is the callback to the function &lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// that called loginUser&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineImpl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  
    &lt;span class=&quot;c1&quot;&gt;// Local variables of the suspend function&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;userDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
  
    &lt;span class=&quot;c1&quot;&gt;// Common objects for all CoroutineImpls&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  
    &lt;span class=&quot;c1&quot;&gt;// this function calls the loginUser again to trigger the&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// state machine (label will be already in the next state) and&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// result will be the result of the previous state's computation&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;invokeSuspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Поскольку &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invokeSuspend&lt;/code&gt; вызывает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loginUser&lt;/code&gt; только с аргументом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt;, остальные аргументы в функции &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loginUser&lt;/code&gt; будут нулевыми. На этом этапе компилятору нужно только добавить информацию как переходить из одного состояния в другое.&lt;/p&gt;

&lt;p&gt;Компилятору нужно знать:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Функция вызывается первый раз или&lt;/li&gt;
  &lt;li&gt;Функция была возобновлена из предыдущего состояния
Для этого проверяется тип аргумента &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt; в функции:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;continuation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Если функция вызывается первый раз, то создается новый экземпляр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LoginUserStateMachine&lt;/code&gt; и аргумент &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completion&lt;/code&gt; передается в этот экземпляр, чтобы возобновить вычисление. Иначе продолжится выполнение машины состояний.&lt;/p&gt;

&lt;p&gt;Давайте взглянем на код, который генерирует компилятор для смены состояний и обмена информацией между ними:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;continuation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Checks for failures&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;throwOnFailure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Next time this continuation is called, it should go to state 1&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// The continuation object is passed to logUserIn to resume &lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// this state machine's execution when it finishes&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Checks for failures&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;throwOnFailure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Gets the result of the previous state&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Next time this continuation is called, it should go to state 2&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// The continuation object is passed to logUserIn to resume &lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// this state machine's execution when it finishes&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;cm&quot;&gt;/* ... leaving out the last state on purpose */&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Обратите внимание на различия между этим и предыдущим примером кода:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Появилась переменная &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;label&lt;/code&gt; из &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LoginUserStateMachine&lt;/code&gt;, которая передается в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;when&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Каждый раз при обработке нового состояния проверяется есть ли ошибка.&lt;/li&gt;
  &lt;li&gt;Перед вызовом следующей suspend функции (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logUserIn&lt;/code&gt;), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LoginUserStateMachine&lt;/code&gt; обновляет переменную &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;label&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Когда внутри машины состояний вызывается другая suspend функция, экземпляр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt; (с типом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LoginUserStateMachine&lt;/code&gt;) передается как аргумент. Вложенная suspend функция также была преобразована компилятором со своей машиной состояний. Когда эта внутренняя машина состояний завершит свою работу, она возобновит выполнение “родительской” машины состояний.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Последнее состояние должно возобновить выполнение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completion&lt;/code&gt; через вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;continuation.cont.resume&lt;/code&gt; (очевидно что входной аргумент &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completion&lt;/code&gt;, сохраняется в переменной &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;continuation.cont&lt;/code&gt; экземпляра &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LoginUserStateMachine&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;continuation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Checks for failures&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;throwOnFailure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Gets the result of the previous state&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDb&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Resumes the execution of the function that called this one&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
Компилятор Kotlin делает много работы “под капотом”. 
Из &lt;em&gt;suspend&lt;/em&gt; функции:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;userDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
Генерируется большой кусок кода:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// completion parameter is the callback to the function that called loginUser&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineImpl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// objects to store across the suspend function&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;userDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Common objects for all CoroutineImpl&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// this function calls the loginUser again to trigger the &lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// state machine (label will be already in the next state) and &lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// result will be the result of the previous state's computation&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;invokeSuspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;loginUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;continuation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoginUserStateMachine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Checks for failures&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;throwOnFailure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Next time this continuation is called, it should go to state 1&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// The continuation object is passed to logUserIn to resume &lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// this state machine's execution when it finishes&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;userRemoteDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Checks for failures&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;throwOnFailure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Gets the result of the previous state&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Next time this continuation is called, it should go to state 2&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// The continuation object is passed to logUserIn to resume &lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// this state machine's execution when it finishes&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;userLocalDataSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logUserIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Checks for failures&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;throwOnFailure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Gets the result of the previous state&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDb&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Resumes the execution of the function that called this one&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Компилятор Kotlin преобразовывает каждую &lt;em&gt;suspend&lt;/em&gt; функцию в машину состояний, с использованием обратных вызовов.&lt;/p&gt;

&lt;p&gt;Зная как компилятор работает “под капотом”, вы лучше понимаете:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;почему &lt;em&gt;suspend&lt;/em&gt; функция возвращает результат только, когда завершится вся работа, которую она начала;&lt;/li&gt;
  &lt;li&gt;каким образом код приостанавливается не блокируя потоки (вся информация, о том что нужно выполнить при возобновлении работы, хранится в объекте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuation&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><category term="Android" /><category term="Kotlin" /><category term="Coroutines" /><category term="Suspend" /><summary type="html">Перевод статьи The suspend modifier — under the hood.</summary></entry><entry><title type="html">Android, Kotlin Flow во ViewModel - все сложно.</title><link href="/android/kotlin-flow/viewmodel/2021/09/12/android-kotlin-flow-viewmodel.html" rel="alternate" type="text/html" title="Android, Kotlin Flow во ViewModel - все сложно." /><published>2021-09-12T12:02:00+00:00</published><updated>2021-09-12T12:02:00+00:00</updated><id>/android/kotlin-flow/viewmodel/2021/09/12/android-kotlin-flow-viewmodel</id><content type="html" xml:base="/android/kotlin-flow/viewmodel/2021/09/12/android-kotlin-flow-viewmodel.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://bladecoder.medium.com/kotlins-flow-in-viewmodels-it-s-complicated-556b472e281a&quot; target=&quot;_blank&quot;&gt;Kotlin’s Flow in ViewModels: it’s complicated&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Загрузка данных для UI в приложении Android может быть непростой задачей. Нам надо учитывать жизненный цикл компонентов Android и &lt;strong&gt;изменения конфигурации&lt;/strong&gt;, потому что все это приводит к уничтожению и восстановлению Activity.&lt;/p&gt;

&lt;p&gt;Отдельные экраны приложения постоянно переключаются между активным и неактивным состоянием, когда пользователь ходит вперед назад по экранам, переключается с одного приложения на другое, блокирует и разблокирует экран. Каждый компонент должен выполнять активную работу в нужном состоянии экрана.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Изменения конфигурации&lt;/strong&gt; происходят в случаях:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;при изменении ориентации экрана;&lt;/li&gt;
  &lt;li&gt;когда приложение переключается в мульти-оконный режим;&lt;/li&gt;
  &lt;li&gt;при переключении визуальной темы смартфона;&lt;/li&gt;
  &lt;li&gt;при изменении системных настроек - языка, шрифтов и т.д.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;повышаем-эффективность&quot;&gt;Повышаем эффективность&lt;/h1&gt;

&lt;p&gt;Для улучшения пользовательского опыта, эффективная загрузка данных во Fragment и Activity должна учитывать следующие правила:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Кеширование&lt;/strong&gt;: актуальные загруженные данные, должны быть доставлены немедленно и не загружаться повторно. В частности, когда существующий Fragment или Activity
становятся видимыми снова или Activity пересоздается после изменения конфигурации.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Избегать фоновую работу&lt;/strong&gt;: когда Activity или Fragment скрываются (состояние изменяется со &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STARTED&lt;/code&gt; на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STOPPED&lt;/code&gt;), любая работа по загрузке внешних данных должна вставать на паузу или отменяться для экономии ресурсов. Это особенно важно для бесконeчных потоков данных, как например геолокация или периодическое обновление каких-либо данных.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Работа не прерывается при изменении конфигурации&lt;/strong&gt;: это исключение из правила #2, Во время смены конфигурации, текущая Activity заменяется новым экземпляром с сохранением состояния, поэтому отменять текущую работу в старом экземпляре Activity и перезапускать работу при создании нового экземпляра Activiti было бы контр продуктивно.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;современный-подход-viewmodel-и-livedata&quot;&gt;Современный подход: ViewModel и LiveData&lt;/h2&gt;
&lt;p&gt;В 2017 Google зарелизила первый набор библиотек &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Architecture Components&lt;/strong&gt;&lt;/a&gt;, там появились &lt;strong&gt;ViewModel&lt;/strong&gt; и &lt;strong&gt;LiveData&lt;/strong&gt; компоненты, которые помогают разработчикам эффективно работать с данными, поддерживая все 3 правила выше.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;ViewModel&lt;/strong&gt;&lt;/a&gt;, сохраняет данные при изменении конфигурации, используется для достижения правил #1 и #3: операции загрузки выполняются непрерывно во время изменения конфигурации, полученные данные могут кешироваться и совместно использоваться одним, или несколькими Fragment или Activity.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/livedata&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;LiveData&lt;/strong&gt;&lt;/a&gt;, простой контейнер данных, поддерживающий подписку на изменения и учитывающий жизненный цикл компонентов Android. Новые данные отправляются наблюдателям только когда их жизненный цикл в состоянии не менее &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STARTED&lt;/code&gt; (видимый), наблюдатели отписываются автоматически, что избавляет от утечек памяти. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; используется для достижения правил #1 и #2: кеширует последнее значение данных и это значение автоматически отправляется новым наблюдателям. В дополнение, LiveData уведомляет, когда в состоянии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STARTED&lt;/code&gt; больше нет наблюдателей и можно избежать ненужной фоновой работы.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-13-android-kotlin-flow-viewmodel/view-model-scope.png&quot; alt=&quot;view-model-scope&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Опытный разработчик, как правило уже знаком со всем этим. Но важно вспомнить все возможности, чтобы сравнить их с &lt;strong&gt;Flow&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;livedata--coroutines&quot;&gt;LiveData + Coroutines&lt;/h2&gt;

&lt;p&gt;LiveData довольна ограничена по сравнению с реактивными решениями (например RxJava):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;передает и берет данные только на главном потоке (main thread). Интересно, что оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; выполняет трансформацию объектов на главном потоке и не может использоваться на I/O потоках или для тяжелых вычислений на CPU. Для этого используется оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switchMap&lt;/code&gt; совместно с ручным запуском асинхронной операции в нужном потоке, даже если в основной поток надо отправить единственное значение.&lt;/li&gt;
  &lt;li&gt;есть только 3 оператора преобразования для LiveData: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switchMap()&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;distinctUntilChanged()&lt;/code&gt;. Если вам нужно больше, вы должны сами это сделать, используя &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MediatorLiveData&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Чтобы преодолеть эти ограничения, библиотеки Jetpack дают специальные “мосты” из LiveData для других технологий, таких как RxJava или Kotlin корутины.&lt;/p&gt;

&lt;p&gt;Самый простой и наиболее элегантный из них, по мнению автора, это &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/coroutines#livedata&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;LiveData coroutine builder&lt;/strong&gt;&lt;/a&gt;, подключается через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;androidx.lifecycle:lifecycle-livedata-ktx&lt;/code&gt; Gradle зависимость. Этот функционал похож на &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flow {} builder function&lt;/code&gt;&lt;/a&gt; из библиотеки Kotlin Coroutines и позволяет грамотно обернуть корутину в экземпляр LiveData:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;liveData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;someSuspendingFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Вы можете использовать все силу корутин, их контекстов для написания асинхронного кода в синхронной манере без колбеков, автоматически переключаясь между нужными потоками;&lt;/li&gt;
  &lt;li&gt;Новые значения отправляются наблюдателям LiveData в главном потоке через &lt;em&gt;suspending&lt;/em&gt; методы &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emit()&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emitSource()&lt;/code&gt; из корутины;&lt;/li&gt;
  &lt;li&gt;Корутина использует специальную область видимости (scope) и жизненный цикл привязанный к экземпляру LiveData. Когда LiveData становится &lt;em&gt;неактивной&lt;/em&gt; (это значит, что больше нет наблюдателей в состоянии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STARTED&lt;/code&gt;), то корутина будет автоматически отменена, работает правило #2;&lt;/li&gt;
  &lt;li&gt;В реальности отмена корутины будет &lt;strong&gt;задержана на 5 секунд&lt;/strong&gt; после того как LiveData станет неактивной для правильной обработки смены конфигурации: если новая Activity немедленно заменит старую и LiveData станет активной до срабатывания задержки, то отмена корутины не будет и цена перезапуска будет нулевой (правило #3);&lt;/li&gt;
  &lt;li&gt;если пользователь вернется назад на экран и LiveData станет активной, то корутина автоматически перезапустится, но только если она была отменена до завершения. Как только корутина завершится, она больше не будет перезапускаться, те же данные не будут загружаться дважды, если входные параметры не изменились (правило #1).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Вывод: используйте LiveData coroutines builder, это дает простой код и лучшее поведение по умолчанию.&lt;/p&gt;

&lt;p&gt;А что если, репозиторий возвращает &lt;em&gt;поток значений&lt;/em&gt; в форме &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flow&lt;/code&gt; (вместо &lt;em&gt;suspend&lt;/em&gt; функций с единственным значением)? В этом случае также возможно сконвертировать поток в LiveData и использовать все преимущества перечисленные выше, используя &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;asLiveData()&lt;/code&gt;&lt;/strong&gt; функцию-расширение.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;someFunctionReturningFlow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;asLiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Внутри &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;asLiveData()&lt;/code&gt; также использует LiveData coroutines builder для создания простой корутины обрабатывающий Flow пока LiveData активна:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Flow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;asLiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;liveData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Но давайте остановимся ненадолго – что такое &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flow&lt;/code&gt; и можно ли им полностью заменить LiveData?&lt;/p&gt;

&lt;h2 id=&quot;введение-в-kotlin-flow&quot;&gt;Введение в Kotlin Flow&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-13-android-kotlin-flow-viewmodel/kotlin-flow.jpeg&quot; alt=&quot;Flow is newer and trendy so it must be better, right?&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://kotlinlang.org/docs/flow.html&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Flow&lt;/strong&gt;&lt;/a&gt; это класс из библиотеки &lt;a href=&quot;https://github.com/Kotlin/kotlinx.coroutines&quot; target=&quot;_blank&quot;&gt;Kotlin Coroutines&lt;/a&gt; представленной в 2019 году, класс является потоком значений, вычисляемый асинхронно. Концептуально похож на RxJava Observable, но основан на корутинах и имеет более простой API.&lt;/p&gt;

&lt;p&gt;Изначально были доступны только &lt;strong&gt;холодные потоки&lt;/strong&gt; (cold flows): потоки без состояний, которые создаются по требованию каждый раз, когда наблюдатель начинает собирать значения в области видимости (scope) корутины. Каждый наблюдатель получает собственную последовательность значений, они не общие.&lt;/p&gt;

&lt;p&gt;Позже были добавлены новые &lt;strong&gt;горячие потоки&lt;/strong&gt; подтипы Flow: &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SharedFlow&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; и &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;. Они были выпущены со стабильной реализацией API в версии библиотеки корутин #1.4.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SharedFlow&lt;/code&gt;&lt;/strong&gt; публикует данные, которые распространяются всем слушателям. Класс может управлять дополнительным кешем и/или буфером и фактически заменяет все варианты устаревшего &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BroadcastChannel&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt;&lt;/strong&gt; специально оптимизированный подкласс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SharedFlow&lt;/code&gt;, который хранит и воспроизводит только последнее значение. Что-то знакомое, да?&lt;/p&gt;

&lt;h1 id=&quot;stateflow-и-livedata-много-общего&quot;&gt;StateFlow и LiveData много общего:&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;Эти классы наблюдаемые (observable)&lt;/li&gt;
  &lt;li&gt;Они хранят и распространяют последнее значение любому количеству наблюдателей&lt;/li&gt;
  &lt;li&gt;Они заставляют перехватывать ошибки на ранней стадии: необработанное исключение (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Exception&lt;/code&gt;) в колбеке LiveData останавливает приложение. Не пойманное исключение в горячем Flow потоке завершает поток без возможности перезапустить его, даже если использовать оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.catch()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;но-есть-и-важные-отличия&quot;&gt;Но есть и важные отличия:&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableStateFlow&lt;/code&gt; требует начального значения, в отличие от &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableLiveData&lt;/code&gt;. Замечание: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableSharedFlow(replay = 1)&lt;/code&gt;  может эмулировать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableStateFlow&lt;/code&gt; без начального значения, но данная реализация менее эффективна.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; всегда фильтрует повторяющиеся значения с помощью сравнения &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Any.equals()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; так не делает, для этого следует подключить оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;distinctUntilChanged()&lt;/code&gt; (замечание: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SharedFlow&lt;/code&gt; не имеет такого поведения).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; не учитывает жизненный цикл (&lt;strong&gt;not lifecycle-aware&lt;/strong&gt;). Однако, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flow&lt;/code&gt; может быть использовано в корутине с учетом жизненного цикла, это требует некоторого кода для настройки без использования LiveData (детали ниже).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; использует “версионность”, чтобы управлять отправкой значений наблюдателям. При помощи этого наблюдатель не получит дважды одного и того же значения при переходе обратно в состояние &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STARTED&lt;/code&gt;.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; не использует “версионность”. Каждый раз, когда корутина собирает данные &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flow&lt;/code&gt;, она рассматривается как &lt;strong&gt;новый наблюдатель&lt;/strong&gt; и всегда будет получать сначала последнее значение. Из-за этого может быть дублирование работы, как мы увидим на следующем примере.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;наблюдение-за-livedata-против-сбора-данных-в-flow&quot;&gt;Наблюдение за LiveData против сбора данных в Flow&lt;/h2&gt;

&lt;p&gt;Организовать наблюдение за экземпляром LiveData довольно просто:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;displayResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Эта операция однократная и дальше LiveData берет на себя синхронизацию потока данных с жизненным циклом наблюдателей.&lt;/p&gt;

&lt;p&gt;Аналогичная операция для Flow называется &lt;em&gt;сбором&lt;/em&gt; (collecting) и сбор должен выполняться в корутине. Из-за того, что Flow не знает ничего о жизненном цикле, ответственность за жизненный цикл возлагают на корутину, работающую с Flow.&lt;/p&gt;

&lt;p&gt;Чтобы создать корутину для работы с Flow, учитывающую жизненный цикл Activity/Fragment (запускать работу с данными при состоянии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STARTED&lt;/code&gt; и автоматически отменять эту работу при уничтожении):&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycleScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launchWhenStarted&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;displayResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Но здесь есть серьезное ограничение: &lt;strong&gt;код будет работать правильно только с “холодными” потоками, без поддержки каналом или буфером&lt;/strong&gt;. Такой Flow управляется только собирающей его корутиной: когда Activity/Fragment перейдет в состояние &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STOPPPED&lt;/code&gt;, корутина приостановится, производитель Flow также приостановится с ней, и больше ничего не произойдет пока корутина не возобновится.&lt;/p&gt;

&lt;p&gt;Однако, есть и другие виды Flow:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;горячие потоки&lt;/strong&gt;, которые всегда активные и посылают результаты всем текущим наблюдателями (включая приостановленные);&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;холодные потоки с колбэком или поддержкой канала&lt;/strong&gt;, которые подписываются на активный источник данных, когда сбор данных запускается и останавливает подписку, когда сбор данных отменяется (не приостанавливается).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;В этих случаях, &lt;strong&gt;основной производитель Flow будет оставаться активным&lt;/strong&gt; даже когда корутина будет приостановлена, сохраняя (в буфер) новые результаты в фоновом режиме. Ресурсы расходуются впустую, правило #2 нарушается.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-13-android-kotlin-flow-viewmodel/forest.jpeg&quot; alt=&quot;Life is like a box of chocolates&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Нужно создать более безопасный способ сборка Flow любого типа. Корутина работающая с потоком данных, должна быть отменена когда Activity/Fragment становится невидимой и перезапущена снова, так же как это делает LiveData-Coroutine-Builder. Для этого был представлен новый API в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lifecycle:lifecycle-runtime-ktx:2.4.0&lt;/code&gt; (остается в статусе alpha на момент написания статьи, на момент перевода - перешел в beta).&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycleScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;repeatOnLifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;STARTED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;displayResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Или аналогично:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycleScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flowWithLifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;STARTED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;displayResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Как видно, эффективно и безопасно работать с данными в Actvivity или Fragment проще с помощью LiveData.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Можно посмотреть дополнительную информацию о новом API в статье “&lt;a href=&quot;https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;&lt;em&gt;A safer way to collect flows from Android UIs.&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;  from Manuel Vivo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;заменяем-livedata-на-stateflow-во-viewmodel&quot;&gt;Заменяем LiveData на StateFlow во ViewModel&lt;/h2&gt;

&lt;p&gt;Давайте-ка вернемся к ViewModel. Мы убедились, что это простой и эффективный способ работы с данными в асинхронном режиме:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;liveData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;someSuspendingFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Как мы можем добиться того же самого, используя &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; вместо &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt;? &lt;em&gt;Jose Alcérreca&lt;/em&gt; написал &lt;a href=&quot;https://medium.com/androiddevelopers/migrating-from-livedata-to-kotlins-flow-379292f419fb&quot; target=&quot;_blank&quot;&gt;внушительное руководство&lt;/a&gt; для ответа на этот вопрос. Вкратце, для случая выше, код будет выглядеть так:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Flow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;flow&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;someSuspendingFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stateIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewModelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;started&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SharingStarted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;WhileSubscribed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5000L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;initialValue&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Loading&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stateIn()&lt;/code&gt; трансформирует наш холодный Flow в горячий, способный делиться одним и тем же результатом между разными наблюдателями. Благодаря &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SharingStarted.WhileSubscribed(5000L)&lt;/code&gt;, горячий поток запускается лениво при подписке первого наблюдателя и отменяется через 5 секунд, когда последний наблюдатель отпишется, что позволяет избегать лишней работы в фоновом режиме и при этом сохраняется работа при смене конфигурации. Кроме того, как только исходящий поток данных достигнет конца, он не будет перезапущен автоматически, этим мы избегаем двойной работы.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Похоже, что мы сумели выполнить 3 наших правила и воспроизвести почти такое же поведение как у LiveData с использованием более сложного кода.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Все же еще остается небольшая, но важная разница: каждый раз когда Activity/Fragment становится видимым снова, начинается новый сбор потока и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt;  всегда будет отправлять последний результат наблюдателю немедленно. &lt;strong&gt;Даже если этот же результат был уже доставлен в тот же самый Activity/Fragment во время последнего сбора данных.&lt;/strong&gt; Потому что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; не поддерживает версионность (в отличие от LiveData) и каждый новый сбор потока данных - это новый подписчик.&lt;/p&gt;

&lt;p&gt;Это проблематично? Для простых случаев нет, Activity или Fragment могут сделать дополнительную проверку, чтобы не делать лишнее обновление UI, если данные не изменились.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycleScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flowWithLifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;STARTED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;distinctUntilChanged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;displayResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Проблемы возникают в более сложных, реальных случаях, как мы увидим в следующем разделе.&lt;/p&gt;

&lt;h1 id=&quot;использование-stateflow-как-триггер-во-viewmodel&quot;&gt;Использование StateFlow как триггер во ViewModel&lt;/h1&gt;

&lt;p&gt;Подход на основе триггера обычно используется во ViewModel: каждый раз когда значение триггера изменяется - данные обновляются.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableLiveData&lt;/code&gt;&lt;/em&gt; для этого работает очень хорошо:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;trigger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MutableLiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SearchResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;switchMap&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;liveData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;При обновлении, оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switchMap()&lt;/code&gt; подключает наблюдателей к новому источнику LiveData, заменяя старый. И так как в примере выше, используется LiveData coroutine builder, старая LiveData автоматически отменит связанную с ним корутину через 5 секунд после отключения от своих наблюдателей. Работа с устаревшими данными прекращается с небольшой задержкой.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Так как в LiveData есть версионность, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableLiveData&lt;/code&gt; триггер отправит новое значение &lt;strong&gt;только один раз&lt;/strong&gt; оператору &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switchMap()&lt;/code&gt;, как только появится хотя бы один активный наблюдатель. Позже, если наблюдатели становятся неактивными и активными снова, работа источника данных LiveData просто возобновится с последними данными, где она остановилась.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Этот код достаточно прост и соблюдает все правила по эффективности выше.&lt;/p&gt;

&lt;p&gt;Давайте посмотрим, можно ли реализовать ту же самую логику с классом &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableStateFlow&lt;/code&gt;&lt;/strong&gt; вместо &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableLiveData&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;наивный-подход&quot;&gt;Наивный подход&lt;/h2&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;trigger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MutableStateFlow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Flow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SearchResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mapLatest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stateIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewModelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;started&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SharingStarted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;WhileSubscribed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5000L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;initialValue&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SearchResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;EMPTY&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;API &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableLiveData&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableLiveData&lt;/code&gt; выглядят очень похоже, код триггера выглядит почти одинаково. Самое большое различие это использование &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map-latest.html&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;mapLatest&lt;/strong&gt;&lt;/a&gt;, это эквивалент функции &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switchMap()&lt;/code&gt; в LiveData для возвращения единственного значения (для возвращения нескольких значений, надо использовать &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-latest.html&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;flatMapLatest&lt;/strong&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapLatest()&lt;/code&gt; работает как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map()&lt;/code&gt;, но вместо полного преобразования по порядку всех входных значений, входные значения используются немедленно, а преобразование происходит в &lt;strong&gt;отдельной корутине&lt;/strong&gt; асинхронно. При появлении нового значения во входящем потоке данных, трансформация предыдущего значения будет &lt;strong&gt;немедленно отменена&lt;/strong&gt;, если она все еще работала и вместо нее будет запущена новая трансформация. Таким образом можно избежать работы с устаревшими данными.&lt;/p&gt;

&lt;p&gt;Вроде выглядит неплохо. Однако здесь всплывает основная проблема: так как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; не поддерживает версионность, триггер отправит &lt;strong&gt;повторно последнее значение&lt;/strong&gt;, когда Flow перезапустится. Это случается &lt;strong&gt;каждый раз, когда Activity/Frgament становится видимым опять&lt;/strong&gt;, после того, как был невидимым более 5 секунд.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-13-android-kotlin-flow-viewmodel/oops.jpeg&quot; alt=&quot;Oops! I emit again!&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Триггер выдает значение повторно, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapLatest()&lt;/code&gt; снова запускается, еще раз дергается метод в репозитории с теми же аргументами, хотя результат уже был получен и обработан.
Правило #1 не работает: актуальные данные не должны загружаться повторно.&lt;/p&gt;

&lt;h2 id=&quot;чиним-повторную-отправку-последнего-сообщения&quot;&gt;Чиним повторную отправку последнего сообщения&lt;/h2&gt;

&lt;p&gt;Вопросы приходящие на ум: должны ли мы предотвращать повторную отправку и как это сделать? &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; уже позаботился об этом &lt;em&gt;внутри коллекции flow&lt;/em&gt; и оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;distinctUntilChanged()&lt;/code&gt; делает то же самое для других типов flow. При этом нет стандартного оператора для отмены повторной отправки среди множества коллекций одного и того же flow, потому что flow коллекции должны быть &lt;strong&gt;самодостаточные&lt;/strong&gt;. Это главная разница с LiveData.&lt;/p&gt;

&lt;p&gt;В конкретном случае flow разделяемого между несколькими наблюдателями с использованием оператора &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stateIn()&lt;/code&gt;, исходящие значения будут кешироваться и в любой момент времени будет только одна корутина обрабатывающая эти значения. Можно попробовать взломать какой-нибудь оператор, который будет запоминать последнее значение предыдущей коллекции, чтобы пропустить его, когда запускается новая коллекция (не делайте так на работе, да и дома тоже):&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Don't do this at home (or at work)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Flow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rememberLatest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Flow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;latest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NULL&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;flow&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;collectIndexed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;latest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;latest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Примечание: внимательный читатель может заметить, что такое же поведение достигается за счет замены &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MutableStateFlow&lt;/code&gt; с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Channel(capacity = CONFLATED)&lt;/code&gt; и затем превратиь его в Flow с использованием &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;receiveAsFlow()&lt;/code&gt;. Сhannel никогда не передают значение повторно.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;К сожалению, &lt;strong&gt;логика в коде выше несовершенна&lt;/strong&gt; и перестанет работать, как задумано, когда трансформация flow будет &lt;strong&gt;отменена до завершения&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Код предполагает, что после возврата &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emit(value)&lt;/code&gt;, значение было обработано и не должно быть выдано снова, если сбор flow перезапустится, но &lt;strong&gt;это так, только при использовании не буферизированных Flow операторов&lt;/strong&gt;. Операторы, подобные &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapLatest()&lt;/code&gt;, буферизированные и в этом случае &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emit(value)&lt;/code&gt; вернется немедленно, при этом преобразование выполняется асинхронно. Это значит, что нет способа узнать, когда значение было обработано flow. Если сбор flow отменяется в середине асинхронного преобразования, нам все равно придется повторно сделать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emit&lt;/code&gt; последнего значения, когда сбор flow возобновится, чтобы возобновить преобразование, иначе значение будет потеряно.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; Использование &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; в качестве триггера в ViewModel приводит к дублирующейся работе каждый раз когда Activity/Fragment становится видимой повторно и здесь нет простого пути избежать такого поведения.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Вот почему &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; предпочтительнее &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StateFlow&lt;/code&gt; при использовании как триггер во ViewModel. Эти различия не упоминаются в Codelab от Google - &lt;a href=&quot;https://developer.android.com/codelabs/advanced-kotlin-coroutines#11&quot; target=&quot;_blank&quot;&gt;Advanced coroutines with Kotlin Flow&lt;/a&gt;, там подразумевается, что реализация на Flow ведет себя так же как на LiveData. Но это не так.&lt;/p&gt;

&lt;h1 id=&quot;выводы&quot;&gt;Выводы&lt;/h1&gt;

&lt;p&gt;Мои рекомендации на основании примеров выше:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Продолжайте использовать LiveData в вашем Android-UI слое и ViewModels, особенно в качестве триггера. Используйте это везде, для передачи данных в Activity/Fragment: код будет простым и эффективным;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;LiveData coroutine builder&lt;/strong&gt; ваш друг и может заменить Flows во ViewModels в большинстве случаев;&lt;/li&gt;
  &lt;li&gt;Вы можете использовать мощь Flow операторов при необходимости, конвертируя Flows в LiveData;&lt;/li&gt;
  &lt;li&gt;Flow лучше подходит, чем LiveData, для других слоев приложения, таких как - репозитории, источники данных и т.д., они не завязаны на платформенные особенности Android и их будет легче тестировать.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Теперь вы знаете все компромиcсы при переходе от LiveData к подходу “полностью на Flow” в вашем Android-UI слое.&lt;/p&gt;</content><author><name></name></author><category term="Android" /><category term="Kotlin-Flow" /><category term="ViewModel" /><summary type="html">Перевод статьи Kotlin’s Flow in ViewModels: it’s complicated.</summary></entry><entry><title type="html">Android, жизненый цикл Jetpack компонентов.</title><link href="/android/jetpack/kotlin/lifecycleobserver/lifecycleowner/2021/09/10/android-jetpack-lifecycle-tutorial.html" rel="alternate" type="text/html" title="Android, жизненый цикл Jetpack компонентов." /><published>2021-09-10T12:02:00+00:00</published><updated>2021-09-10T12:02:00+00:00</updated><id>/android/jetpack/kotlin/lifecycleobserver/lifecycleowner/2021/09/10/android-jetpack-lifecycle-tutorial</id><content type="html" xml:base="/android/jetpack/kotlin/lifecycleobserver/lifecycleowner/2021/09/10/android-jetpack-lifecycle-tutorial.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://www.raywenderlich.com/22025947-lifecycle-aware-components-using-android-jetpack&quot; target=&quot;_blank&quot;&gt;Lifecycle-Aware Components Using Android Jetpack&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;используется: Kotlin 1.4, Android 10.0, Android Studio 4.2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Android Jetpack - коллекция библиотек для разработчиков, которая улучшает код, уменьшает повторяющийся код и работает единообразно в различных версиях Android. Архитектурные компоненты Android Jetpack дают инструменты 
для создания компонентов в приложении с учетом жизненного цикла Activity или Fragment.&lt;/p&gt;

&lt;p&gt;В этой статье, вы создадите компонент, учитывающий жизненный цикл (lifecycle-aware компонент), в приложении &lt;strong&gt;AwarenessFood&lt;/strong&gt;. Компонент будет обрабатывать изменения сетевого подключения. Вы также создадите &lt;strong&gt;lifecycle owner&lt;/strong&gt; для обновления состояние сети в Activity.&lt;/p&gt;

&lt;p&gt;Само приложение показывает случайный рецепт пользователю и имеет две опции: получить новый случайный рецепт, показать детали связанные с едой. При отключении сети, на главном экране приложения появляется SnackBar с сообщением и кнопкой повтора.&lt;/p&gt;

&lt;p&gt;В статье вы изучите:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Жизненные циклы в Android компонентах&lt;/li&gt;
  &lt;li&gt;Компоненты учитывающие жизненный цикл (Lifecycle-aware components)&lt;/li&gt;
  &lt;li&gt;Наблюдателей жизненного цикла (Lifecycle observers)&lt;/li&gt;
  &lt;li&gt;События и состояния&lt;/li&gt;
  &lt;li&gt;Владельцев жизненного цикла (Lifecycle owners)&lt;/li&gt;
  &lt;li&gt;Как тестировать Lifecycle-aware компоненты&lt;/li&gt;
  &lt;li&gt;Компонент LiveData&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;Статья предполагает, что вы знакомы с основами разработки под Android. Если это не так, посмотрите &lt;a href=&quot;http://www.raywenderlich.com/78574/android-tutorial-for-beginners-part-1&quot; target=&quot;_blank&quot;&gt;руководство для начинающих в Android&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;начало&quot;&gt;Начало&lt;/h2&gt;

&lt;p&gt;Загрузите материал с оригинальной статьи (кнопка Download вверху страницы &lt;a href=&quot;https://www.raywenderlich.com/22025947-lifecycle-aware-components-using-android-jetpack#toc-anchor-001&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;). Откройте Android Studio версии 4.2.1 или выше и импортируйте проект.&lt;/p&gt;

&lt;p&gt;Ниже общая информация, что делает каждый пакет в проекте:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;analytics&lt;/strong&gt; классы для трекинга событий в приложении&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;data&lt;/strong&gt; классы модели&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;di&lt;/strong&gt; поддержка зависимостей&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;monitor&lt;/strong&gt; содержит единственный класс, который смотрит за состоянием подключения к сети&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;network&lt;/strong&gt; классы для доступа к внешним API&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;repositories&lt;/strong&gt; классы для доступа к постоянному хранилищу&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;viewmodels&lt;/strong&gt; бизнес логика&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;view&lt;/strong&gt; экраны&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;регистрация-в-spoonacular-api&quot;&gt;Регистрация в spoonacular API&lt;/h2&gt;

&lt;p&gt;AwarenessFood приложение получает рецепты через &lt;strong&gt;spoonacular API&lt;/strong&gt;. Вам нужно зарегистрироваться там, чтобы приложение работало правильно.&lt;/p&gt;

&lt;p&gt;Заходим на сайт &lt;a href=&quot;https://spoonacular.com/food-api/console#Dashboard&quot; target=&quot;_blank&quot;&gt;spoonacular.com&lt;/a&gt; и создаем новый аккаунт. После подтверждения аккаунта, входим в личный кабинет и ищем там API ключ. Копируем его, открываем файл &lt;strong&gt;RecipesModule.kt&lt;/strong&gt; внутри пакета &lt;strong&gt;di&lt;/strong&gt; и заменяем ключ в следующей строке:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot; data-lang=&quot;kotlin&quot;&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;API_KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;YOUR_API_KEY_HERE&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Компилируем и запускаем. Должен появиться экран со случайным рецептом, похожий на такой:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/awareness-food-running-237x500.png&quot; alt=&quot;awareness-food-running&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Жмем на кнопку &lt;strong&gt;Reload&lt;/strong&gt;, чтобы загрузить другой случайный рецепт. Если отключить интернет на устройстве и попробовать получить новый рецепт, то появится SnackBar с ошибкой и кнопкой повторить, как на изображении ниже:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/awareness-food-app-network-error-237x500.png&quot; alt=&quot;awareness-food-app-network-error&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Чтобы перейти на экран деталей, жмем в меню &lt;strong&gt;More&lt;/strong&gt; пункт &lt;strong&gt;Food Trivia&lt;/strong&gt;. Вы добавите этот функционал позже. Сейчас это просто экран с кнопкой:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/awareness-food-trivia-237x500.png&quot; alt=&quot;wareness-food-trivia&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;На этом настройка проекта закончена. Сейчас вы готовы познакомиться с компонентами, учитывающие жизненный цикл (&lt;strong&gt;lifecycle-aware&lt;/strong&gt;).&lt;/p&gt;

&lt;h2 id=&quot;жизненные-циклы-в-android&quot;&gt;Жизненные циклы в Android&lt;/h2&gt;

&lt;p&gt;Важная базовая вещь для Android разработчика - это понимание, как работает жизненный цикл Activity и Fragment. Жизненный цикл представляет собой серию вызовов в определенном порядке при изменении состояния Activity или Fragment.&lt;/p&gt;

&lt;p&gt;Жизненный цикл – необходимая концепция, потому что нужно понимать, что делать в определенных состояниях Activity и Fragment. Например, настройка layout (макета) происходит в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity.onCreate()&lt;/code&gt;. Во Fragment за настройку layout отвечает метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fragment.onCreateView()&lt;/code&gt;. Другой пример - включение чтения геолокации в методе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStart()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Для освобождения ресурсов, чтение геолокации должно быть отключено в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStop()&lt;/code&gt;, здесь же освобождаются и другие ресуры и компоненты. Важный момент: не все lifecycle вызовы обязательно выполняются каждый раз. К примеру, операционная система может вызывать, а может и нет метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDestory()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Жизненный цикл Activity:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/activity_lifecycle-650x205.png&quot; alt=&quot;life-cycle activity&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Для тех, кто хочет узнать побольше о жизненном цикле Activity, взгляните на статью &lt;a href=&quot;https://www.raywenderlich.com/222232/introduction-to-android-activities-with-kotlin&quot; target=&quot;_blank&quot;&gt;Introduction to Android Activities With Kotlin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Жизненный цикл Fragment:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/fragment-lifecycle-1-133x500.png&quot; alt=&quot;life-cycle fragment&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Подробная статья о жизненном цикле Frament - &lt;a href=&quot;https://www.raywenderlich.com/216981/android-fragments-tutorial-an-introduction-with-kotlin&quot; target=&quot;_blank&quot;&gt;Android Fragments Tutorial: An Introduction With Kotlin&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;реагируем-на-изменения-жизненного-цикла&quot;&gt;Реагируем на изменения жизненного цикла&lt;/h2&gt;

&lt;p&gt;В большинстве приложений есть несколько компонентов, которым надо реагировать на события жизненного цикла Activity или Fragment. Вам нужно инициализировать или регистрировать их в методе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStart()&lt;/code&gt; и освобождать ресурсы в методе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStop()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;При таком подходе, ваш код может стать запутанным и подверженным ошибкам. Код внутри методов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStart()&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStop()&lt;/code&gt; будет нарастать бесконечно. При этом, легко забыть отписаться от компонента или вызвать метод компонента в неподходящем событии жизненного цикла, что может привести к багам, утечкам памяти и падениям приложения.&lt;/p&gt;

&lt;p&gt;Некоторые из этих проблем есть прямо сейчас в приложении. Откройте файл &lt;strong&gt;NetworkMonitor.kt&lt;/strong&gt; в проекте. Этот класс слушает состояние сети и уведомляет Activity об этом.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Примечание: Подробная информация о мониторинге сетевого соединения находится здесь - &lt;a href=&quot;https://developer.android.com/training/basics/network-ops/reading-network-state&quot; target=&quot;_blank&quot;&gt;Read Network State&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Экземпляр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; должен быть доступен с момента инициализации Activity. Откройте &lt;strong&gt;MainActivity.kt&lt;/strong&gt; и метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCreate()&lt;/code&gt;, где монитор сети инициализируется вызовом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;networkMonitor.init()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Затем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; регистрирует колбек в методе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStart()&lt;/code&gt; через вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;networkMonitor.registerNetworkCallback()&lt;/code&gt;. Наконец, экземпляр Activity отписывается от колбека в методе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStop&lt;/code&gt;, вызывая &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;networkMonitor.unregisterNetworkCallback()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Инициализация компонента, подписка на события через колбек и отписка от событий добавляет большое количество шаблонного кода в Activity. Кроме того, необходимо использовать только &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStart&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStop&lt;/code&gt; для вызова методов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;В &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActivity&lt;/code&gt; используется только один компонент, который зависит от жизненного цикла Activity. В более крупных и сложных проектах таких компонентов гораздо больше и это может привести к полному беспорядку.&lt;/p&gt;

&lt;h2 id=&quot;используем-компоненты-жизненного-цикла-lifecycle-aware&quot;&gt;Используем компоненты жизненного цикла (Lifecycle-Aware)&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NetworkMonitor&lt;/strong&gt; выполняет различные действия, зависящие от состояния жизненного цикла Activity. Другими словами, &lt;strong&gt;NetworkMonitor&lt;/strong&gt; должен быть компонентом учитывающим жизненный цикл (&lt;strong&gt;lifecycle-aware компонент&lt;/strong&gt;) и реагировать на изменения в жизненном цикле своего родителя – в нашем случае это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActivity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Android Jetpack предоставляет классы и интерфейсы для создания lifecycle-aware компонентов. Используя их, вы можете улучшить работу &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;. Он будет работать автоматически с учетом текущего состояния жизненного цикла родительской Activity.&lt;/p&gt;

&lt;p&gt;Владелец жизненного цикла (&lt;strong&gt;lifecycle owner&lt;/strong&gt;) – это компонент имеющий жизненный цикл, такой как Activity или Fragment. Владелец жизненного цикла должен знать все компоненты, которые будут слушать события жизненного цикла. Паттерн &lt;a href=&quot;https://www.raywenderlich.com/18409174-common-design-patterns-and-app-architectures-for-android#toc-anchor-014&quot; target=&quot;_blank&quot;&gt;“Наблюдатель”&lt;/a&gt; считается лучшим подходом для решения такой задачи.&lt;/p&gt;

&lt;h3 id=&quot;создание-наблюдателя-жизненного-цикла-lifecycle-observer&quot;&gt;Создание наблюдателя жизненного цикла (lifecycle observer)&lt;/h3&gt;

&lt;p&gt;Наблюдатель жизненного цикла компонент, который способен слушать и реагировать на состояния жизненного цикла своего родителя. У Jetpack есть специальный интерфейс для этого – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleObserver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ну что, настало время улучшить наш &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;. Откройте &lt;strong&gt;NetworkMonitor.kt&lt;/strong&gt; и добавьте к классу поддержку интерфейса &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleObserver&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot; data-lang=&quot;kotlin&quot;&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NetworkMonitor&lt;/span&gt; 
&lt;span class=&quot;nd&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LifecycleObserver&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;// Code to observe changes in the network connection.&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Вот и все, теперь это lifecycle наблюдатель. Вы только что сделали первый шаг по превращению &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; в lifecycle-aware компонент.&lt;/p&gt;

&lt;h1 id=&quot;события-и-состояния-жизненного-цикла&quot;&gt;События и состояния жизненного цикла&lt;/h1&gt;

&lt;p&gt;Класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lifecycle&lt;/code&gt; знает состояние (state) жизненного цикла родителя и передает его любому прослушивающему &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleObserver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lifecycle&lt;/code&gt; использует два перечисления для обмена данными о жизненном цикле: &lt;strong&gt;Event&lt;/strong&gt; и &lt;strong&gt;State&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;events&quot;&gt;Events&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; представляет события жизненного цикла, которые отправляет операционная система:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ON_CREATE&lt;/li&gt;
  &lt;li&gt;ON_START&lt;/li&gt;
  &lt;li&gt;ON_RESUME&lt;/li&gt;
  &lt;li&gt;ON_PAUSE&lt;/li&gt;
  &lt;li&gt;ON_STOP&lt;/li&gt;
  &lt;li&gt;ON_DESTROY&lt;/li&gt;
  &lt;li&gt;ON_ANY&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Каждое значение это эквивалент колбека жизненного цикла. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_ANY&lt;/code&gt; отличается тем, что отправляется при каждом событии (можно сделать один метод для обработки всех событий).&lt;/p&gt;

&lt;h2 id=&quot;реакция-на-события-жизненного-цикла&quot;&gt;Реакция на события жизненного цикла&lt;/h2&gt;

&lt;p&gt;Сейчас &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; – это &lt;strong&gt;LifecycleObserver&lt;/strong&gt;, но он пока не реагирует на жизненный цикл. Нужно добавить аннотацию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OnLifecycleEvent&lt;/code&gt; к методу, чтобы он начал реагировать на конкретное событие.&lt;/p&gt;

&lt;p&gt;Добавьте  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OnLifecycleEvent&lt;/code&gt; к нужным методам, как указано ниже:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@OnLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_CREATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@OnLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_START&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;registerNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@OnLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;unregisterNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;В этом случае, метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init()&lt;/code&gt; реагирует на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_CREATE&lt;/code&gt; событие, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registerNetworkCallback()&lt;/code&gt; на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt; и на событие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt; вызывается &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unregisterNetworkCallback()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Итак, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; теперь получает изменения жизненного цикла, сейчас нужно немного подчистить код. Следующий код больше не нужен в &lt;strong&gt;MainActivity.kt&lt;/strong&gt;, потому что эти действия  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; выполняет сам:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;savedInstanceState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Bundle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 1.Network Monitor initialization.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 2. Register network callback.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 3. Unregister network callback.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onStop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onStop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unregisterNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Полностью удаляйте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStart()&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onStop()&lt;/code&gt;. Кроме того надо удалить &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;networkMonitor.init()&lt;/code&gt; из метода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCreate()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Сделав эти изменения в коде, вы переместили из Activity ответственность за инициализацию, регистрацию и освобождение ресурсов в сам компонент.&lt;/p&gt;

&lt;h1 id=&quot;состояния-states&quot;&gt;Состояния (States)&lt;/h1&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt; хранит текущее состояние владельца жизненного цикла. Возможные значения:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;INITIALIZED&lt;/li&gt;
  &lt;li&gt;CREATED&lt;/li&gt;
  &lt;li&gt;STARTED&lt;/li&gt;
  &lt;li&gt;RESUMED&lt;/li&gt;
  &lt;li&gt;DESTROYED&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Состояния жизненного цикла сигнализируют, что конкретное событие случилось.&lt;/p&gt;

&lt;p&gt;В качестве примера, представим, что мы запускаем длительную инициализацию компонента в событии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt; и Activity (или Fragment) уничтожаются до того, как инициализация  закончится. В этом случае компонент не должен выполнять никаких действий по событию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt;, так как он не был инициализирован.&lt;/p&gt;

&lt;p&gt;Есть прямая связь между событиями и состояниями жизненного цикла. На диаграмме ниже, показана это взаимосвязь:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/state-events.png&quot; alt=&quot;state events&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Эти состояния возникают при:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;INITIALIZED: Когда Activity или Fragment уже созданы, но &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCreate()&lt;/code&gt; еще не вызван. Это первоначальное состояние жизненного цикла.&lt;/li&gt;
  &lt;li&gt;CREATED: После &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_CREATE&lt;/code&gt; и после &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;STARTED: После &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt; and После &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_PAUSE&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;RESUMED: только после &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_RESUME&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;DESTROYED: после &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_DESTROY&lt;/code&gt;, но прямо перед вызовом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDestroy()&lt;/code&gt;. Как только состояние становится &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DESTROYED&lt;/code&gt;, Activity или Fragment больше не будут посылать события.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;используем-состояния-state-жизненного-цикла&quot;&gt;Используем состояния (State) жизненного цикла&lt;/h1&gt;

&lt;p&gt;Иногда компоненты должны выполнять код, если их родитель находится, по крайней мере, в определенном состоянии. Нужно быть уверенным, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; выполняет &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registerNetworkCallback()&lt;/code&gt; в нужный момент жизненного цикла.&lt;/p&gt;

&lt;p&gt;Добавьте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lifecycle&lt;/code&gt; аргумент в конструктор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;С ним у &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; будет доступ к состоянию жизненного цикла родителя.&lt;/p&gt;

&lt;p&gt;После этого добавьте в код &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registerNetworkCallback()&lt;/code&gt; условие:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;registerNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;isAtLeast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;STARTED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;С этим условием &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; будет запускать мониторинг состояния сети только, если состояние жизненного цикла не менее, чем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STARTED&lt;/code&gt;.
Это достаточно удобно, потому что жизненный цикл может измениться до завершения кода в компоненте. Зная состояние родителя можно избежать крешей, утечек памяти и гонок состояния в компоненте.&lt;/p&gt;

&lt;p&gt;Итак, откройте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActivity.kt&lt;/code&gt; и сделайте изменения для &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NetworkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;У &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; сейчас есть доступ к родительскому жизненному циклу и прослушивание сети запускается только когда Activity находится в нужном состоянии.&lt;/p&gt;

&lt;h2 id=&quot;подписка-на-события-жизненного-цикла&quot;&gt;Подписка на события жизненного цикла&lt;/h2&gt;

&lt;p&gt;Чтобы &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; действительно начал реагировать на изменения, его родитель должен зарегистрировать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; как слушателя событий. 
В &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActivity.kt&lt;/code&gt; добавьте следующую линию в методе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCreate()&lt;/code&gt; после инициализации компонента:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Теперь владелец жизненного цикла будет уведомлять &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; об изменениях. Таким же образом можно добавить другие компоненты на прослушивание событий жизненного цикла.&lt;/p&gt;

&lt;p&gt;Это отличное улучшение. С помощью одной строчки кода компонент будет получать события жизненного цикла от родителя. Больше не надо писать шаблонный код в Activity или Fragment. Кроме того, компонент содержит весь код инициализации и конфигурации, что делает его самодостаточном и тестируемым.&lt;/p&gt;

&lt;p&gt;Соберите и запустите приложение снова. После загрузки рецептов включите “Самолетный режим”. Вы увидите ошибку сети в SnackBar, так же как и раньше:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/awareness-food-app-network-error-237x500.png&quot; alt=&quot;awareness-food-app-network-error&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Однако внутреннняя реализация была значительно улучшена.&lt;/p&gt;

&lt;h2 id=&quot;кто-владеет-жизненным-циклом&quot;&gt;Кто владеет жизненным циклом?&lt;/h2&gt;

&lt;p&gt;Все выглядит хорошо, но… кто владеет жизненным циклом? Почему Activity сама владеет жизненным циклом в этом примере? Существуют ли другие владельцы?&lt;/p&gt;

&lt;p&gt;Владелец жизненного цикла (&lt;strong&gt;lifecycle owner&lt;/strong&gt;) – компонент реализующий интерфейс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;. В нем есть единственный метод, который необходимо реализовать: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lifecycle.getLifecycle()&lt;/code&gt;. Выходит, что любой класс поддерживающий этот интерфейс может быть владельцем жизненного цикла.&lt;/p&gt;

&lt;p&gt;Android имеет встроенные компоненты с поддержкой жизненного цикла. Для Activity это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ComponentActivity&lt;/code&gt; (является базовым классом для &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppCompatActivity&lt;/code&gt; и реализует интерфейс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Однако, есть и другие классы с поддержкой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;. Например, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fragment&lt;/code&gt; это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;. Это значит, что можно переместить код в любой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fragment&lt;/code&gt;, если это надо и это будет работать точно так же, как и в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActivity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Жизненный цикл Fragment может быть значительно длиннее, чем цикл визуальных компонентов (UI), которых он содержит. Если наблюдатель взаимодействует с UI во Fragment, это может привести к проблемам, поскольку наблюдатель может изменить UI до инициализации или после уничтожения.&lt;/p&gt;

&lt;p&gt;Это причина почему есть &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewLifecycleOwner&lt;/code&gt; в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fragment&lt;/code&gt;. Вы можете использовать  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewLifecycleOwner&lt;/code&gt; после вызова &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCreateView()&lt;/code&gt; и перед &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDestroyView()&lt;/code&gt;.  Как только жизненный цикл будет уничтожен, он больше не будет отправлять события.&lt;/p&gt;

&lt;p&gt;Распространенный случай использования &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewLifecycleOwner&lt;/code&gt; это работа с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; в Fragment. Откройте &lt;strong&gt;FoodTriviaFragment.kt&lt;/strong&gt; и добавьте в методе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onViewCreated()&lt;/code&gt;, до &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewModel.getRandomFoodTrivia()&lt;/code&gt;, следующий код:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;foodTriviaState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Observer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;nf&quot;&gt;handleFoodTriviaApiState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Также надо будет добавить импорт класса &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import androidx.lifecycle.Observer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Используя этот код, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FoodTriviaFragment&lt;/code&gt; будет реагировать на события от &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foodTriviaState&lt;/code&gt; (является &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt;). Так как Fragment является владельцем жизненного цикла (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewLifecycleOwner&lt;/code&gt;), наблюдатель будет получать обновления данных только, когда Fragment находится в активном состоянии.&lt;/p&gt;

&lt;p&gt;Время сделать сборку и запустить приложение. Нажмите &lt;strong&gt;More&lt;/strong&gt; в меню и выберите &lt;strong&gt;Food Trivia&lt;/strong&gt;. Теперь можно получить несколько забавных и интересных Food Trivia в вашем приложении.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/awareness-food-trivia-working-237x500.png&quot; alt=&quot;awareness-food-app-working&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h1 id=&quot;использование-processlifecycleowner&quot;&gt;Использование ProcessLifecycleOwner&lt;/h1&gt;

&lt;p&gt;В некоторых случаях компоненту надо реагировать на изменения жизненного цикла самого приложения. К примеру, отследить когда приложение уходит в фоновый режим и возвращается на передний план. Для таких ситуаций в Jetpack есть &lt;strong&gt;ProcessLifecycleOwner&lt;/strong&gt;, который естественно реализует интерфейс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Этот класс представляет жизненный цикл всего процесса приложения. Событие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_CREATE&lt;/code&gt; посылается только один раз, когда приложение стартует. При этом событие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_DESTROY&lt;/code&gt; не будет посылаться вообще.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessLifecycleOwner&lt;/code&gt; отправляет события &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_RESUME&lt;/code&gt;, когда первая Activity проходит через эти состояния. Наконец, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessLifecycleOwner&lt;/code&gt; отправляет события &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_PAUSE&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt; после того, последняя видимая Activity приложения проходит через соответствующие состояния.&lt;/p&gt;

&lt;p&gt;Важно знать, что эти два последних события произойдут после определенной задержки. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessLifecycleOwner&lt;/code&gt; должен быть уверен в причине этих изменений. Он отправляет события &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_PAUSE&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt; только если приложение перешло в фоновый режим, а не из-за изменения конфигурации.&lt;/p&gt;

&lt;p&gt;При использовании такого владельца жизненного цикла, компонент должен также поддерживать интерфейс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleObserver&lt;/code&gt;. Откройте &lt;strong&gt;AppGlobalEvents.kt&lt;/strong&gt; в пакете &lt;strong&gt;analytics&lt;/strong&gt;. Видно, что это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleObserver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Класс отслеживает, когда приложение выходит на передний план или переходит в фоновый режим. Это происходит когда владелец жизненного цикла посылает события &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Регистрация такого &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleObserver&lt;/code&gt; происходит немного по-другому. Откройте &lt;strong&gt;RecipesApplication.kt&lt;/strong&gt; и добавьте следующий код в метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCreate()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;nc&quot;&gt;ProcessLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appGlobalEvents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Здесь мы получаем экземпляр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessLifecycleOwner&lt;/code&gt; и добавляем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appGlobalEvents&lt;/code&gt; как слушателя событий.&lt;/p&gt;

&lt;p&gt;Соберите и запустите приложение. После старта приложения, сверните его в фон. Если открыть &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LogCat&lt;/code&gt; и отфильтровать вывод по тегу &lt;em&gt;APP_LOGGER&lt;/em&gt;, то вы должны увидеть сообщения:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-09-10-android-jetpack-lifecycle-tutorial/logcat-1.png&quot; alt=&quot;logcat-1&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Итак, вы увидели как Fragment, Activity и Application компоненты реализуют интерфейс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;. Но это еще не все. Сейчас вы узнаете, как создавать собственные классы, которые делают то же самое.&lt;/p&gt;

&lt;h1 id=&quot;создаем-собственного-владельца-жизненного-цикла&quot;&gt;Создаем собственного владельца жизненного цикла&lt;/h1&gt;

&lt;p&gt;Как вы помните, любой класс может поддерживать интерфейс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;. Это значит, что можно создать кастомного владельца жизненного цикла.&lt;/p&gt;

&lt;p&gt;Давайте создадим такого владельца, жизненный цикл которого начинается, когда смартфон теряет сетевое соединение и заканчивается при восстановлении соединения.&lt;/p&gt;

&lt;p&gt;Откройте &lt;strong&gt;UnavailableConnectionLifecycleOwner.kt&lt;/strong&gt; в пакете &lt;strong&gt;monitor&lt;/strong&gt; и сделайте изменения, чтобы класс поддерживал интерфейс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Singleton&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UnavailableConnectionLifecycleOwner&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LifecycleOwner&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;После этого, добавьте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleRegistry&lt;/code&gt; в &lt;strong&gt;UnavailableConnectionLifecycleOwner&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;lifecycleRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LifecycleRegistry&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LifecycleRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getLifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lifecycleRegistry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleRegistry&lt;/code&gt; это реализация &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lifecycle&lt;/code&gt;, который может работать с несколькими наблюдателями и уведомлять их о любых изменениях в жизненном цикле.&lt;/p&gt;

&lt;h1 id=&quot;добавляем-события&quot;&gt;Добавляем события&lt;/h1&gt;

&lt;p&gt;Для уведомления о событиях жизненного цикла можно использовать метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handleLifecycleEvent()&lt;/code&gt;. Давайте добавим пару методов в наш класс:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onConnectionLost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lifecycleRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handleLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_START&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onConnectionAvailable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lifecycleRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handleLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В случае, когда смартфон потеряет сетевое соединение, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lifecycleRegistry&lt;/code&gt; пошлет событие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt; всем наблюдателям. Когда соединение снова появится, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lifecycleRegistry&lt;/code&gt; отправит &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Наконец, добавьте следующий код:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycleObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LifecycleObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lifecycleRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycleObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addObserver()&lt;/code&gt; регистрирует слушателя жизненного цикла &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UnavailableConnectionLifecycleOwner&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;реакция-на-события&quot;&gt;Реакция на события&lt;/h1&gt;

&lt;p&gt;Теперь откройте &lt;strong&gt;MainActivity.kt&lt;/strong&gt; и добавьте такую строчку кода:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;unavailableConnectionLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;После этого &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;networkObserver&lt;/code&gt; будет реагировать на события &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unavailableConnectionLifecycleOwner&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkObserver&lt;/code&gt; покажет SnackBar, когда устройство потеряет сеть и скроет SnackBar при восстановлении сети.&lt;/p&gt;

&lt;p&gt;И наконец, замените &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handleNetworkState()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handleNetworkState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NetworkState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;NetworkState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Unavailable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unavailableConnectionLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onConnectionLost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;NetworkState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Available&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unavailableConnectionLifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onConnectionAvailable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Этот код запускает события &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unavailableConnectionLifecycleOwner&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Время собрать и запустить ваше приложение. Все работает так же как и раньше, кроме мониторинга сети, где мы используем сейчас кастомный &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lifecycleOwner&lt;/code&gt; для обработки сетевого состояния.&lt;/p&gt;

&lt;p&gt;В следующей секции, вы узнаете как тестировать компоненты жизненного цикла.&lt;/p&gt;

&lt;h2 id=&quot;тестирование-компонентов-жизненного-цикла&quot;&gt;Тестирование компонентов жизненного цикла&lt;/h2&gt;

&lt;p&gt;Использование компонента жизненного цикла в нашем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; дает еще одно преимущество - мы можем тестировать код со всеми связанными событиями жизненного цикла.&lt;/p&gt;

&lt;p&gt;Эти тесты будут проверять, что вызывается нужный метод в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt; в соответствии с состоянием владельца жизненного цикла. Чтобы создать тест, нужно пройти те же шаги, что и при создании кастомного владельца жизненного цикла.&lt;/p&gt;

&lt;h3 id=&quot;настройка-тестов&quot;&gt;Настройка тестов&lt;/h3&gt;
&lt;p&gt;Откройте &lt;strong&gt;NetworkMonitorTest.kt&lt;/strong&gt;. Для запуска теста необходимо сделать заглушки (mock) для владельца жизненного цикла и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;. Добавьте две заглушки в тестовый класс:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;lifecycleOwner&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mockk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relaxed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;networkMonitor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mockk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;NetworkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relaxed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Здесь используется функционал библиотеки &lt;a href=&quot;https://mockk.io&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;MockK&lt;/strong&gt;&lt;/a&gt;, она позволяет имитировать реализацию класса. Аргумент &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;relaxed&lt;/code&gt; говорит, что заглушки могут работать без указания их поведения.&lt;/p&gt;

&lt;p&gt;После этого создайте переменную:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LifecycleRegistry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Этот код добавляет объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleRegistry&lt;/code&gt; в тест, для управления наблюдателями и рассылки им событий жизненного цикла.&lt;/p&gt;

&lt;p&gt;И наконец, добавьте следующую строчку кода в метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LifecycleRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lifecycleOwner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addObserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Здесь инициализируется реестр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleRegistry&lt;/code&gt;, в него передается заглушка владельца жизненного цикла и добавляется наблюдатель &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;, чтобы слушать события.&lt;/p&gt;

&lt;h3 id=&quot;добавляем-тесты&quot;&gt;Добавляем тесты&lt;/h3&gt;

&lt;p&gt;Чтобы убедиться, что нужный метод вызывается в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;, реестр жизненного цикла должен установить правильное состояние и уведомить своих слушателей. Для этого будет использоваться метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handleLifecycleEvent()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Первый тест будет проверять, что при событии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_CREATE&lt;/code&gt; вызывается метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Давайте напишем тест:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;`When&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatching&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;On&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Create&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 1. Notify observers and set the lifecycle state.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handleLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_CREATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 2. Verify the execution of the correct method.&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;verify&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В коде выше вы:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Сначала устанавливаете состояние &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_CREATE&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;После этого, проверяете, что был вызван метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init()&lt;/code&gt;  в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Не забудьте импортировать зависимости:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;androidx.lifecycle.Lifecycle&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.mockk.verify&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.junit.Test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Запускайте тест, он проходит успешно.&lt;/p&gt;

&lt;p&gt;Теперь давайте проверим событие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt;, оно должно вызывать метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registerNetworkCallback()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;`When&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatching&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;On&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Start&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;registerNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handleLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_START&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;verify&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Этот код проверяет &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt; событие.&lt;/p&gt;

&lt;p&gt;Тест должен пройти успешно.&lt;/p&gt;

&lt;p&gt;В конце концов, создадим тест для проверки события &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt; и вызова метода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unregisterNetworkCallback()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;`When&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatching&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;On&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Stop&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;unregisterNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 1. Notify observers and set the lifecycle state.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handleLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 2. Verify the execution of the correct method.&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;verify&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unregisterNetworkCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Этот код проверяет &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt; событие.&lt;/p&gt;

&lt;p&gt;Запустите тест и … он падает с ошибкой &lt;strong&gt;Verification failed: call 1 of 1: NetworkMonitor(#2).unregisterNetworkCallback()) was not called.&lt;/strong&gt;
Это значит, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unregisterNetworkCallback()&lt;/code&gt; не выполнился при событии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt;? Тестовый код посылает событие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_STOP&lt;/code&gt;, но перед ним обязательно должно быть событие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Добавляем событие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON_START&lt;/code&gt; в тест:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handleLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_START&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handleLifecycleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ON_STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Теперь тест проходит успешно.&lt;/p&gt;

&lt;p&gt;Таким образом, мы проверили все события жизненного цикла и они вызывают нужные методы в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkMonitor&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;livedata-компонент-жизненного-цикла&quot;&gt;LiveData: компонент жизненного цикла&lt;/h2&gt;

&lt;p&gt;К этому моменту вы узнали, как создавать собственный компонент жизненного цикла. Но существуют ли такие же готовые компоненты в Android? Конечно, возможно самый известный из них это &lt;strong&gt;LiveData&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Принцип &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; достаточно прост. Это хранилище данных за которыми можно наблюдать, то есть оно может содержать данные и уведомлять слушателей об изменениях этих данных. Однако &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; это еще и компонент жизненного цикла, то есть он уведомляет своих наблюдателей только тогда, когда жизненный цикл находится в активном состоянии.&lt;/p&gt;

&lt;p&gt;Наблюдатель в &lt;strong&gt;активном состоянии&lt;/strong&gt; если его жизненный цикл в состоянии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STARTED&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RESUMED&lt;/code&gt;. Если жизненный цикл в другом состоянии, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; не будет уведомлять слушателей об изменениях.&lt;/p&gt;

&lt;h1 id=&quot;создание-и-присваивание-переменных-livedata&quot;&gt;Создание и присваивание переменных LiveData&lt;/h1&gt;

&lt;p&gt;Откройте &lt;strong&gt;MainViewModel.kt&lt;/strong&gt; из пакета &lt;strong&gt;viewmodels&lt;/strong&gt;. Добавьте там следующие переменные:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;_loadingState&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MutableLiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;UiLoadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;loadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LiveData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;UiLoadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_loadingState&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_loadingState&lt;/code&gt; это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; с двумя возможными значениями: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Loading&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NotLoading&lt;/code&gt;. Эти значения будут говорить UI показывать или скрывать загрузку (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProgressBar&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Каждый раз, когда &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; переменная получает новое значение, она уведомляет своих слушателей об изменении. Для установки нового значения используйте поле &lt;strong&gt;value&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Давайте изменим метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getRandomRecipe()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getRandomRecipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_loadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UiLoadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Loading&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;viewModelScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;recipeRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getRandomRecipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;_loadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UiLoadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;NotLoading&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;_recipeState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Теперь обновление &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt; будет отправлено всем слушателям &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_loadingState&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;наблюдения-за-изменениями-livedata&quot;&gt;Наблюдения за изменениями LiveData&lt;/h1&gt;

&lt;p&gt;Откройте &lt;strong&gt;MainActivity.kt&lt;/strong&gt; и добавьте код в метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCreate()&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Observer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uiLoadingState&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;handleLoadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uiLoadingState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Здесь, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActivity&lt;/code&gt; начнет наблюдать за обновлениями от &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewModel.loadingState&lt;/code&gt;. Как вы видите, первый аргумент &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;observe()&lt;/code&gt; это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;, то есть текущий экземпляр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActivity&lt;/code&gt;. Взгляните на сигнатуру метода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;observe()&lt;/code&gt;, там видно, что первый аргумент имеет тип &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt;. Это значит, что наблюдатели &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiveData&lt;/code&gt; будут реагировать на изменения в зависимости от состояния жизненного цикла Activity. Для того чтобы открыть сигнатуру метода, нажмите &lt;strong&gt;Control&lt;/strong&gt;- или &lt;strong&gt;Command&lt;/strong&gt;-click.&lt;/p&gt;

&lt;p&gt;В методе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;observe()&lt;/code&gt; есть такой код:&lt;/p&gt;
&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getLifecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCurrentState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DESTROYED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ignore&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Видно, если &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt; в состоянии &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DESTROYED&lt;/code&gt; и новое значение было установлено для переменной, то наблюдатель не получит обновление.&lt;/p&gt;

&lt;p&gt;Однако, если &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LifecycleOwner&lt;/code&gt; становится активным опять, то наблюдатели получат &lt;em&gt;последнее значение&lt;/em&gt; автоматически.&lt;/p&gt;

&lt;p&gt;Соберите и запустите проект. Приложение будет показывать прогресс бар во время загрузки приложения, после окончания прогресс бар скрывается.&lt;/p&gt;

&lt;p&gt;Поздравляю! Вы отрефакторили проект с использованием компонентов жизненного цикла.&lt;/p&gt;

&lt;h2 id=&quot;что-посмотреть&quot;&gt;Что посмотреть?&lt;/h2&gt;

&lt;p&gt;Официальная документация по архитектурным компонентам: &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture&quot; target=&quot;_blank&quot;&gt;Architecture Components: Official Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Изучаем Jetpack компоненты: &lt;a href=&quot;https://www.raywenderlich.com/200817/android-jetpack-architecture-components-getting-started&quot; target=&quot;_blank&quot;&gt;Android Jetpack Architecture Components: Getting Started&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Дополнительные материалы по тестированию Jetpack компонентов: &lt;a href=&quot;https://www.raywenderlich.com/12678525-testing-android-architecture-components&quot; target=&quot;_blank&quot;&gt;Testing Android Architecture Components&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Глубокое погружение в LiveData: &lt;a href=&quot;https://www.raywenderlich.com/10391019-livedata-tutorial-for-android-deep-dive&quot; target=&quot;_blank&quot;&gt;Testing Android Architecture Components&lt;/a&gt;.&lt;/p&gt;</content><author><name></name></author><category term="Android" /><category term="Jetpack" /><category term="Kotlin" /><category term="LifecycleObserver" /><category term="LifecycleOwner" /><summary type="html">Перевод статьи Lifecycle-Aware Components Using Android Jetpack. используется: Kotlin 1.4, Android 10.0, Android Studio 4.2</summary></entry><entry><title type="html">Android, работа с BLE - часть 4.</title><link href="/android/ble/bluetoothlowenergy/2020/01/25/android-ble-part-4.html" rel="alternate" type="text/html" title="Android, работа с BLE - часть 4." /><published>2020-01-25T10:03:00+00:00</published><updated>2020-01-25T10:03:00+00:00</updated><id>/android/ble/bluetoothlowenergy/2020/01/25/android-ble-part-4</id><content type="html" xml:base="/android/ble/bluetoothlowenergy/2020/01/25/android-ble-part-4.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://medium.com/@martijn.van.welie/making-android-ble-work-part-4-72a0b85cb442&quot; target=&quot;_blank&quot;&gt;Making Android BLE work — part 4&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;внимание: в цикле статей используется минимальная версия - Android 6&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;В &lt;a href=&quot;https://fmaxx.github.io/android/ble/bluetoothlowenergy/2020/01/18/android-ble-part-3.html&quot;&gt;предыдущей статье&lt;/a&gt; мы разобрались с операциями чтения/записи, включения/выключения нотификаций и организации очереди команд. В этой статье мы поговорим о &lt;strong&gt;спряжении устройств&lt;/strong&gt; (&lt;em&gt;Прим. переводчика – далее я буду использовать термин «bonding»&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-01-25-android-ble-part-4/1.jpeg&quot; alt=&quot;devices&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;bonding&quot;&gt;Bonding&lt;/h2&gt;
&lt;p&gt;Некоторые устройства для правильной работы требуют bonding. Технически это обозначает, что генерируются ключи шифрования, обмениваются и хранятся, для безопасного обмена данными. При запуске процедуры bonding, Android может запросить у пользователя согласие, пин-код или кодовую фразу. При следующих подключениях, Android уже знает, что устройство сопряжено и обмен ключами шифрования происходит скрытно без участия пользователя.  Использование bonding делает подключение к устройству более безопасным, так как соединение зашифровано.&lt;/p&gt;

&lt;p&gt;Тема bonding плохо описана в документации Google, полностью непонятно, как приложение должно работать с bonding. Первое на что вы обратите внимание это метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt;. Что интересно, в iOS такого метода нет вообще и фреймворк &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoreBluetooth&lt;/code&gt; делает все за вас! Тогда зачем вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt;? Кажется немного странным, вам заранее надо знать, какие устройства требуют bonding, а какие нет. Протокол Bluetooth был спроектирован так, что обычно устройства явно говорят – им требуется или нет bonding. Я копнул немного глубже и поэкспериментировал. Чтобы разобраться с этим,  ушло некоторое время, но в конце концов, все оказалось просто.&lt;/p&gt;

&lt;p&gt;Принципы работы с bonding:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Пусть Android сам работает с bonding.&lt;/strong&gt; Android сделает bonding за вас, когда устройство скажет, что нужен bonding, или во время операции чтения/записи зашифрованной характеристики. В большинстве случаев не надо вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt; самостоятельно (&lt;em&gt;Прим. переводчика: мне пришлось это делать самостоятельно, из-за особенностей прошивки устройства. Кроме того, Samsung работает по-другому, чем другие вендоры&lt;/em&gt;);&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Нельзя запускать другие операции, в процессе работы bonding.&lt;/strong&gt; Если вы будете запускать обнаружение сервисов или читать/писать характеристики, это приведет к ошибками и сбросу соединения. Просто дождитесь пока Android выполнит bonding;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Продолжайте очередь операций после завершения bonding.&lt;/strong&gt; Как только операция bonding завершилась, продолжайте выполнение операций из очереди;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Если вы знаете, что делаете, и это необходимо&lt;/strong&gt; вы можете вызвать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt; для запуска bonding с устройством самостоятельно. Но это должно быть исключением.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;что-вызывает-bonding&quot;&gt;Что вызывает bonding?&lt;/h2&gt;

&lt;p&gt;Есть три причины, по которым запускается процесс bonding:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;При соединении с устройством&lt;/strong&gt;, оно сигнализирует, что требуется bonding, до любых других операций;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Характеристика может быть «зашифрована» для чтения или записи.&lt;/strong&gt; При попытке прочитать или записать такую характеристику, запустится bonding. Если он пройдет удачно чтение/запись также выполнится, в случае ошибки bonding – чтение/запись выполнится с ошибкой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSUFFICIENT_AUTHENTICATION&lt;/code&gt;. Такая же ошибка есть в iOS.&lt;/li&gt;
  &lt;li&gt;Вы &lt;strong&gt;запускаете процесс bonding самостоятельно&lt;/strong&gt; через вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt;. Если этого требует ваше устройство, оно вероятно не будет совместимо с iOS, так как там нет аналогичного метода. Но формально в протоколе Bluetooth такое возможно.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Давайте обсудим каждый случай.&lt;/p&gt;

&lt;h3 id=&quot;bonding-во-время-подключения&quot;&gt;Bonding во время подключения&lt;/h3&gt;
&lt;p&gt;Если устройство требует bonding сразу после подключения, то при вызове колбека &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChange&lt;/code&gt; состояние bonding будет &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOND_BONDING&lt;/code&gt;. Это означает что идет процесс bonding и &lt;strong&gt;вы не должны ничего делать в этот момент&lt;/strong&gt;, например вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt;, до тех пор пока процесс bonding не закончится! Иначе возможны неожиданные дисконнекты или ошибки обнаружения сервисов. Поэтому следует специально обрабатывать эту ситуацию в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChanged&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Take action depending on the bond state&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BOND_NONE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BOND_BONDED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Connected to device, now proceed to discover it's services&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; 
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BOND_BONDING&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Bonding process has already started let it complete&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;waiting for bonding to complete&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Чтобы следить, как идет процесс bonding, необходимо зарегистрировать колбек &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BroadcastReceiver&lt;/code&gt; для интента &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACTION_BOND_STATE_CHANGED&lt;/code&gt; до вызова &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt&lt;/code&gt;. Этот колбек будет вызываться несколько раз в процессе bonding.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;registerReceiver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bondStateReceiver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IntentFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ACTION_BOND_STATE_CHANGED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BroadcastReceiver&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bondStateReceiver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BroadcastReceiver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onReceive&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Intent&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothDevice&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParcelableExtra&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;EXTRA_DEVICE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Ignore updates for other devices&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Check if action is valid&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Take action depending on new bond state&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ACTION_BOND_STATE_CHANGED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bondState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getIntExtra&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EXTRA_BOND_STATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ERROR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;previousBondState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getIntExtra&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;EXTRA_PREVIOUS_BOND_STATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bondState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;BOND_BONDING:&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// Bonding started&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;BOND_BONDED:&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// Bonding succeeded&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;BOND_NONE:&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// Oh oh&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;После завершения bonding, мы запускаем обнаружение сервисов (service discovery), если они еще не обнаружены, это можно проверить:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;BOND_BONDED:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Bonding succeeded&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bonded&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Check if there are services&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// No services discovered yet&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bleHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;discovering services of '%s'&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;discoverServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;discoverServices failed to start&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Вот и все, что касается особенностей bonding при подключении.&lt;/p&gt;

&lt;h3 id=&quot;bonding-при-чтениизаписи-зашифрованных-характеристик&quot;&gt;Bonding при чтении/записи зашифрованных характеристик&lt;/h3&gt;

&lt;p&gt;Если bonding стартует при чтении/записи зашифрованной характеристики, то самая первая операция чтения/записи окончится с ошибкой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_INSUFFICIENT_AUTHENTICATION&lt;/code&gt;. На версиях Android-6, 7 вы получите эту ошибку в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCharacteristicRead&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCharacteristicWrite&lt;/code&gt;, при этом процесс bonding уже будет запущен внутри Android. С версии Android-8 ошибки не будет и Android самостоятельно повторит операцию после завершения bonding. Получается на Android-6, 7 надо повторить операцию чтения/записи самостоятельно. Итак, вам надо поймать ошибку и сделать повтор операции после bonding.&lt;/p&gt;

&lt;p&gt;При получении такой ошибки, не продолжайте запуск операций:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onCharacteristicRead&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Perform some checks on the status field&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GATT_SUCCESS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GATT_INSUFFICIENT_AUTHENTICATION&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Characteristic encrypted and needs bonding,&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// So retry operation after bonding completes&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// This only happens on Android 5/6/7&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;read needs bonding, bonding in progress&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ENGLISH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: Read failed for characteristic: %s, status %d&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;completedCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;После bonding проверяем, есть ли операция в процессе выполнения и повторяем ее:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;BOND_BONDED:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Bonding succeeded&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bonded&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Check if there are services&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// If bonding was triggered by a read/write, we must retry it&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SDK_INT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VERSION_CODES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;O&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commandQueueBusy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manuallyBonding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;bleHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;postDelayed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;retrying command after bonding&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;retryCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;запуск-bonding-самостоятельно&quot;&gt;Запуск bonding самостоятельно&lt;/h3&gt;
&lt;p&gt;Как я говорил выше, лучше не вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond&lt;/code&gt; самостоятельно, хотя сделать это, конечно можно. Спросите себя, это действительно необходимо? На iOS нет эквивалента метода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt;, если этот метод – единственный способ сделать bonding для вашего устройства, то скорее всего оно несовместимо с iOS. Это прямо указывается в документации iOS. Я перепробовал несколько десятков BLE устройств, и только в единственном случае я вызывал &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt; самостоятельно из-за исключительных обстоятельств.&lt;/p&gt;

&lt;p&gt;При вызове &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond&lt;/code&gt; самостоятельно, также нельзя ничего делать, пока bonding не завершится и требуется регистрировать колбек &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BroadcastReceiver&lt;/code&gt; для отслеживания процесса. Если устройство уже сопряжено (bonding завершился), то &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt; вызовет ошибку, надо проверить состояние bonding перед вызовом.&lt;/p&gt;

&lt;p&gt;Еще одна причина запускать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createBond()&lt;/code&gt; самостоятельно – упростить повторное подключение. Объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothDevice&lt;/code&gt; можно получить при помощи MAC-адреса, если устройство закешировано или сопряжено (bonding). Таким образом вам не придется снова сканировать устройство… Может пригодиться! (&lt;em&gt;Прим. переводчика: я как раз работал с таким вариантом подключения, его требовалось сделатьполностью детерминированным, разбитым на подфазы, для точного понимания что происходит.&lt;/em&gt;)&lt;/p&gt;

&lt;h2 id=&quot;удаление-bonding&quot;&gt;Удаление bonding&lt;/h2&gt;
&lt;p&gt;Как пользователь Android, я могу увидеть список сопряженных устройств в Bluetooth настройках. Там можно удалить устройство, bonding также будет удален.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Требуется некоторое время на удаление устройства.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Достаточно странно, что нет официального способа удалить bonding устройства программно. Это можно сделать, используя скрытый метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;removeBond()&lt;/code&gt;, доступный через механизм рефлексии в Java:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Method&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;removeBond&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[])&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[])&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Successfully removed bond&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: could not remove bond&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;потеря-bonding&quot;&gt;Потеря bonding&lt;/h2&gt;
&lt;p&gt;Большинство BLE устройств поддерживают bonding только с одним смартфоном. Типичный сценарий, когда мы теряем bonding такой:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Смартфон А делает bonding с устройством Х&lt;/li&gt;
  &lt;li&gt;Смартфон B делает bonding с устройством Х&lt;/li&gt;
  &lt;li&gt;Смартфон А переподключается к устройству Х, и теперь bonding потерян.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;При реконнекте смартфон А получит состояние bonding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOND_NONE&lt;/code&gt; в колбеке &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BroadcastReceiver&lt;/code&gt;. Сравнивайте предыдущее состояние bonding, чтобы понять была потеря или нет:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;BOND_NONE:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;previousBondState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BOND_BONDING&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;c1&quot;&gt;// Bonding failed&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;c1&quot;&gt;// Bond lost&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Если случилась потеря bonding, отключаемся от устройства, иначе будут происходить странные вещи и соединение с устройством не будет нормально работать. Когда вы делаете реконнект, Android снова запускает процедуру bonding. Тоже самое происходит и при обрыве связи.&lt;/p&gt;

&lt;p&gt;Существует мелкий баг, о котором следует знать. При потере bonding, кажется нужна &lt;strong&gt;одна секунда&lt;/strong&gt; для того, чтобы Bluetooth стек обновил свое внутреннее состояние. Если сделать реконнект сразу после потери bonding, Android может сказать, что устройство все еще сопряжено, но на самом деле это будет не так. Сделайте задержку в одну секунду перед переподключением.&lt;/p&gt;

&lt;h2 id=&quot;pairing-попап&quot;&gt;Pairing попап&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Прим. переводчика: не нашел толковой замены слова «pairing», «спаривание» - звучит неблагозвучно здесь.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Когда Android запускает процесс bonding, может появится всплывающее окно. Я говорю «может», потому что некоторые вендоры используют свою логику показа этого попапа (&lt;em&gt;Прим. переводчика: на моем Samsung-S9, после обновления до Android-10, это попап стал появляться всегда, при коннекте любого нового устройства, до этого обновления, такого не было&lt;/em&gt;). На смартфонах Google (или других вендоров, где код Android в этой части не изменялся), всплывающий попап появляется только при определенный условиях.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pairing попап появляется на переднем фоне если:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Устройство недавно было в режиме обнаружения;&lt;/li&gt;
  &lt;li&gt;Устройство было обнаружено недавно;&lt;/li&gt;
  &lt;li&gt;Устройство недавно было выбрано в «сборщике устройств»;&lt;/li&gt;
  &lt;li&gt;Экран настроек Bluetooth виден.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Значение «недавно» означает &lt;strong&gt;в течение последних 60 секунд&lt;/strong&gt;. Условия выглядят непонятными, поэтому лучше посмотреть на &lt;a href=&quot;https://android.googlesource.com/platform/packages/apps/Settings/+/eclair-release/src/com/android/settings/bluetooth/LocalBluetoothManager.java?autodive=0%2F%2F#304&quot; target=&quot;_blank&quot;&gt;исходный код&lt;/a&gt;. Если все эти условия не выполняются, то вместо попапа появится уведомление, которое большинство пользователей не замечает. Но если они заметят и нажмут на него, всплывающее окно сбивает с толку своей опцией доступа к контактам. Ужасный UI по-моему! Некоторые производители (справедливо) решили исправить такое поведение! На устройствах Samsung всплывающее окно-подтверждение (для подключений в режиме JustWorks) вообще не отображается, а всплывающие окна всегда появляются на переднем плане. При этом всплывающее окно открывается только при вводе PIN-кода или кодовой фразы. Никаких доступов к контактам и всегда передний план. Так намного лучше!&lt;/p&gt;

&lt;p&gt;Так что, если вдруг вы захотите, чтобы всплывающее окно всегда отображалось на переднем плане, запускайте обнаружение на одну секунду перед подключением к устройству. Выглядит как хак, но это работает. Код ниже:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;startPairingPopupHack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manufacturer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MANUFACTURER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manufacturer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;samsung&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bluetoothAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startDiscovery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;callBackHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;postDelayed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;popup hack completed&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;bluetoothAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;cancelDiscovery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Важный момент здесь – вы не должны запускать никакие BLE операции пока попап на экране. Подождите ответа от пользователя.&lt;/p&gt;

&lt;p&gt;Если учтете все эти моменты, bonding будет работать как часики!&lt;/p&gt;

&lt;h2 id=&quot;подведение-итогов&quot;&gt;Подведение итогов…&lt;/h2&gt;

&lt;p&gt;На этом мы завершаем цикл статей о BLE в Android (&lt;em&gt;Прим. переводчика: я готовлю отдельную статью-заключение, где опишу свои подходы к работе с BLE устройствами на Android, небольшие ньюансы и решения для стабильной продолжительной работы с устройствами&lt;/em&gt;). Надеюсь эта информация будет полезной вам и сделает работу с BLE комфортнее. Чем больше знаешь про BLE, тем лучше работает ваше приложение. Успехов!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Не терпится поработать с BLE? Попробуйте &lt;a href=&quot;https://github.com/weliem/blessed-android&quot; target=&quot;_blank&quot;&gt;мою библиотеку Blessed for Android&lt;/a&gt;. Она использует все подходы из этой серии статей и упрощает работу с BLE в вашем приложении.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name></name></author><category term="Android" /><category term="BLE" /><category term="BluetoothLowEnergy" /><summary type="html">Перевод статьи Making Android BLE work — part 4. внимание: в цикле статей используется минимальная версия - Android 6</summary></entry><entry><title type="html">Android, работа с BLE - часть 3.</title><link href="/android/ble/bluetoothlowenergy/2020/01/18/android-ble-part-3.html" rel="alternate" type="text/html" title="Android, работа с BLE - часть 3." /><published>2020-01-18T12:30:00+00:00</published><updated>2020-01-18T12:30:00+00:00</updated><id>/android/ble/bluetoothlowenergy/2020/01/18/android-ble-part-3</id><content type="html" xml:base="/android/ble/bluetoothlowenergy/2020/01/18/android-ble-part-3.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://medium.com/@martijn.van.welie/making-android-ble-work-part-3-117d3a8aee23&quot; target=&quot;_blank&quot;&gt;Making Android BLE work — part 3&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;внимание: в цикле статей используется минимальная версия - Android 6&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;В &lt;a href=&quot;https://fmaxx.github.io/android/ble/bluetoothlowenergy/2020/01/10/android-ble-part-2.html&quot;&gt;предыдущей статье&lt;/a&gt; мы подробно поговорили о подключении/отключении BLE устройств. Эта статья о &lt;strong&gt;чтении&lt;/strong&gt; и &lt;strong&gt;записи&lt;/strong&gt; характеристик, а также &lt;strong&gt;включение-выключение уведомлений&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-01-18-android-ble-part-3/1.jpeg&quot; alt=&quot;devices&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;чтение-и-запись-характеристик&quot;&gt;Чтение и запись характеристик&lt;/h2&gt;
&lt;p&gt;Многие разработчики, которые начинают работать с BLE на Android, сталкиваются с проблемами чтения/записи BLE характеристик. На &lt;a href=&quot;https://stackoverflow.com/search?q=android+ble+reading+writing+characteristic&quot; target=&quot;_blank&quot;&gt;Stackoverflow&lt;/a&gt; полно людей, предлагающих просто использовать задержки… Большинство таких советов неверные.&lt;/p&gt;

&lt;p&gt;Есть две основные причины проблем:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Операции чтения/записи асинхронные&lt;/strong&gt;. Это значит, что вызов метода вернется немедленно, но результат вызова вы получите немного позже – в соответствующих колбеках. Например &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCharacteristicRead()&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCharacteristicWrite()&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Одновременно может быть запущена только одна операция&lt;/strong&gt;. Нужно дождаться выполнения текущей операции, и затем, запускать следующую. В исходном коде &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt&lt;/code&gt; есть блокирующая переменная, которая при запуске операции устанавливается и при вызове колбека сбрасывается. Google забыла про это упомянуть в документации… (&lt;em&gt;Прим. переводчика: речь идет о &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mDeviceBusy&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mDeviceBusyLock&lt;/code&gt; &lt;a href=&quot;https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/bluetooth/BluetoothGatt.java&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Первая причина, на самом деле, не является проблемой, такова природа BLE. Асинхронное программирование это распространенная штука, используется, например, при сетевых вызовах. Однако вторая причина раздражает и требует специального подхода.&lt;/p&gt;

&lt;p&gt;Ниже кусок кода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt.java&lt;/code&gt; с блокировкой переменной &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mDeviceBusy&lt;/code&gt;, перед чтением характеристики:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;readCharacteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; 
            &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;PROPERTY_READ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VDBG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;readCharacteristic() - uuid: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mClientIf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;BluetoothGattService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;BluetoothDevice&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mDeviceBusy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mDeviceBusy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mDeviceBusy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;readCharacteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mClientIf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstanceId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AUTHENTICATION_NONE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RemoteException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mDeviceBusy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Когда приходит результат чтения/записи, переменная &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mDeviceBusy&lt;/code&gt; сбрасывается в false снова:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onCharacteristicRead&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VDBG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;onCharacteristicRead() - Device=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; handle=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; Status=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mDeviceBusy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mDeviceBusy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;....&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;используем-очередь&quot;&gt;Используем очередь&lt;/h2&gt;
&lt;p&gt;Выполнять чтение/запись по одной операции за раз неудобно, но любое сложное приложение должно это учитывать. Решение этой проблемы - использование &lt;strong&gt;очереди команд&lt;/strong&gt;. Все BLE библиотеки, которые я ранее упоминал, так или иначе реализуют очередь. Это одна из лучших практик!
Идея простая – каждая команда сначала добавляется в очередь. Затем команда забирается из очереди на исполнение, после результата, команда помечается как «завершенная» и, удаляется из очереди. Запускать команды можно в любое время, но они выполняются точно в том порядке, в котором поступают в очередь. Это очень упрощает разработку под BLE. В iOS аналогично работает фреймворк &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoreBluetooth&lt;/code&gt; (&lt;em&gt;Прим. переводчика: который намного удобнее, чем реализация Bluetooth стека в Android&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Очередь создается для каждого объекта &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt&lt;/code&gt;. К счастью, Android сможет обрабатывать очереди от нескольких объектов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt&lt;/code&gt;, вам не нужно об этом беспокоиться (&lt;em&gt;Прим. переводчика: у меня это не сработало, я использовал глобальную очередь команд для всех устройств&lt;/em&gt;). Есть много способов создать очередь, мы будем использовать простую очередь &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Queue&lt;/code&gt; с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Runnable&lt;/code&gt; для каждой команды и переменной &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commandQueueBusy&lt;/code&gt; для отслеживания работы команды:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Queue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commandQueueBusy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Мы добавляем новый экземпляр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Runnable&lt;/code&gt; в очередь при выполнении команды. Ниже пример чтения характеристики (readCharacteristic):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;readCharacteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: Gatt is 'null', ignoring read request&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Check if characteristic is valid&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: Characteristic is 'null', ignoring read request&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Check if this characteristic actually has READ property&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROPERTY_READ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: Characteristic cannot be read&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Enqueue the read command now that all checks have been passed&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;readCharacteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: readCharacteristic failed for characteristic: %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;completedCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;reading characteristic &amp;lt;%s&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;nrTries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nextCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: Could not enqueue read characteristic command&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;В этом методе, сначала проверяем все ли готово для выполнения (наличие и тип характеристики) и логгируем ошибки, если они есть. Внутри &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Runnable&lt;/code&gt;, фактически вызывается метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readCharacteristic()&lt;/code&gt;, который выдает команду на устройство. Мы также отслеживаем сколько было попыток, чтобы сделать повтор в случае ошибки (&lt;em&gt;Прим. переводчика: это лучшая тактика, чтобы добиться стабильной работы с устройством&lt;/em&gt;). Если чтение характеристики возвращает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;, мы логгируем ошибку, «завершаем» команду, чтобы можно было запустить следующую. Наконец вызывается &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextCommand()&lt;/code&gt;, чтобы запустить следующую команду из очереди:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;nextCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// If there is still a command being executed then bail out&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commandQueueBusy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Check if we still have a valid gatt object&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: GATT is 'null' for peripheral '%s', clearing command queue&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;commandQueueBusy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Execute the next command in the queue&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bluetoothCommand&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;peek&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;commandQueueBusy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nrTries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;bleHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;bluetoothCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: Command exception for device '%s'&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Обратите внимание, мы используем метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;peek()&lt;/code&gt; для получения объекта &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Runnable&lt;/code&gt; из очереди, чтобы можно было повторить запуск позже. Этот метод не удаляет объект из очереди.&lt;/p&gt;

&lt;p&gt;Результат чтения будет отправлен в ваш колбек:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onCharacteristicRead&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Perform some checks on the status field&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GATT_SUCCESS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ENGLISH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: Read failed for characteristic: %s, status %d&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;completedCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Characteristic has been read so processes it   &lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// We done, complete the command&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;completedCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Мы завершаем команду &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completedCommand()&lt;/code&gt; после обработки нового значения.  Это помогает избежать одновременный вызов другой команды и состояния гонки.&lt;/p&gt;

&lt;p&gt;Теперь мы готовы завершить команду, убираем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Runnable&lt;/code&gt; из очереди через вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;poll()&lt;/code&gt; и запускаем следующую из очереди:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;completedCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;commandQueueBusy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;isRetrying&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nextCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;В некоторых случаях (ошибка, неожиданное значение), вам нужно будет повторить команду. Сделать это просто, так как объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Runnable&lt;/code&gt; остается в очереди до вызова &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completedCommand()&lt;/code&gt;. Чтобы не уйти в бесконечное повторение – проверяем лимит на повторы:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;retryCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;commandQueueBusy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentCommand&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;peek&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentCommand&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nrTries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MAX_TRIES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Max retries reached, give up on this one and proceed&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Max number of tries reached&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;isRetrying&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nextCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;запись-характеристик&quot;&gt;Запись характеристик&lt;/h2&gt;
&lt;p&gt;Чтение характеристики достаточно простая операция, а запись требует дополнительных пояснений. Для выполнения записи нужно предоставить &lt;strong&gt;характеристику&lt;/strong&gt;, &lt;strong&gt;массив байтов&lt;/strong&gt; и &lt;strong&gt;тип записи&lt;/strong&gt;. Существует несколько типов записи, важные для нас это:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WRITE_TYPE_DEFAULT&lt;/code&gt; (вы получите ответ от устройства, например, код завершения);&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WRITE_TYPE_NO_RESPONSE&lt;/code&gt; (никакого ответа от устройства не будет).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Использовать тот или иной тип зависит от вашего устройства и характеристики (иногда она поддерживает оба типа записи, иногда только один конкретный тип).&lt;/p&gt;

&lt;p&gt;В Android каждая характеристика имеет дефолтный тип записи, который определяется при ее создании. Ниже фрагмент кода из исходников Android, где определяется тип:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mProperties&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROPERTY_WRITE_NO_RESPONSE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mWriteType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WRITE_TYPE_NO_RESPONSE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mWriteType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WRITE_TYPE_DEFAULT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Как вы видите, это работает нормально, если характеристика поддерживает только один их двух типов записи. Если характеристика поддерживает оба типа, то значение по умолчанию будет &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WRITE_TYPE_NO_RESPONSE&lt;/code&gt;. Имейте это ввиду!&lt;/p&gt;

&lt;p&gt;Перед записью можно проверить характеристику, поддерживает ли она нужный тип записи:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Check if this characteristic actually supports this writeType&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writeType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;WRITE_TYPE_DEFAULT:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeProperty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROPERTY_WRITE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WRITE_TYPE_NO_RESPONSE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeProperty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROPERTY_WRITE_NO_RESPONSE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WRITE_TYPE_SIGNED&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeProperty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROPERTY_SIGNED_WRITE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeProperty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ENGLISH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: Characteristic &amp;lt;%s&amp;gt; does not support writeType '%s'&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeTypeToString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writeType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Я рекомендую всегда явно указывать тип записи и не полагаться на дефолтные настройки выбранные Android!&lt;/p&gt;

&lt;p&gt;Итак, запись массива байтов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bytesToWrite&lt;/code&gt; в характеристику выглядит так:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytesToWrite&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setWriteType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writeType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;writeCharacteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: writeCharacteristic failed for characteristic: %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;completedCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;writing &amp;lt;%s&amp;gt; to characteristic &amp;lt;%s&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes2String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytesToWrite&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nrTries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;включениевыключение-уведомлений&quot;&gt;Включение/выключение уведомлений&lt;/h2&gt;
&lt;p&gt;Кроме самостоятельного чтения и записи характеристик, вы можете включить или отключить уведомления от устройств. При включении уведомления, устройство сообщит вам о появлении новых данных и отправит их автоматически.&lt;/p&gt;

&lt;p&gt;Для включения уведомлений нужно сделать две вещи в Android:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;вызвать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setCharacteristicNotification&lt;/code&gt;. Bluetooth стек будет ожидать уведомления для этой характеристики.&lt;/li&gt;
  &lt;li&gt;записать &lt;strong&gt;1&lt;/strong&gt; или &lt;strong&gt;2&lt;/strong&gt;  как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsigned int16&lt;/code&gt; в дескриптор конфигурации характеристик (Client Characteristic Configuration, сокращенно - ССС). Дескриптор CCC имеет короткий UUID &lt;strong&gt;2902&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Почему &lt;strong&gt;1&lt;/strong&gt; или &lt;strong&gt;2&lt;/strong&gt;? Потому что «под капотом» Bluetooth стека есть Уведомление и Индикация. Полученное Уведомление не подтверждаются стеком Bluetooth, а Индикация наоборот – подтверждается стеком. При использовании Индикации, устройство будет точно знать, что данные получены и может их, например, удалить из локального хранилища. С точки зрения Android приложения нет разницы: в обоих случаях вы просто получите массив байтов и Bluetooth стек уведомит устройство об этом, если вы используете Индикацию. Итак, &lt;strong&gt;1&lt;/strong&gt; включает уведомления, &lt;strong&gt;2&lt;/strong&gt; – индикацию. Чтобы выключить их, записываем &lt;strong&gt;0&lt;/strong&gt;. Вы должны самостоятельно определить, что записать в дескриптор CCC.&lt;/p&gt;

&lt;p&gt;В iOS метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setNotify()&lt;/code&gt; делает всю работу за вас. Ниже пример, как сделать тоже самое на Android, там сначала идут проверки входных параметров, определяется что записать в дескриптор и, наконец команда отправляется в очередь:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CCC_DESCRIPTOR_UUID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;00002902-0000-1000-8000-00805f9b34fb&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setNotify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                        &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Check if characteristic is valid&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: Characteristic is 'null', ignoring setNotify request&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Get the CCC Descriptor for the characteristic&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattDescriptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fromString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CCC_DESCRIPTOR_UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: Could not get CCC descriptor for characteristic %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Check if characteristic has NOTIFY or INDICATE properties and set the correct byte value to be written&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROPERTY_NOTIFY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ENABLE_NOTIFICATION_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROPERTY_INDICATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ENABLE_INDICATION_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: Characteristic %s does not have notify or indicate property&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finalValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;DISABLE_NOTIFICATION_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Queue Runnable to turn on/off the notification now that all checks have been passed&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commandQueue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// First set notification for Gatt object  if(!bluetoothGatt.setCharacteristicNotification(descriptor.getCharacteristic(), enable)) {&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: setCharacteristicNotification failed for descriptor: %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// Then write to descriptor&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finalValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;writeDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: writeDescriptor failed for descriptor: %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;completedCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;nrTries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nextCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: Could not enqueue write command&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Результат записи в CCC дескриптор обрабатывается в колбеке &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDescriptorWrite&lt;/code&gt;. Здесь вы должны отличить запись в CCC от записей в другие дескрипторы. Во время обработки колбека, мы также должны хранить, какие в данный момент характеристики уведомляются.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onDescriptorWrite&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattDescriptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Do some checks first&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parentCharacteristic&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCharacteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GATT_SUCCESS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR: Write descriptor failed value &amp;lt;%s&amp;gt;, device: %s, characteristic: %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes2String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentWriteBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parentCharacteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Check if this was the Client Configuration Descriptor  if(descriptor.getUuid().equals(UUID.fromString(CCC_DESCRIPTOR_UUID))) {&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GATT_SUCCESS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Check if we were turning notify on or off&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// Notify set to on, add it to the set of notifying characteristics          notifyingCharacteristics.add(parentCharacteristic.getUuid());&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// Notify was turned off, so remove it from the set of notifying characteristics               notifyingCharacteristics.remove(parentCharacteristic.getUuid());&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// This was a setNotify operation&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;....&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// This was a normal descriptor write....&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;completedCommand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Чтобы узнать из какой характеристики пришло уведомление – используйте метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isNotifying()&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isNotifying&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;notifyingCharacteristics&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;лимиты-на-установку-уведомлений&quot;&gt;Лимиты на установку уведомлений&lt;/h2&gt;
&lt;p&gt;К сожалению, нельзя включить столько уведомлений, сколько хочешь. Начиная с Android-5 лимит равен 15. В более старых версиях он был равен 7 или даже 4. Большинство смартфонов поддерживают 15 уведомлений. Не забывайте отключать их, если они вам больше не нужны, чтобы не исчерпать лимит.&lt;/p&gt;

&lt;h2 id=&quot;проблемы-с-потоками&quot;&gt;Проблемы с потоками&lt;/h2&gt;
&lt;p&gt;Итак, мы научились читать/писать характеристики, включать/выключать уведомления, а значит готовы использовать это в реальном проекте. Я думаю, что устройства BLE можно разделить на две категории:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Простые устройства&lt;/strong&gt;. Например, термометр, который использует официальный Bluetooth Health Thermometer сервис. Такие устройства легко использовать, вы просто включаете уведомления и данные начинают поступать. Здесь мы используем только операции чтения характеристики, запись не нужна;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Сложные устройства&lt;/strong&gt;. Это может быть любое устройство, но обычно все они используют свой внутренний протокол обмена данными. Часто эти протоколы не спроектированы под BLE, а просто транслируют внутренний последовательный протокол в BLE, где одна характеристика используется для отправки данных, а другая для приема. Сложность в том, что вам требуется знать большое количество команд для работы с устройством: авторизация, обновление пользовательских параметров, параметров самого устройства, получение сохраненных данных и т.д.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Простые устройства обычно не создают проблем с потоками, для сложных – следует работать внимательно. Чтение, запись и уведомления в этом случае будут чередоваться и могут мешать друг другу, особенно если у вас устройство с высокой частотой передачи данных (30Hz или около).&lt;/p&gt;

&lt;p&gt;Типичная проблема с потоками выглядит так:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;приходит уведомление&lt;/li&gt;
  &lt;li&gt;вы отправляете событие в свою собственную очередь для обработки&lt;/li&gt;
  &lt;li&gt;запускается обработку полученных данных&lt;/li&gt;
  &lt;li&gt;в это время приходит новое уведомление и перезаписывает предыдущее значение в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGattCharacteristic&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;если ваша обработка данных медленная, вы потенциально теряете значение из первого уведомления.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Причины такого поведения:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;как только сообщение доставлено, Android будет отправлять следующее&lt;/strong&gt; (если оно есть). Посколько обработка данных отправляется в другой поток, текущий освобождается и Android продолжит доставку уведомлений;&lt;/li&gt;
  &lt;li&gt;Android &lt;strong&gt;переиспользует BluetoothGattCharacteristic объекты внутри&lt;/strong&gt;. Они создаются в время обнаружения сервисов (services discovering) и после этого &lt;strong&gt;переспользуются&lt;/strong&gt; многократно. Таким образом, когда приходит уведомления Android сохраняет значение в объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGattCharacteristic&lt;/code&gt;. Если характеристика в этот момент обрабатывается в другом потоке мы получим гонку состояний (race condition) и результат будет непредсказуемым.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Очевидно, что &lt;strong&gt;нужно всегда работать с копией массива байтов&lt;/strong&gt;. Получили данные, сразу же делаем копию и работаем с ней.&lt;/p&gt;

&lt;p&gt;Ниже пример, который использует такую тактику:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onCharacteristicChanged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGattCharacteristic&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Copy the byte array so we have a threadsafe copy&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;arraycopy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; 
        &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Characteristic has new value so pass it on for processing&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bleHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;         
            &lt;span class=&quot;n&quot;&gt;myProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onCharacteristicUpdate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothPeripheral&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;characteristic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;другие-рекомендации-по-работе-с-потоками&quot;&gt;Другие рекомендации по работе с потоками&lt;/h2&gt;
&lt;p&gt;Есть несколько дополнительных рекомендаций по работе с BLE на Android. Поскольку стек BLE в основном асинхронный, у нас есть мульти-поточная обработка задач.&lt;/p&gt;

&lt;p&gt;Android использует потоки:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;При сканировании (результаты приходят в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; поток);&lt;/li&gt;
  &lt;li&gt;Вызове колбеков &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGattCallback&lt;/code&gt; (выполняются в потоках &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Binder&lt;/code&gt;);&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Обработка результатов сканирования на main потоке не будет проблемой. Но с потоками Binder все немного сложнее. При вызове колбека на потоке Binder, Android не будет отправлять новые данные пока не закончится обработка текущих, то есть поток Binder блокируется пока ваш код не завершится. Следует избегать тяжелых операций в колбеках, никаких &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sleep()&lt;/code&gt; или что-то подобное. Кроме того, никаких новых вызовов в объекте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt&lt;/code&gt;, пока вы находитесь в потоке Binder, хотя большинство методов асинхронные.&lt;/p&gt;

&lt;p&gt;Я рекомендую следующее:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Всегда выполняйте вызовы &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGattCallback&lt;/code&gt; в отдельном потоке,  возможно даже из потока пользовательского интерфейса (&lt;em&gt;Прим. переводчика: работать на main потоке - плохая идея, если у вас есть активный обмен с устройством, обязательно будут залипания UI, не делайте так)&lt;/em&gt;;&lt;/li&gt;
  &lt;li&gt;Освобождайте потоки &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Binder&lt;/code&gt; как можно быстрее и &lt;strong&gt;никогда&lt;/strong&gt; не блокируйте их;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Самый простой способ выполнить рекомендации выше – создать выделенный &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handler&lt;/code&gt; и использовать его для обработки данных и выдачи новых команд. Обратите внимание, я уже использовал &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handler&lt;/code&gt; на примере кода для колбека &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCharacteristicUpdate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Объявление объекта:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;Handler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bleHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Если хотите запустить &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handler&lt;/code&gt; на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; потоке:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;Handler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bleHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Looper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMainLooper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Прокрутите назад и взгляните на наш метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextCommand()&lt;/code&gt;, каждый &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Runnable&lt;/code&gt; выполняется в нашем собственном &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handler&lt;/code&gt;, следовательно, мы гарантируем, что все команды выполняются вне потока &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Binder&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;следующая-статья-сопряжение-bonding&quot;&gt;Следующая статья: сопряжение (bonding)&lt;/h2&gt;
&lt;p&gt;В этой статье мы разобрались с чтением и записью характеристик, включением и выключением уведомлений/нотификаций. &lt;a href=&quot;https://fmaxx.github.io/android/ble/bluetoothlowenergy/2020/01/25/android-ble-part-4.html&quot;&gt;В следующей статье&lt;/a&gt;, мы детально изучим процесс спряжения с устройством (bonding).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Не терпится поработать с BLE? Попробуйте &lt;a href=&quot;https://github.com/weliem/blessed-android&quot; target=&quot;_blank&quot;&gt;мою библиотеку Blessed for Android&lt;/a&gt;. Она использует все подходы из этой серии статей и упрощает работу с BLE в вашем приложении.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name></name></author><category term="Android" /><category term="BLE" /><category term="BluetoothLowEnergy" /><summary type="html">Перевод статьи Making Android BLE work — part 3. внимание: в цикле статей используется минимальная версия - Android 6</summary></entry><entry><title type="html">Android, работа с BLE - часть 2.</title><link href="/android/ble/bluetoothlowenergy/2020/01/10/android-ble-part-2.html" rel="alternate" type="text/html" title="Android, работа с BLE - часть 2." /><published>2020-01-10T09:00:00+00:00</published><updated>2020-01-10T09:00:00+00:00</updated><id>/android/ble/bluetoothlowenergy/2020/01/10/android-ble-part-2</id><content type="html" xml:base="/android/ble/bluetoothlowenergy/2020/01/10/android-ble-part-2.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://medium.com/@martijn.van.welie/making-android-ble-work-part-2-47a3cdaade07&quot; target=&quot;_blank&quot;&gt;Making Android BLE work — part 2&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;внимание: в цикле статей используется минимальная версия - Android 6&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;В &lt;a href=&quot;https://fmaxx.github.io/android/ble/bluetoothlowenergy/2019/11/12/android-ble-part-1.html&quot;&gt;предыдущей статье&lt;/a&gt; мы подробно рассмотрели сканирование устройств. Эта статья - о &lt;strong&gt;подключении&lt;/strong&gt;, &lt;strong&gt;отключении&lt;/strong&gt; и &lt;strong&gt;обнаружении сервисов&lt;/strong&gt; (discovering services).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-01-10-android-ble-part-2/1.jpeg&quot; alt=&quot;devices&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;подключение-к-устройству&quot;&gt;Подключение к устройству&lt;/h2&gt;
&lt;p&gt;После удачного сканирования, вы должны подключиться к устройству, вызывая метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt;. В результате мы получаем объект – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt&lt;/code&gt;, который будет использоваться для всех &lt;a href=&quot;https://developer.android.com/reference/android/bluetooth/BluetoothGatt&quot; target=&quot;_blank&quot;&gt;GATT операций&lt;/a&gt;, такие как чтение и запись характеристик. Однако будьте внимательны, есть две версии метода &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt;. Поздние версии Android имеют еще несколько вариантов, но нам нужна совместимость с Android-6 и мы рассматриваем только эти две:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;connectGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;autoConnect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;BluetoothGattCallback&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;connectGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;autoConnect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;BluetoothGattCallback&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Внутренняя реализация первой версии – это фактически вызов второй версии с аргументом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transport = TRANSPORT_AUTO&lt;/code&gt;. Для подключения BLE устройств такой вариант не подходит. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TRANSPORT_AUTO&lt;/code&gt; используется для устройств с поддержкой и BLE и классического Bluetooth протоколов. Это значит, что Android будет сам выбирать протокол подключения. Этот момент практически нигде не описан и может привести к непредсказуемым результатам, много людей сталкивались с такой проблемой. Вот почему вы должны использовать вторую версию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt; с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transport = TRANSPORT_LE&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;connectGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;bluetoothGattCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TRANSPORT_LE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Первый аргумент – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context&lt;/code&gt; приложения.
Второй аргумент – флаг &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect&lt;/code&gt;, говорит подключаться немедленно (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;) или нет (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;). При немедленном подключении (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;) Android будет пытаться соединиться в течение 30 секунд (на большинстве смартфонов), по истечении этого времени придет статус соединения &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status_code = 133&lt;/code&gt;. Это не официальная ошибка для таймаута соединения. В исходниках Android код фигурирует как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_ERROR&lt;/code&gt;. К сожалению, эта ошибка появляется и в других случаях. Имейте ввиду, с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = false&lt;/code&gt; Android делает соединение только с одним устройством в одно и то же время (это значит если у вас несколько устройств - подключайте их последовательно, а не паралелльно).
Третий аргумент – функция обратного вызова &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGattCallback&lt;/code&gt; (callback) для конкретного устройства. Этот колбек используется для всех связанных с устройством операциях, такие как чтение и запись. Мы рассмотрим это более детально в следующей статье.&lt;/p&gt;

&lt;h2 id=&quot;autoconnect--true&quot;&gt;Autoconnect = true&lt;/h2&gt;
&lt;p&gt;Если вы установите &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = true&lt;/code&gt;, Android будет подключаться самостоятельно к устройству всякий раз, когда оно будет обнаружено. Внутри это работает так: Bluetooth стек сканирует сохраненные устройства и когда увидит одно из них – подключается к нему. Это довольно удобно, если вы хотите подключиться к конкретному устройству, когда оно становится доступным. Фактически, это предпочтительный способ для переподключения. Вы просто создаете &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothDevice&lt;/code&gt; объект и вызываете &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGattwith&lt;/code&gt; с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = true&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothDevice&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
&lt;span class=&quot;n&quot;&gt;bluetoothAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRemoteDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;12:34:56:AA:BB:CC&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;connectGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bluetoothGattCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TRANSPORT_LE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Обратите внимание, этот подход работает только, если устройство есть в Bluetooth кеше или устройство было уже сопряжено (bonding). Посмотрите мою &lt;a href=&quot;https://fmaxx.github.io/android/ble/bluetoothlowenergy/2019/11/12/android-ble-part-1.html&quot; target=&quot;_blank&quot;&gt;предыдущую статью&lt;/a&gt;, где подробно объясняется работа с Bluetooth кешем.
При перезагрузке смартфона или выключении/включении Bluetooth (а также Airplane режима) – кеш очистится, это надо проверять перед подключением с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = true&lt;/code&gt;, что действительно раздражает.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Autoconnect работает только с закешированными и сопряженными (bonded) устройствами!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Для того, чтобы узнать, закешировано устройство или нет, можно использовать небольшой трюк. После создания объекта &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothDevice&lt;/code&gt;, вызовите у него &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getType&lt;/code&gt;, если результат – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TYPE_UNKNOWN&lt;/code&gt;, значит устройство не закешировано. В этом случае, необходимо просканировать устройство с этим мак-адресом (используя не агрессивный метод сканирования) и после этого можно использовать автоподключение снова.&lt;/p&gt;

&lt;p&gt;Android-6 и ниже имеет известный баг, в котором возникает гонка состояний и автоматическое подключение становится обычным (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = false&lt;/code&gt;). К счастью, умные ребята из Polidea нашли &lt;a href=&quot;https://github.com/Polidea/RxAndroidBle/blob/7663a1ab96605dc26eba378a9e51747ad254b229/rxandroidble/src/main/java/com/polidea/rxandroidble2/internal/util/BleConnectionCompat.java&quot; target=&quot;_blank&quot;&gt;решение для этого&lt;/a&gt;. Настоятельно рекомендуется использовать его, если думаете использовать автоподключение.&lt;/p&gt;

&lt;p&gt;Преимущества:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;работает достаточно хорошо на современных версиях Android (прим. переводчика - от Android-8 и выше);&lt;/li&gt;
  &lt;li&gt;возможность подключаться к нескольким устройствам одновременно;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Недостатки:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;работает медленнее (Android в этом случае сканирует в режиме &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCAN_MODE_LOW_POWER&lt;/code&gt;, экономя энергию), если сравнивать сканирование в агрессивном режиме + подключение с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = false&lt;/code&gt;;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;изменения-статуса-подключения&quot;&gt;Изменения статуса подключения&lt;/h2&gt;
&lt;p&gt;После вызова &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt;, Bluetooth стек присылает результат в колбек &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChange&lt;/code&gt;, он вызывается при любом изменении соединения.&lt;/p&gt;

&lt;p&gt;Работа с этим колбеком – достаточно нетривиальная вещь. Большинство простых примеров из сети выглядит так (не обольщайтесь):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onConnectionStateChange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothProfile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;STATE_CONNECTED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;discoverServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Этот код обрабатывает только аргумент &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newState&lt;/code&gt; и полностью игнорирует &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;. В многих случаях это работает и кажется безошибочным. Действительно, после подключения, следующее что нужно сделать – это вызвать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt;. А в случае отключения - необходимо сделать вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;, чтобы Android освободил все связанные ресурсы в стеке Bluetooth. Эти два момента очень важные для стабильной работы BLE под Android, давайте их обсудим прямо сейчас!&lt;/p&gt;

&lt;p&gt;При вызове &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt;, Bluetooth стек регистрирует внутри себя интерфейс для нового клиента (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client interface: clientIf&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Возможно вы заметили такие логи в LogCat:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;D/BluetoothGatt: connect() - device: B0:49:5F:01:20:XX, auto: false
D/BluetoothGatt: registerApp()
D/BluetoothGatt: registerApp() — UUID=0e47c0cf-ef13–4afb-9f54–8cf3e9e808d5
D/BluetoothGatt: onClientRegistered() — status=0 clientIf=6&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Здесь видно, что клиент &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6&lt;/code&gt; был зарегистрирован после вызова &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt;. Максимальное количество клиентов (подключения) у Android равно 30 (константа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_MAX_APPS&lt;/code&gt; в исходниках), при достижении которого – Android не будет подключаться к устройствам вообще и вы будете получать постоянно ошибку подключения. Достаточно странно, но сразу после загрузки Android уже имеет 5 или 6 таких подключенных клиентов, предполагаю, что Android использует их для внутренних нужд. Таким образом, если вы не вызываете метод &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;, то счетчик клиентов увеличивается каждый раз при вызове &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt;. Когда вы вызываете &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;, Bluetooth стек удаляет ваш колбек, счетчик клиентов уменьшается на единицу и освобождает ресурсы клиента.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;D/BluetoothGatt: close()
D/BluetoothGatt: unregisterApp() — mClientIf=6&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Важно всегда вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; после отключения! А сейчас обсудим основные случаи дисконнекта устройств.&lt;/p&gt;

&lt;h2 id=&quot;состояние-подключения-newstate&quot;&gt;Состояние подключения (newState)&lt;/h2&gt;
&lt;p&gt;Переменная &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newState&lt;/code&gt; содержит новое состояние подключения и может иметь 4 значения:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE_CONNECTED&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE_DISCONNECTED&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE_CONNECTING&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE_DISCONNECTING&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Значения говорят сами за себя. Хотя состояния &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE_CONNECTING&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE_DISCONNECTING&lt;/code&gt; на практике я их не встречал. Так что, в принципе, можно не обрабатывать их, но для уверенности, я предлагаю их явно учитывать (&lt;em&gt;прим. переводчика - и это лучше, чем не обрабатывать их.&lt;/em&gt;), вызывая &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; только в том случае если устройство действительно отключено.&lt;/p&gt;

&lt;h2 id=&quot;статус-подключения-status&quot;&gt;Статус подключения (status)&lt;/h2&gt;
&lt;p&gt;В примере выше, переменная статуса &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt; полностью игнорировалась, но в действительности обрабатывать ее важно. Эта переменная, по сути, является кодом ошибки. Вы можете получить &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_SUCCESS&lt;/code&gt; в результате как подключения, так и контролируемого отключения. Таким образом, мы можем по-разному обрабатывать контролируемое или внезапное отключение устройства. 
Если вы получили значение отличное от &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_SUCCESS&lt;/code&gt;, значит «что-то пошло не так» и в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt; будет указана причина. К сожалению, объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt&lt;/code&gt; дает очень мало кодов ошибок, все они описаны &lt;a href=&quot;https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/5738f83aeb59361a0a2eda2460113f6dc9194271/stack/include/gatt_api.h&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;. Чаще всего вы будете встречаться с кодом 133 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_ERROR&lt;/code&gt;). Который не имеет точного описания, и просто говорит – “произошла какая-то ошибка”. Не очень информативно, подробнее об &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_ERROR&lt;/code&gt; позже.&lt;/p&gt;

&lt;p&gt;Теперь мы знаем, что обозначают переменные &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newState&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;, давайте улучшим наш колбек &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChange&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onConnectionStateChange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothGatt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
                                    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GATT_SUCCESS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothProfile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;STATE_CONNECTED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Мы подключились, можно запускать обнаружение сервисов&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;discoverServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothProfile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;STATE_DISCONNECTED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Мы успешно отключились (контролируемое отключение)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// мы или подключаемся или отключаемся, просто игнорируем эти статусы&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;c1&quot;&gt;// Произошла ошибка... разбираемся, что случилось!&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Это не последний вариант, мы еще улучшим колбек в этой статье. В любом случае, теперь у нас есть обработка ошибок и успешных операций.&lt;/p&gt;

&lt;h2 id=&quot;состояние-bonding-bondstate&quot;&gt;Состояние bonding (bondState)&lt;/h2&gt;
&lt;p&gt;Последний параметр, который необходимо учитывать в колбеке &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChange&lt;/code&gt; – это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bondState&lt;/code&gt;, состояние сопряжения (bonding) с устройством. Мы получаем этот параметр так:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBondState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Состояние bonding может иметь одно из трех значений &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOND_NONE&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOND_BONDING&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOND_BONDED&lt;/code&gt;. Каждое из них влияет на то, как обрабатывать подключение.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOND_NONE&lt;/code&gt;, нет проблем, можно вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOND_BONDING&lt;/code&gt;, устройство в процессе сопряжения, нельзя вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt;, так как Bluetooth стек в работе и запуск &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt; может прервать сопряжение и вызвать ошибку соединения. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt; вызываем только после того, как пройдет сопряжение (bonding);&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOND_BONDED&lt;/code&gt;, для Android-8 и выше, можно запускать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt; без задержки. Для версий 7 и ниже может потребоваться задержка перед вызовом. Если ваше устройство имеет &lt;strong&gt;Service Changed Characteristic&lt;/strong&gt;, то Bluetooth стек в этот момент еще обрабатывает их и запуск &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt; без задержки может вызвать ошибку соединения. Добавьте 1000-1500мс задержки, конкретное значение зависит от количества характеристик на устройстве. Используйте задержку всегда, если вы не знаете сколько &lt;strong&gt;Service Changed Characteristic&lt;/strong&gt; имеет устройство.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Теперь мы можем учитывать состояние &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bondState&lt;/code&gt; вместе с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newState&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GATT_SUCCESS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothProfile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;STATE_CONNECTED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBondState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Обрабатываем bondState&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BOND_NONE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BOND_BONDED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Подключились к устройству, вызываем discoverServices с задержкой&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delayWhenBonded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SDK_INT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VERSION_CODES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;delayWhenBonded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BOND_BONDED&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delayWhenBonded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;discoverServicesRunnable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ENGLISH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;discovering services of '%s' with delay of %d ms&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
                    &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;discoverServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;discoverServices failed to start&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;discoverServicesRunnable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;bleHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;postDelayed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;discoverServicesRunnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bondstate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BOND_BONDING&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Bonding в процессе, ждем когда закончится&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;waiting for bonding to complete&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;....&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;обработка-ошибок&quot;&gt;Обработка ошибок&lt;/h2&gt;
&lt;p&gt;После того как мы разобрались с успешными операциями, давайте взглянем на ошибки. Есть ряд ситуаций, которые на самом деле “нормальные”, но выдают себя за ошибки.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Устройство отключилось намеренно&lt;/strong&gt;. Например, все данные были переданы и больше ему нечего делать. Вы получите статус - 19 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_CONN_TERMINATE_PEER_USER&lt;/code&gt;);&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Истекло время ожидания соединения  и устройство отключилось само&lt;/strong&gt;. В этом случае придет статус - 8 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_CONN_TIMEOUT&lt;/code&gt;);&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Низкоуровневая ошибка соединения, которая привела к отключению&lt;/strong&gt;. Обычно это статус - 133 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_ERROR&lt;/code&gt;) или более конкретный код, если повезет;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Bluetooth стек не смог подключится ни разу&lt;/strong&gt;. Здесь также получим статус - 133 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_ERROR&lt;/code&gt;);&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Соединение было потеряно в процессе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bonding&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices&lt;/code&gt;&lt;/strong&gt;. Необходимо выяснить причину и возможно повторить попытку подключения.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Первые два случая абсолютно нормальные явления и все что нужно сделать - это вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; и подчистить ссылки на объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt&lt;/code&gt;, если необходимо.
В остальных случаях, либо ваш код, либо устройство, что-то делает не так. Вы возможно захотите уведомить UI или другие части приложения о проблеме, повторить подключение или еще каким-то образом отреагировать на ситуацию.
Взгляните как я сделал это в &lt;a href=&quot;https://github.com/weliem/blessed-android/blob/master/blessed/src/main/java/com/welie/blessed/BluetoothPeripheral.java#L234&quot; target=&quot;_blank&quot;&gt;моей библиотеке&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;статус-133-при-подключении-connecting&quot;&gt;Статус 133 при подключении (connecting)&lt;/h2&gt;
&lt;p&gt;Статус - 133 часто встречается при попытках подключиться к устройству, особенно во время разработки. Этот статус может иметь множество причин, некоторые из них можно контролировать:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Убедитесь, что вы всегда вызываете &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; при отключении. Если этого не сделать, в следующий раз при подключении вы точно получите &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status=133&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Всегда используйте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TRANSPORT_LE&lt;/code&gt; в вызове &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Перезагрузите смартфон. Возможно Bluetooth стек выбрал лимит по клиентским подключениям или есть внутренняя проблема. (&lt;em&gt;Прим. переводчика: я сначала выключал/включал Bluetooth, потом Airplane режим и если не помогало - перезагружал&lt;/em&gt;);&lt;/li&gt;
  &lt;li&gt;Проверьте что устройство посылает advertising пакеты. Вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt; с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = false&lt;/code&gt; имеет таймаут 30 секунд, после чего присылает ошибку &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status=133&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Замените/зарядите батарею на устройстве. Обычно устройства работают нестабильно при низком заряде;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если вы попробовали все способы выше и все еще получаете статус 133, необходимо просто &lt;strong&gt;повторить подключение&lt;/strong&gt;! Это одна из Android ошибок, которую мне так и не удалось понять или решить. Иногда вы получаете 133 при подключении к устройству, но если вызывать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; и переподключиться, то все работает без проблем! Есть подозрение, что проблема в кеше Android и вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; сбрасывает его состояние для конкретного устройства. Если кто-нибудь поймет, как решить эту проблему – дайте мне знать!&lt;/p&gt;

&lt;h2 id=&quot;отключение-по-запросу-disconnect&quot;&gt;Отключение по запросу (disconnect)&lt;/h2&gt;
&lt;p&gt;Для отключения устройства вам необходимо сделать шаги:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;вызвать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnect()&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;подождать обновления статуса в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChange&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;вызвать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;освободить связанные с объектом gatt ресурсы;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Команда &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnect()&lt;/code&gt; фактически разрывает соединение с устройством и обновляет внутреннее состояние Bluetooth стека. Затем вызывается колбек &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChange&lt;/code&gt; с новым состоянием «disconnected».&lt;/p&gt;

&lt;p&gt;Вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; удаляет ваш  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGattCallback&lt;/code&gt;  и освобождает клиента в Bluetooth стеке.&lt;/p&gt;

&lt;p&gt;Наконец, удаление &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothGatt&lt;/code&gt; освободит все связанные с подключением ресурсы.&lt;/p&gt;

&lt;h2 id=&quot;отключение-неправильно&quot;&gt;Отключение «неправильно»&lt;/h2&gt;
&lt;p&gt;В примерах из сети можно увидеть, разные примеры отключения, например:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;вызвать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnect()&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;сразу&lt;/strong&gt; вызвать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Это будет работать более-менее. Да устройство отключится, но вы никогда не получите вызов колбека с состоянием «disconnected». Дело в том, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnect()&lt;/code&gt; операция асинхронная (не блокирует поток и имеет свое время выполнения), а &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; немедленно удаляет коллбек! Получается, когда Android будет готов вызвать колбек, его уже не будет.&lt;/p&gt;

&lt;p&gt;Иногда в примерах не вызывают &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnect()&lt;/code&gt;, а только &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;. Это приведет к отключению устройства, но это неправильный способ, поскольку &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnect()&lt;/code&gt; отключает активное соединение и отменяет ожидающее автоматическое подключение (вызов с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = true&lt;/code&gt;). Поэтому, если вы вызываете только &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;, любое ожидающее автоподключение может привести к новому подключению.&lt;/p&gt;

&lt;h2 id=&quot;отмена-попытки-подключения&quot;&gt;Отмена попытки подключения&lt;/h2&gt;
&lt;p&gt;Если вы хотите отменить подключение после &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connectGatt()&lt;/code&gt;, вам нужно вызвать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnect()&lt;/code&gt;. Так как в этому моменту вы еще не подключены, колбек &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChange&lt;/code&gt; не сработает! Просто подождите некоторое время после &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnect()&lt;/code&gt; и после этого вызывайте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt; (&lt;em&gt;прим. переводчика: обычно это 50-100мс&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;При удачной отмене вы увидите примерно такое в логах:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;D/BluetoothGatt: cancelOpen() — device: CF:A9:BA:D9:62:9E&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Скорее всего, вы никогда не отмените соединение, для параметра &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = false&lt;/code&gt;. Часто это делается для подключений с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = true&lt;/code&gt;. Например, когда приложение на переднем плане – вы подключаетесь к вашим устройствам и отключаетесь от них, если приложение переходит в фон.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Прим. переводчика: но это не значит что для &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoconnect = false&lt;/code&gt; не надо проводить такую отмену! Скорее всего вы не увидите этого в логах.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;обнаружение-сервисов-discovering-services&quot;&gt;Обнаружение сервисов (discovering services)&lt;/h2&gt;
&lt;p&gt;Как только вы подключились к устройству, необходимо запустить обнаружение его сервисов вызовом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverServices()&lt;/code&gt;. Bluetooth стек запустит серию низкоуровневых команд для получения &lt;strong&gt;сервисов&lt;/strong&gt;, &lt;strong&gt;характеристик&lt;/strong&gt; и &lt;strong&gt;дескрипторов&lt;/strong&gt;. Это занимает обычно около одной секунды в зависимости от того сколько таких служб, характеристик, дескрипторов имеет ваше устройство. В результате будет вызыван колбек &lt;strong&gt;onServicesDiscovered&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Первым делом проверим, есть ли какие ошибки после обнаружения сервисов:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Проверяем есть ли ошибки? Если да - отключаемся&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GATT_INTERNAL_ERROR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Service discovery failed&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Если есть ошибки (обычно это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GATT_INTERNAL_ERROR&lt;/code&gt; со значением 129), делаем отключение устройства, что-то исправить здесь невозможно (нет специальных технических способов для этого). Вы просто отключаете устройство и повторно пробуете подключиться.&lt;/p&gt;

&lt;p&gt;Если все прошло удачно, вы получите список сервисов:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothGattService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ENGLISH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;discovered %d services for '%s'&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Работа со списком сервисов (если требуется)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;кеширование-сервисов&quot;&gt;Кеширование сервисов.&lt;/h2&gt;
&lt;p&gt;Bluetooth стек кеширует найденные на устройстве сервисы, характеристики и дескрипторы. Первое подключение вызывает реальное обнаружение сервисов, все последующие – возвращаются кешированные версии. Это соответствует стандарту Bluetooth. Обычно это нормально и сокращает время соединения с устройством.
Однако в некоторых случаях, может потребоваться очистить кеш, чтобы снова обнаружить их с устройства при следующем соединении. Типичный сценарий: обновление прошивки, в которой изменяется набор сервисов, характеристик, дескрипторов. Есть  скрытый метод очистки кеша и добраться до него нам поможет рефлексия:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;clearServicesCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Method&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refreshMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;refresh&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;refreshMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refreshMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothGatt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ERROR: Could not invoke refresh method&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Этот метод асинхронный, дайте ему некоторое время для завершения!&lt;/p&gt;

&lt;h2 id=&quot;странные-штуки-в-подключенииотключении&quot;&gt;Странные штуки в подключении/отключении&lt;/h2&gt;
&lt;p&gt;Хотя операции подключения и отключения выглядят просто, есть некоторые особенности, которые нужно знать.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Случайная ошибка 133 при подключении, выше мы разобрались как с ней работать;&lt;/li&gt;
  &lt;li&gt;Периодическое зависание подключения, не срабатывает таймаут и не вызывается колбек &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onConnectionStateChange&lt;/code&gt;. Это случается не часто, но я видел такие случае при низком уровне батареи или когда устройство находится на границе доступности по расстоянию Bluetooth. Скорее всего общение с устройством происходит, но затем прерывается и зависает. Мой обходной путь – использовать свой таймер подключения и в случае таймаута – закрывать соединение и отключаться;&lt;/li&gt;
  &lt;li&gt;Некоторые смартфоны имеют проблему с подключением во время сканирования. Например, Huawei P8 Lite один из таких. Останавливаем сканнер перед любым подключением (&lt;em&gt;Прим. переводчика: это правило соблюдаем строго!&lt;/em&gt;);&lt;/li&gt;
  &lt;li&gt;Все вызовы подключения/отключения асинхронные. То есть неблокирующие, но при этом им нужно время, чтобы выполнится до конца. Избегайте быстрый запуск их друг за другом (&lt;em&gt;Прим. переводчика: я обычно использую задержку 50-100мс между вызовами&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;следующая-статья-чтение-и-запись-характеристик&quot;&gt;&lt;a href=&quot;https://fmaxx.github.io/android/ble/bluetoothlowenergy/2020/01/18/android-ble-part-3.html&quot;&gt;Следующая статья: чтение и запись характеристик&lt;/a&gt;.&lt;/h3&gt;
&lt;p&gt;Теперь мы разобрались с подключением/отключением и обнаружением сервисов, &lt;a href=&quot;https://fmaxx.github.io/android/ble/bluetoothlowenergy/2020/01/18/android-ble-part-3.html&quot;&gt;следующая статья – о том, как работать с характеристиками&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Не терпится поработать с BLE? Попробуйте &lt;a href=&quot;https://github.com/weliem/blessed-android&quot; target=&quot;_blank&quot;&gt;мою библиотеку Blessed for Android&lt;/a&gt;. Она использует все подходы из этой серии статей и упрощает работу с BLE в вашем приложении.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name></name></author><category term="Android" /><category term="BLE" /><category term="BluetoothLowEnergy" /><summary type="html">Перевод статьи Making Android BLE work — part 2. внимание: в цикле статей используется минимальная версия - Android 6</summary></entry><entry><title type="html">Android, работа с BLE - часть 1.</title><link href="/android/ble/bluetoothlowenergy/2019/11/12/android-ble-part-1.html" rel="alternate" type="text/html" title="Android, работа с BLE - часть 1." /><published>2019-11-12T09:00:00+00:00</published><updated>2019-11-12T09:00:00+00:00</updated><id>/android/ble/bluetoothlowenergy/2019/11/12/android-ble-part-1</id><content type="html" xml:base="/android/ble/bluetoothlowenergy/2019/11/12/android-ble-part-1.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;https://medium.com/@martijn.van.welie/making-android-ble-work-part-1-a736dcd53b02&quot;&gt;Making Android BLE work — part 1&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;внимание: в цикле статей используется минимальная версия - Android 6&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;В последний год я разрабатывал Bluetooth Low Energy (BLE) приложения под iOS и это оказалось довольно простым. Далее было портирование их на Android… насколько это могло быть сложным?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-10-30-android-ble-part-1/1.jpg&quot; alt=&quot;devices&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Могу точно сказать это было сложней, чем я представлял, пришлось приложить немало усилий для стабильной работы под Android. Я изучил много статей в свободном доступе, некоторые оказались ошибочными, многие были очень полезными и помогли в деле.
В этой серии статей я хочу описать свои выводы, чтобы вы не тратили уйму времени на поиски, как я.&lt;/p&gt;

&lt;h2 id=&quot;особенности-работы-ble-под-android&quot;&gt;Особенности работы BLE под Android:&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Google документация по BLE очень общая&lt;/strong&gt;, в некоторых случаях нет важной информации или она устарела, примеры приложений не показывают как правильно использовать BLE. Я обнаружил лишь несколько источников, как правильно сделать BLE. 
&lt;a href=&quot;https://www.stkent.com/2017/09/18/ble-on-android.html&quot; target=&quot;_blank&quot;&gt;Презентация Stuart Kent&lt;/a&gt; дает замечательный материал для старта. Для некоторых продвинутых тем есть хорошая статья &lt;a href=&quot;https://devzone.nordicsemi.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-00-04-DZ-1046/2604.BLE_5F00_on_5F00_Android_5F00_v1.0.1.pdf&quot; target=&quot;_blank&quot;&gt;Nordic&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Android BLE API это низкоуровневые операции&lt;/strong&gt;, в реальных приложениях нужно использовать несколько слоев абстракции (как например сделано “из коробки” в iOS-CoreBluetooth). Обычно нужно самостоятельно сделать: очередь команд, bonding, обслуживание соединений, обработка ошибок и багов, мультипоточный доступ . Самые известные библиотеки: &lt;a href=&quot;https://github.com/iDevicesInc/SweetBlue&quot; target=&quot;_blank&quot;&gt;SweetBlue&lt;/a&gt;, &lt;a href=&quot;https://github.com/Polidea/RxAndroidBle&quot; target=&quot;_blank&quot;&gt;RxAndroidBle&lt;/a&gt; и &lt;a href=&quot;https://github.com/NordicSemiconductor/Android-BLE-Library&quot; target=&quot;_blank&quot;&gt;Nordic&lt;/a&gt;. На мой взгляд самая легкая для изучения - Nordic, &lt;a href=&quot;https://github.com/weliem/blessed-android&quot; target=&quot;_blank&quot;&gt;см. детали тут&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Производители делают изменения в Android BLE стеке&lt;/strong&gt; или полностью заменяют на свою реализацию. И надо учитывать разницу поведения для разных устройств в приложении. То что прекрасно работает на одном телефоне, может не работать на других! В целом не все так плохо, например реализация Samsung сделана лучше собственной реализации от Google!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;В Android есть несколько известных (и неизвестных) багов&lt;/strong&gt; которые должны быть обработаны, особенно в версиях 4,5 и 6. Более поздние версии работают намного лучше, но тоже имеют определенные проблемы, такие как случайные сбои соединения с ошибкой 133. Подробнее об этом ниже.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Не претендую на то, что я решил все проблемы, но мне удалось выйти на “приемлемый” уровень. Начнем со сканирования.&lt;/p&gt;

&lt;h2 id=&quot;сканирование-устройств&quot;&gt;Сканирование устройств&lt;/h2&gt;
&lt;p&gt;Перед подключением к устройству вам нужно его просканировать. Это делается при помощи класса &lt;a href=&quot;https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothLeScanner&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothAdapter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDefaultAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;BluetoothLeScanner&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBluetoothLeScanner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scanner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scanner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startScan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;scan started&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TAG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;could not get scanner object&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Сканер пытается обнаружить устройства в соответствии с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filters&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scanSettings&lt;/code&gt; и при нахождении устройства вызывается &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scanCallback&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScanCallback&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanCallback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScanCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onScanResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callbackType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScanResult&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;BluetoothDevice&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...do whatever you want with this found device&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onBatchScanResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScanResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Ignore for now&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onScanFailed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errorCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Ignore for now&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;В результате сканирования &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScanResult&lt;/code&gt; есть объект &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BluetoothDevice&lt;/code&gt;, его используют для подключения к устройству. Прежде чем начать подключаться, давайте поговорим о сканировании подробнее, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScanResult&lt;/code&gt; содержит несколько полезных сведений об устройстве:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Advertisement data&lt;/strong&gt; - массив байтов с информацией об устройстве, для большинства устройств это имя и UUIDы сервисов, можно задать в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filters&lt;/code&gt; имя устройства и UUID сервисов для поиска конкретных устройств.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;RSSI уровень&lt;/strong&gt; - уровень сигнала (насколько близко устройство).&lt;/li&gt;
  &lt;li&gt;… дополнительные данные, см. документацию по &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScanResult&lt;/code&gt; &lt;a href=&quot;https://developer.android.com/reference/android/net/wifi/ScanResult&quot;&gt;здесь&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;Не забывайте про жизненный цикл &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onScanResult&lt;/code&gt; может вызываться многократно для одних и тех же устройств, при пересоздании &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; сканирование может запускаться повторно, вызываю лавину вызовов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onScanResult&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;настраиваем-фильтр-для-сканирования&quot;&gt;Настраиваем фильтр для сканирования&lt;/h2&gt;
&lt;p&gt;Вообще можно передать null вместо фильтров и получить все ближайшие устройства, иногда это полезно, но чаще требуются устройства с определенным именем или набором UUID сервисов.&lt;/p&gt;

&lt;h1 id=&quot;сканирование-устройств-по-uuid-сервиса&quot;&gt;Сканирование устройств по UUID сервиса&lt;/h1&gt;
&lt;p&gt;Используется если вам необходимо найти устройства определенной категории, например мониторы артериального давления со стандартным сервисным UUID: 1810. При сканировании устройство может содержать в &lt;em&gt;Advertisement data&lt;/em&gt; UUID сервис, который характеризует это устройство. На самом деле эти данные ненадежные, фактически сервисы могут не поддерживаться, или подделываеться &lt;em&gt;Advertisement data&lt;/em&gt; данные, в общем тут есть творческий момент.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Прим. перевочика: одно из моих устройств со специфичной прошивкой, вообще не содержало список UUID сервисов в &lt;em&gt;Advertisement data&lt;/em&gt;, хотя все остальные прошивки работали ок.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Пример сканирования службы с артериальным давлением:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BLP_SERVICE_UUID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fromString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;00001810-0000-1000-8000-00805f9b34fb&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serviceUUIDs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BLP_SERVICE_UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serviceUUIDs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serviceUUID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serviceUUIDs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setServiceUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ParcelUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serviceUUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;scanner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startScan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Обратите внимание, короткий UUID (например &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1810&lt;/code&gt;), называется &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16-bit UUID&lt;/code&gt; является частью длинного &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;128-bit UUID&lt;/code&gt; (в данном случае &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;00001810-000000-1000-8000-000-00805f9b34fb&lt;/code&gt;). Короткий UUID это BASE_PART длинного UUID, см. спецификацию &lt;a href=&quot;https://www.bluetooth.com/specifications/assigned-numbers/service-discovery&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;сканирование-устройств-по-имени&quot;&gt;Сканирование устройств по имени&lt;/h1&gt;
&lt;p&gt;Поиск устройств использует точное совпадение имени устройства, обычно это применяется в двух случаях:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;поиск конкретного устройства&lt;/li&gt;
  &lt;li&gt;поиск конкретной модели устройста
Например мой нагрудный напульсник Polar H7 определяется как “Polar H7 391BBB014”, первая часть - “Polar H7” общая для всех таких устройств этой модели, а последняя часть “391BBB014” - уникальный серийный номер. Это очень распространненая практика. Если вы хотите найти все устройства “Polar H7”, то фильтр по имени вам не поможет, придется искать подстроку у всех отсканированных устройств в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScanResult&lt;/code&gt;.
Пример с поиском &lt;strong&gt;точно&lt;/strong&gt; по имени:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;names&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Polar H7 391BB014&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;names&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setDeviceName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;scanner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startScan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;сканирование-устройств-по-mac-адресам&quot;&gt;Сканирование устройств по MAC-адресам.&lt;/h1&gt;
&lt;p&gt;Обычно применяется для &lt;strong&gt;переподключения&lt;/strong&gt; к уже известным устройствам. Обычно мы не знаем MAC-адрес девайса, если не сканировали его раньше, иногда адрес печатается на коробке или на корпусе самого устройства, особенно это касается медицинских приборов. Существует другой способ повторного подключения, но в некоторых случаях придется еще раз сканировать устройство, например при очистке кеша Bluetooth.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;peripheralAddresses&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;01:0A:5C:7D:D0:1A&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Build filters list&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;peripheralAddresses&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;peripheralAddresses&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScanFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setDeviceAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;scanner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startScan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanByServiceUUIDCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Вероятно вы уже поняли, что можно комбинировать в фильтре UUID, имя и MAC-адрес устройства. Выглядит неплохо, но на практике я не применял такое. Хотя может быть вам это пригодится?&lt;/p&gt;

&lt;h2 id=&quot;настройка-scansettings&quot;&gt;Настройка ScanSettings&lt;/h2&gt;
&lt;p&gt;ScanSettings объясняют Android как он должен сканировать устройства. Там несколько настроек, которые можно задать, ниже полный пример:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;ScanSettings&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scanSettings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setScanMode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SCAN_MODE_LOW_POWER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCallbackType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;CALLBACK_TYPE_ALL_MATCHES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setMatchMode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MATCH_MODE_AGGRESSIVE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setNumOfMatches&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ScanSettings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MATCH_NUM_ONE_ADVERTISEMENT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setReportDelay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;scanmode&quot;&gt;&lt;strong&gt;ScanMode&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Безусловно, это самый важный параметр. Определяет метод и время сканирования в Bluetooth стеке. Такая операция требует много энергии и необходим контроль над этим процессом, чтобы не разрядить батарею телефона быстро.
Есть 4 режима работы, в соответствии с руководством &lt;a href=&quot;https://devzone.nordicsemi.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-00-04-DZ-1046/2604.BLE_5F00_on_5F00_Android_5F00_v1.0.1.pdf&quot; target=&quot;_blank&quot;&gt;Nordics&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCAN_MODE_LOW_POWER&lt;/code&gt;. В этом режиме Android сканирует 0.5с, потом делает паузу на 4.5с. Поиск может занять относительно длительное время, зависит от того насколько часто устройство посылает пакет advertisement данных.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCAN_MODE_BALANCED&lt;/code&gt;. Время сканирования: 2с, время паузы: 3с, “компромисный” режим работы.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCAN_MODE_LOW_LATENCY&lt;/code&gt;. В этом случае, Android сканирует непрерывно, что очевидно требует больше энергозатрат, при этом получаются лучшие результаты сканирования. Режим подходит если вы хотите найти свое устройство как можно быстрее. Не стоит использовать для длительного сканирования.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCAN_MODE_OPPORTUNISTIC&lt;/code&gt;. Результаты будут получены, если сканирование выполняется другими приложениями! Строго говоря, это вообще не гарантирует, что обнаружится ваше устройство. Стек Android использует этот режим в случае долгого сканирования, для понижения качества результатов (см. ниже “Непрерывное сканирование”).&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;callback-type&quot;&gt;&lt;strong&gt;Callback Type&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Настройка контролирует как будет вызываться callback со &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScanResult&lt;/code&gt; в соответствии с заданными фильтрами, есть 3 варианта:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CALLBACK_TYPE_ALL_MATCHES&lt;/code&gt;. Callback будет вызывать каждый раз, при получении advertisement пакета от устройств. На практике - каждые 200-500мс будет срабатывать сallback, в зависимости от частоты отправки advertisement пакетов устройствами.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CALLBACK_TYPE_FIRST_MATCH&lt;/code&gt;. Callback сработает один раз для устройства, даже если оно далее будет снова посылать advertisement пакеты.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CALLBACK_TYPE_MATCH_LOST&lt;/code&gt;. Callback будет вызыван если получен первый advertisement пакет от устройства и дальнейшие advertisement пакеты не обнаружены. Немного странное поведение.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;В практике обычно используются настройка &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CALLBACK_TYPE_ALL_MATCHES&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CALLBACK_TYPE_FIRST_MATCH&lt;/code&gt;. Правильный тип зависит от конкретного случая. Если не знаете - используйте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CALLBACK_TYPE_ALL_MATCHES&lt;/code&gt;, это дает больше контроля при получении callback, если вы останавливаете сканирование после получения нужных результатов - фактически это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CALLBACK_TYPE_FIRST_MATCH&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;match-mode&quot;&gt;&lt;strong&gt;Match mode&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Настройка того, как Android определяет “совпадения”.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MATCH_MODE_AGGRESSIVE&lt;/code&gt;. Агрессивность обуславливается поиском минимального количества advertisement пакетов и устройств даже со слабым сигналом.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MATCH_MODE_STICKY&lt;/code&gt;. В противоположность, этот режим требует большего количества advertisement пакетов и хорошего уровня сигнала от устройств.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Я не тестировал эти настройки подробно, но я в основном использую &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MATCH_MODE_AGGRESSIVE&lt;/code&gt;, это помогает быстрее найти устройства.&lt;/p&gt;

&lt;h1 id=&quot;number-of-matches&quot;&gt;&lt;strong&gt;Number of matches&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Параметр определяет сколько advertisement данных необходимо для совпадения.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MATCH_NUM_ONE_ADVERTISEMENT&lt;/code&gt;. Одного пакета достаточно.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MATCH_NUM_FEW_ADVERTISEMENT&lt;/code&gt;. Несколько пакетов нужно для соответствия.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MATCH_NUM_MAX_ADVERTISEMENT&lt;/code&gt;. Максимальное количество advertisement данных, которые устройство может обработать за один временной кадр.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Нет большой необходимости в таком низкоуровнем контроле. Все что вам надо - быстро найти свое устройство, используйте первые 2 варианта.&lt;/p&gt;

&lt;h1 id=&quot;report-delay&quot;&gt;&lt;strong&gt;Report delay&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Задержка для вызова сallback в милисекундах. Если она больше нуля, Android будет собирать результаты в течение этого времени и вышлет их сразу все в обработчике &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onBatchScanResults&lt;/code&gt;. Важно понимать что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onScanResult&lt;/code&gt; не будет вызываться. Обычно применяется, когда есть несколько устройств одного типа и мы хотим дать пользователю выбрать одно из них. Единственная проблема здесь - предоставить информацию пользователю для выбора, это должно быть больше чем MAC-адрес.&lt;/p&gt;

&lt;p&gt;Важно: есть &lt;a href=&quot;https://devzone.nordicsemi.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-00-04-DZ-1046/2604.BLE_5F00_on_5F00_Android_5F00_v1.0.1.pdf&quot; target=&quot;_blank&quot;&gt;известный баг&lt;/a&gt; для Samsung S6 / Samsung S6 Edge, когда все результаты сканирования имеют один и тот же RSSI (уровень сигнала) при задержке больше нуля.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Прим. переводчика: в моему случае требовалось обнаружить 2 однотипных устройства, при этом каждое из них имело свое имя, которое отображалось в UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;кеширование-android-bluetooth-стека&quot;&gt;&lt;strong&gt;Кеширование Android Bluetooth стека&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Процесс сканирования дает вам список BLE устройств и при этом данные устройств “кешируются” в Bluetooth стеке. Там хранится основная информация: имя, MAC-адрес, тип адреса (публичный, случайный), тип устройства (Classic, Dual, BLE) и т.д.  Android нужны эти данные, чтобы подключится к устройству быстрее. Он кеширует все устройства, которые видит при сканировании. Для каждого из них записывается небольшой файл с данными. Когда вы пытаетесь подключиться к устройству, стек Android ищет соответствующий файл, чтобы прочитать данные для подключения. Важный момент - одного MAC-адреса недостаточно для успешного подключения к устройству!&lt;/p&gt;

&lt;h1 id=&quot;очистка-кеша&quot;&gt;&lt;strong&gt;Очистка кеша&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Bluetooth cache, как и любой другой, не существует вечно и есть 3 ситуации когда он очищается:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Выключение и включение системного переключателя Bluetooth,&lt;/li&gt;
  &lt;li&gt;Перезагрузка телефона,&lt;/li&gt;
  &lt;li&gt;Очистка в ручном режиме в настройках телефона.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Это достаточно неудобный момент для разработчиков, потому что телефон часто перезагружается, пользователь может включать-выключать самолетный режим. Есть еще различия между производителями телефонов, например на некоторых телефонах Samsung, кеш не очищался при выключении Bluetooth.&lt;/p&gt;

&lt;p&gt;Это значит, что нельзя полагаться на данные об устройстве из BT кеша. Есть небольшой трюк, он поможет узнать закешировано ли устройство или нет:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Get device object for a mac address&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;BluetoothDevice&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bluetoothAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRemoteDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;peripheralAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Check if the peripheral is cached or not&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deviceType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deviceType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothDevice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;DEVICE_TYPE_UNKNOWN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// The peripheral is not cached&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// The peripheral is cached&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Это важно, если нужно подключиться к устройству позже, не сканируя его. Подробнее об этом позже.&lt;/p&gt;

&lt;h1 id=&quot;непрерывное-сканирование&quot;&gt;&lt;strong&gt;Непрерывное сканирование?&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Вообще сканирование не следует делать непрерывно, это очень энергоемкая операция. Пользователи любят, когда батарея их смартфона работает долго. Если вам действительно нужно постоянное сканирование, например при поиске BLE-маячков, выберите настройки сканирования с низким потреблением и ограничивайте время сканирования, например когда приложение находится только на передноем плане (foreground), либо сканируйте с перерывами.&lt;/p&gt;

&lt;p&gt;В последнее время Google ограничивает (недокументированно) непрерывное сканирование:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;c Android  8.1 &lt;a href=&quot;https://stackoverflow.com/questions/48077690/ble-scan-is-not-working-when-screen-is-off-on-android-8-1-0&quot; target=&quot;_blank&quot;&gt;сканирование без фильтров блокируется при выключенном экране&lt;/a&gt;. Если у вас нет никаких &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ScanFilters&lt;/code&gt;, Android приостановит сканирование, когда экран выключен и продолжит, когда экран снова будет включен. &lt;a href=&quot;https://android.googlesource.com/platform/packages/apps/Bluetooth/+/319aeae6f4ebd13678b4f77375d1804978c4a1e1&quot; target=&quot;_blank&quot;&gt;Комментарии от Google.&lt;/a&gt; Это очевидно очередной способ энергосбережения от Google.&lt;/li&gt;
  &lt;li&gt;c Android 7 вы можете сканировать только в течение 30 минут, после чего Android меняет параметры на &lt;a href=&quot;https://blog.classycode.com/undocumented-android-7-ble-behavior-changes-d1a9bd87d983&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCAN_MODE_OPPORTUNISTIC&lt;/code&gt;.&lt;/a&gt; Очевидное решение, перезапускать сканирование с периодом менее, чем &lt;a href=&quot;https://android-review.googlesource.com/c/platform/packages/apps/Bluetooth/+/231662/2/src/com/android/bluetooth/gatt/ScanManager.java&quot; target=&quot;_blank&quot;&gt;30 мин&lt;/a&gt;. Посмотрите &lt;a href=&quot;https://android-review.googlesource.com/c/platform/packages/apps/Bluetooth/+/215844&quot; target=&quot;_blank&quot;&gt;commit&lt;/a&gt; в исходном коде.&lt;/li&gt;
  &lt;li&gt;с Android 7 запуск и останов сканирования более 5 раз за 30 секунд &lt;a href=&quot;https://blog.classycode.com/undocumented-android-7-ble-behavior-changes-d1a9bd87d983&quot; target=&quot;_blank&quot;&gt;временно отключает сканирование&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;непрерывное-сканирование-в-фоне&quot;&gt;&lt;strong&gt;Непрерывное сканирование в фоне&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Google значительно усложнил сканирование на переднем плане. Для фонового режима вы столкнетесь с еще большими трудностями!
Новые версии Android имеют лимиты на работу служб в фоновом режиме, обычно после 10 минут работы, фоновый сервис прекращает свою работу принудительно.
Посмотрите возможные решения этой проблемы:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Обсуждение на &lt;a href=&quot;https://stackoverflow.com/questions/51371372/beacon-scanning-in-background-android-o&quot; target=&quot;_blank&quot;&gt;StackOverflow&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Статья &lt;a href=&quot;http://www.davidgyoungtech.com/2017/08/07/beacon-detection-with-android-8&quot; target=&quot;_blank&quot;&gt;David Young&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;Прим. переводчика: я использовал &lt;a href=&quot;https://developer.android.com/guide/components/services&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Foreground Service&lt;/code&gt;&lt;/a&gt;, потому что после сканирования, будет длительный обмен данными с устройствами в процессе использованя аппы. Один из плюсов этого решения - работает в &lt;a href=&quot;https://developer.android.com/training/monitoring-device-state/doze-standby&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Doze Mode&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;проверка-разрешений-permissions&quot;&gt;&lt;strong&gt;Проверка разрешений (permissions)&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;Есть еще несколько важных моментов, прежде чем мы закончим статью. Для начала сканирования нужны системные разрешения (permissions):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android.permission.BLUETOOTH&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android.permission.BLUETOOTH_ADMIN&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android.permission.ACCESS_COARSE_LOCATION&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Убедитесь что все разрешения одобрены, или запросите их у пользователя. Разрешение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACCESS_COARSE_LOCATION&lt;/code&gt; Google считает “опасным” и для него требуется обязательное согласие пользователя.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hasPermissions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SDK_INT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VERSION_CODES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getApplicationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;checkSelfPermission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Manifest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ACCESS_COARSE_LOCATION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PackageManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;PERMISSION_GRANTED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;requestPermissions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Manifest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ACCESS_COARSE_LOCATION&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ACCESS_COARSE_LOCATION_REQUEST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Прим. переводчика, в моем проекте для корректной работы с BLE потребовалось еще 2 разрешения: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACCESS_FINE_LOCATION&lt;/code&gt; (для API&amp;lt;23) и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACCESS_BACKGROUND_LOCATION&lt;/code&gt; &lt;a href=&quot;https://stackoverflow.com/a/32730190/2154011&quot; target=&quot;_blank&quot;&gt;обсуждение на Stackoverflow&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;В итоге полный список разрешений включая версию Android10:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android.permission.BLUETOOTH&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android.permission.BLUETOOTH_ADMIN&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android.permission.ACCESS_COARSE_LOCATION&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android.permission.ACCESS_FINE_LOCATION&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android.permission.ACCESS_BACKGROUND_LOCATION&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;После получения всех нужный разрешений, нужно проверить включен Bluetooth, если нет - используйте &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Intent&lt;/code&gt; для запуска запроса на включение:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothAdapter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bluetoothAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BluetoothAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDefaultAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bluetoothAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Intent&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enableBtIntent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Intent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BluetoothAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ACTION_REQUEST_ENABLE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;startActivityForResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;enableBtIntent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;REQUEST_ENABLE_BT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;следующая-статья-подключени-и-отключение&quot;&gt;&lt;strong&gt;Следующая статья: подключени и отключение&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;На данный момент мы рассмотрели сканирование, в &lt;a href=&quot;https://fmaxx.github.io/android/ble/bluetoothlowenergy/2020/01/10/android-ble-part-2.html&quot; target=&quot;_blank&quot;&gt;следующей статье&lt;/a&gt; мы погрузимся глубже в процесс подключения и отключения к устройствам.&lt;/p&gt;

&lt;p&gt;Спасибо!&lt;/p&gt;</content><author><name></name></author><category term="Android" /><category term="BLE" /><category term="BluetoothLowEnergy" /><summary type="html">Перевод статьи Making Android BLE work — part 1. внимание: в цикле статей используется минимальная версия - Android 6</summary></entry><entry><title type="html">Provisioning ios часть-2.</title><link href="/ios/provisioning/2017/08/30/ios-provisioning-part-2.html" rel="alternate" type="text/html" title="Provisioning ios часть-2." /><published>2017-08-30T10:00:00+00:00</published><updated>2017-08-30T10:00:00+00:00</updated><id>/ios/provisioning/2017/08/30/ios-provisioning-part-2</id><content type="html" xml:base="/ios/provisioning/2017/08/30/ios-provisioning-part-2.html">&lt;p&gt;Перевод статьи &lt;a href=&quot;http://martiancraft.com/blog/2017/07/demystifying-provisioning-part2/&quot;&gt;DEMYSTIFYING IOS PROVISIONING PART 2: CREATING AND ASSIGNING CERTIFICATES AND PROFILES&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/ios/provisioning/2017/08/20/ios-provisioning-part-1.html&quot;&gt;Перевод первой части здесь&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;разрушая-мифы-ios-provisioning-часть-2-управление-сертификатами-и-профилями&quot;&gt;Разрушая мифы iOS Provisioning, часть 2: управление сертификатами и профилями.&lt;/h1&gt;

&lt;p&gt;В &lt;a href=&quot;/ios/provisioning/2017/08/20/ios-provisioning-part-1.html&quot;&gt;первой части&lt;/a&gt; этой статьи мы узнали что такое сертификаты и профили, как они используются в разработке под платформу Apple. Во второй части статьи мы обсудим как создать все нужное для подписи приложения в личном кабинете разработчика на сайте Apple и как это подключить в Xcode.&lt;/p&gt;

&lt;p&gt;Наши задачи:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;создать App ID&lt;/li&gt;
  &lt;li&gt;сгенерировать &lt;em&gt;development&lt;/em&gt; and &lt;em&gt;App Store&lt;/em&gt; сертификаты&lt;/li&gt;
  &lt;li&gt;создать &lt;em&gt;development&lt;/em&gt; и &lt;em&gt;distribution&lt;/em&gt; provisioning профили&lt;/li&gt;
  &lt;li&gt;создать схему (назовем ее &lt;em&gt;App Store&lt;/em&gt;) в Xcode проекте&lt;/li&gt;
  &lt;li&gt;настроить сертификаты и профили в схеме&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Многие задачи выполняются в разделе Provisioning в личном кабинете разработчика на сайте &lt;a href=&quot;http://developer.apple.com&quot;&gt;Apple Developer&lt;/a&gt;.
&lt;img src=&quot;/images/2017-08-30-ios-provisioning-part-2/1.png&quot; alt=&quot;Provisioning&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Если у вас уже есть оплаченный аккаунт, откройте &lt;a href=&quot;http://developer.apple.com&quot;&gt;Apple Developer website&lt;/a&gt;, выберете “Account” -&amp;gt; “Certificates, Identifiers &amp;amp; Profiles.”&lt;/p&gt;

&lt;h2 id=&quot;создание-app-ids&quot;&gt;Создание App IDs&lt;/h2&gt;
&lt;p&gt;App ID это уникальный идентификатор для регистрации вашего приложения. Первым делом, давайте создадим его.
&lt;img src=&quot;/images/2017-08-30-ios-provisioning-part-2/2.png&quot; alt=&quot;App IDs&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Шаги для регистрации на сайте apple разрабочика:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Выберите “App IDs” под вкладкой “Identifiers”&lt;/li&gt;
  &lt;li&gt;Нажмите “+” вверху списка&lt;/li&gt;
  &lt;li&gt;Введите имя для App ID (используйте название приложения или название + дополнительное имя)&lt;/li&gt;
  &lt;li&gt;Выберите “Explicit App ID” и введите ваш Bundle ID в текстовое поле&lt;/li&gt;
  &lt;li&gt;Под “App Services” раздела, выберите сервисы нужные для вашего приложения (их можно будет отредактировать позже)&lt;/li&gt;
  &lt;li&gt;Нажимаем “Continue”&lt;/li&gt;
  &lt;li&gt;Если все хорошо - выбираем “Register”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Это все. Bundle ID зарегистрирован и можно генерировать профили на основе этого ID. Если в будущем вы измените Bundle ID, придется также удалить App ID (если он не будет использоваться) и пересоздать новый App ID.&lt;/p&gt;

&lt;p&gt;Так, а в чем тогда разница между App ID и Bundle ID? В общем App ID это уникальный идентификатор приложения в экосистеме App Store, не может быть двух приложений там с одинаковыми App ID. Bunlde ID это идентификатор учетной записи разработчика (используется обратная доменная запись - com.mysite.appname).&lt;/p&gt;

&lt;h2 id=&quot;создание-сертификатов&quot;&gt;Создание сертификатов&lt;/h2&gt;
&lt;p&gt;Я расскажу вам как создавать сертификат для App Store (другие сертификаты созадаются аналогично). Сертификат можно использовать для разработки и распространения всех ваших приложений.&lt;/p&gt;

&lt;p&gt;Первое - нужно создать &lt;em&gt;Certificate Signing Request (CSR)&lt;/em&gt; в личном кабинете разработчика, для этого:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;на вашем Mac откройте &lt;em&gt;Keychain Access&lt;/em&gt; (находится тут - /Applications/Utilities).&lt;/li&gt;
  &lt;li&gt;далее в Keychain Access &amp;gt; Certificate Assistant &amp;gt; Request a Certificate From a Certificate Authority.&lt;/li&gt;
  &lt;li&gt;в окне введите ваш email и имя, затем выберите “Save to disk” в опции “Request is”.&lt;/li&gt;
  &lt;li&gt;жмите Contunue и сохраните файл на компьютере Mac.
&lt;img src=&quot;/images/2017-08-30-ios-provisioning-part-2/3.png&quot; alt=&quot;Certificate Signing Request&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Теперь у на есть CSR, и мы готовы сгенерировать рабочий сертификат, для этого заходим в личный кабинет и:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;идем в раздел Certificates, Identifiers &amp;amp; Profiles &amp;gt; Certificates &amp;gt; All&lt;/li&gt;
  &lt;li&gt;нажимаем “+” чтобы создать новый сертификат&lt;/li&gt;
  &lt;li&gt;выбираем “iOS App Development” для создания development сертификата для отладки на зарегистрированных устройствах&lt;/li&gt;
  &lt;li&gt;Continue&lt;/li&gt;
  &lt;li&gt;дальше будут инструкции как сгенерировать CSR (мы уже это сделали!)&lt;/li&gt;
  &lt;li&gt;загрузите запрос CSR (инструкции прилагаются)&lt;/li&gt;
  &lt;li&gt;жмем на Download для загрузки сгенерированного сертификата, после этого двойным кликом мышки добавляем его в Keychain Access
&lt;img src=&quot;/images/2017-08-30-ios-provisioning-part-2/4.png&quot; alt=&quot;certificate&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Для проверки что сертификат установлен в системе, зайдите в Keychain Access &amp;gt; Login Keychain &amp;gt; Certificate, вы должны увидеть публичный и приватные ключи с префиксом “iPhone Developer” (это значит что сертификат для подписки ios приложений, у сертификатов для Mac приложений будет - “Mac development”).&lt;/p&gt;

&lt;p&gt;“App Store and Ad Hoc” сертификаты создаются аналогично (разница в п.3 только), сертификат действителен один год, по окончании срока его нужно удалить и создать новый, при этом все связанные provisioning профили аннулируются и их также придется повторно сгенерировать.&lt;/p&gt;

&lt;h2 id=&quot;регистрация-устройств&quot;&gt;Регистрация устройств&lt;/h2&gt;
&lt;p&gt;Тестовые устройства для отладки приложений регистрируются в помощью UDID - уникального идентификатора. Он включается в provisioning профили (создадим на следующем шаге), при добавлении/удалении нового устройства нужно повторно сгенерировать provisioning профиль. Поэтому мы сделаем этот шаг первым.&lt;/p&gt;

&lt;p&gt;UDID устройства можно найти в iTunes или в Xcode (подключите устройство к Mac запустите Xcode, найдите в  Window &amp;gt; Devices &amp;gt; ваше устройство и скопируйте UDID строку в разделе “Identifier”), это уникальный идентификатор вашего физического устройства.&lt;/p&gt;

&lt;p&gt;Для регистрации UDID:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;идем в Certificates, Identifiers &amp;amp; Profiles &amp;gt; Devices &amp;gt; All (личный кабинет разработчика)&lt;/li&gt;
  &lt;li&gt;жмем “+” для добавления нового устройства&lt;/li&gt;
  &lt;li&gt;вводим имя, мы рекомендуем формат для этого “Имя/Тип устройства/Добавлен или Изменен/Дата”, удобно для больший команд&lt;/li&gt;
  &lt;li&gt;вводим UDID&lt;/li&gt;
  &lt;li&gt;нажимаем “Contunue”
&lt;img src=&quot;/images/2017-08-30-ios-provisioning-part-2/5.png&quot; alt=&quot;UDID&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Теперь устройство зарегистрировано в вашей учетной записи, также можно добавить его у нескольких разработчиков. После регистрации устройство можно включать в provisioning профиль для запуска приложения на нем.&lt;/p&gt;

&lt;h2 id=&quot;создание-профилей&quot;&gt;Создание профилей&lt;/h2&gt;
&lt;p&gt;Provisionig профиль это ключевой момент для подписи приложения. Он определяет какие устройства имеют право запускать подписанное приложение, указывает на сертификат для подписи приложения, а также какие сервисы будут доступны для приложения на этом устройстве (iCloud, APNs и т.д).&lt;/p&gt;

&lt;p&gt;Создать provision профиль довольно просто в личном кабинете разработчика, для этого:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;идем в Certificates, Identifiers &amp;amp; Profiles &amp;gt; Provisioning Profiles &amp;gt; All и видим список созданных профилей&lt;/li&gt;
  &lt;li&gt;жмем “+” для добавления нового профиля&lt;/li&gt;
  &lt;li&gt;выбираем из списка “iOS App Development” и жмем на Continue&lt;/li&gt;
  &lt;li&gt;далее выберите App ID (созданный ранее) и опять Continue&lt;/li&gt;
  &lt;li&gt;на этом экране вам предлагают выбрать сертификат для подписи профиля, указываем только что созданный сертификат, и жмем Contunue (можно выбрать несколько сертфикатов для профиля, каждый из них может подписать приложение)&lt;/li&gt;
  &lt;li&gt;на экране “Select Devices” укажите устройства, на которых вы будете запускать приложение&lt;/li&gt;
  &lt;li&gt;экран “Generate” предлагает дать профилю читаемое имя, в MartianCraft мы обычно используем формат: &lt;em&gt;[App Name]: [Profile Type] Profile&lt;/em&gt; (например “MartianApp: Development Profile”), жмем по привычке Continue&lt;/li&gt;
  &lt;li&gt;итак на последнем экране (вы тоже не любите неуместное употребление “крайний”?) можно загрузить созданный профиль. После загрузки перетащите его на иконку Xcode в доке (быстрый способ добавить файл в ~/Library/MobileDevice/Provisioning Profiles, это директория для всех профилей, можно сделать это вручную).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Для создания &lt;em&gt;App Store&lt;/em&gt; профиля повторяем весь процесс, но на шаге 3 выбираем “App Store”, это профиль для распространия приложений в AppStore, и поэтому в него нельзя добавить устройства.&lt;/p&gt;

&lt;p&gt;Важное замечание, если сертификат связанный с профилем будет отозван, или изменится список устройств для этого профиля, то придется обновить provisioning профиль.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2017-08-30-ios-provisioning-part-2/6.png&quot; alt=&quot;Provisioning profile details&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Процесс обновления профиля простой: идем в Certificates, Identifiers &amp;amp; Profiles in the provisioning portal &amp;gt; Provisioning Profiles &amp;gt; All, находим нужный профиль и жмем Edit для изменений App ID, настроек сертификата или списка устройств.&lt;/p&gt;

&lt;h2 id=&quot;работа-с-схемами-и-сертификатами-в-xcode&quot;&gt;Работа с схемами и сертификатами в Xcode&lt;/h2&gt;
&lt;p&gt;Наконец-то у нас есть все для подписи iOS приложения, мы соберем все вместе внутри Xcode и свяжем сертификат и provisioning профиль в особую схему.&lt;/p&gt;

&lt;p&gt;По умолчанию, XCode автоматически создает две схемы: Debug и Release. Воспользуемся ими, для Debug схемы мы будем использовать Development профиль и сертификат, для Release - AppStore профиль и сертификат. 
Открываем “General” вкладку и ищем там “targets” (помним что для этого используем сертификаты добавленные в Keychain и Provisioning profiles это папка в XCode см. выше).&lt;/p&gt;

&lt;p&gt;Снимаем галочку “Automatically manage signing”, так как мы будем подписывать “самостоятельно”. После этого появятся две секции: “Signing (Debug)” и “Signing (Release).”
&lt;img src=&quot;/images/2017-08-30-ios-provisioning-part-2/7.png&quot; alt=&quot;Manual signing&quot; /&gt;&lt;/p&gt;

&lt;p&gt;В Signing (Debug) &amp;gt; Provisioning Profile выставляем Development профиль, а в Signing (Release) - AppStore профиль.&lt;/p&gt;

&lt;p&gt;Это все что нужно сделать для работы с профилями в “ручном” режиме. Мне кажется не так уж и сложно :) Теперь можно и поработать.&lt;/p&gt;

&lt;h2 id=&quot;частые-ошибки-и-как-их-исправить&quot;&gt;Частые ошибки и как их исправить&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Provisioning Profile doesn’t match bundle ID&lt;/strong&gt; Это значит что provisioning профиль был сгенерирован с неправильным App ID/Bundle ID и профиль содержит Bundle ID от другого проекта.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Code Signing Entitlements file do not match those specified in your provisioning profile&lt;/strong&gt; Локальные права не соответствуют профилю (посмотрите Capabilites вкладку). При добавлении сервисов типа iCloud, Keychain они должны быть зарегистрированы в профиле.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;No Matching Provisioning Profiles Found&lt;/strong&gt; случается когда сертификату не имеет связанных профилей для подписи приложений. Проверьте что профиль импортирован в Xcode (можно вручную открыть папку: ~/Library/MobileDevice/Provisioning Profiles), посл этого в личном кабинете убедитесь, что профиль использует необходимый сертификат, в случае необходимости обновите профиль.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Более подробно ошибки описаны на сайте для разработчиков &lt;a href=&quot;https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/Troubleshooting/Troubleshooting.html&quot;&gt;Apple Developer&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;сборка-и-подпись-приложения&quot;&gt;Сборка и подпись приложения&lt;/h2&gt;
&lt;p&gt;С нашими настройками удобно создавать сборки приложения и запускать на зарегистрированных устройствах.
Для выпуска приложения под AppStore следует выбрать “Generic iOS Device”, затем Product &amp;gt; Archive, будет создан архив, подписанный профилем-сертификатом “AppStore Provisioning”.&lt;/p&gt;

&lt;p&gt;Отправка приложения происходит в Xcode Organizer (Window &amp;gt; Organizer), выбирайте архив для отправки в AppStore и следуйте инструкциям в окне. Не забываем выбрать “Use local signing assets” при выборе команды разработчиков.&lt;/p&gt;

&lt;h2 id=&quot;выводы&quot;&gt;Выводы&lt;/h2&gt;
&lt;p&gt;В этих двух статьях мы рассмотрели все этапы provisioning процесса, для новичков это может показаться непростым делом, к счастью это нужно делать только раз в год и для старта нового проекта.
Все коснулись всех базовых моментов provisioning и сборки приложения, если еще остались вопросы, у Apple есть несколько видео с прошлых WWDCC для более глубокого понимания процесса, ссылки ниже.&lt;/p&gt;

&lt;h2 id=&quot;ссылки-на-видео&quot;&gt;Ссылки на видео&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2016/401/&quot;&gt;WWDC ’16: “What’s New in Xcode App Signing”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2014/705/&quot;&gt;WWDC ’14: “Distributing Enterprise Apps”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2015/304/&quot;&gt;WWDC ’15: “iTunes Connect: Development to Distribution”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2016/413/&quot;&gt;WWDC ’16: “Introduction to Xcode”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Спасибо!&lt;/p&gt;</content><author><name></name></author><category term="ios" /><category term="provisioning" /><summary type="html">Перевод статьи DEMYSTIFYING IOS PROVISIONING PART 2: CREATING AND ASSIGNING CERTIFICATES AND PROFILES</summary></entry></feed>