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

Конечно, существуют ограничения на количество человек. Bluetooth может поддерживать до 4 соединений одновременно, а Wi-Fi  —  в диапазоне от 10 до 100 (в зависимости от того, доступны ли настенные маршрутизаторы в качестве мостов).

В отличие от Nearby Messages API, который работает на Android и iOS, Nearby Connections API доступен только на Android. В то же время у Nearby Connections API есть два ключевых преимущества.

  1. Он позволяет передавать неограниченное количество данных по Bluetooth и одноранговому Wi-Fi, в то время как Nearby Messages API предназначен только для небольших пакетов (до 4 Кб).
  2. Nearby Connections API предоставляет пользователям возможность общаться друг с другом без интернет-соединения, а для Nearby Messages API требуется подключение.

Чтобы разрешить приложению включить Nearby Connections API для пользователя, необходимо сначала добавить в модуль приложения последнюю версию описанной ниже зависимости Gradle.

dependencies {
implementation 'com.google.android.gms:play-services-nearby:18.0.0'
}

Не менее важно и то, что в настоящее время Android запрашивает у пользователя разрешения ACCESS_FINE_LOCATION для приложения от его имени.

В манифест необходимо добавить следующие разрешения, даже если пользователю не будет предложено их предоставить:

  • BLUETOOTH;
  • BLUETOOTH_ADMIN;
  • ACCESS_WIFI_STATE;
  • CHANGE_WIFI_STATE;
  • ACCESS_FINE_LOCATION.

До Android 10 (уровень API 29) приложению требовалось только запросить разрешение ACCESS_COARSE_LOCATION. Однако для устройств на базе Android 10 и выше требуется ACCESS_FINE_LOCATION. Поскольку разрешение на точное местоположение подразумевает разрешение на грубое местоположение в рамках Android  —  и обратное неверно  —  приложение может просто запросить ACCESS_FINE_LOCATION для всех уровней API.

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

Чтобы два человека (устройство X и устройство Y) приложения могли подключаться и обмениваться контентом с помощью Nearby Connections API, необходимо проделать нижеописанные действия.

Оба пользователя должны выбрать одну и ту же стратегию

Nearby Connections API требует, чтобы вы указали одноранговую стратегию (Strategy), которую приложение будет использовать для связи.

Существует три варианта стратегии:

  1. P2P_CLUSTER;
  2. P2P_STAR;
  3. P2P_POINT_TO_POINT..

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

  • P2P_CLUSTER, как правило, приводит к подключениям с более низкой пропускной способностью, чем P2P_STAR.
  • Из трех стратегий P2P_POINT_TO_POINT обычно обладает максимально возможной пропускной способностью.

Как только вы определитесь со Strategy, оповещатель должен использовать ее для оповещения:

AdvertisingOptions.Builder().setStrategy(Strategy.P2P_STAR).build()

Обнаружитель должен использовать ту же Strategy во время обнаружения:

DiscoveryOptions.Builder().setStrategy(Strategy.P2P_STAR).build()

Приложение на обоих устройствах должно использовать один и тот же serviceId

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

Связь происходит только между одним и тем же Id-сервисом. Например, serviceId=”YouTube” никогда не обнаружит устройство с оповещением serviceId=”Gmail”. Идентификатор должен быть уникальным. Поэтому рекомендуется использовать имя пакета приложения в качестве уникального идентификатора для всех вызовов API, требующих этого.

Вызов и ответ

Когда приложение на устройстве X вызывает функцию startAdvertising(), передается сигнал по Bluetooth.

Когда сигнал достигает устройства Y, Nearby Connections API запускает обратный вызов EndpointDiscoveryCallback в приложении на устройстве Y.

// private val connectionsClient: ConnectionsClient by lazy{Nearby.getConnectionsClient(this)}

private fun startAdvertising(){
   // Примечание: Оповещение может не сработать. Мы не рассматриваем случаи сбоев, чтобы не усложнять процесс демонстрации.
   // Мы также продемонстрируем connectionLifecycleCallback позже.
   connectionsClient.startAdvertising(yourCodeName, packageName, connectionLifecycleCallback,
                                      AdvertisingOptions.Builder().setStrategy(Strategy.P2P_STAR).build())
}

В ответ на обнаружение оповещения с устройства X устройство Y может отправить запрос на подключение устройству X.

Примечание. В режиме обнаружения устройство Y потенциально может обнаруживать конечные точки со 100 близлежащих устройств, и в этом случае метод onEndpointFound() будет срабатывать 100 раз. Следовательно, пользователю обычно приходится выбирать, на какое устройство отправлять запрос на подключение. В нашем примере пользователь выбирает устройство X.

private fun startDiscovery(){
   val endpointDiscoveryCallback:EndpointDiscoveryCallback =
       object:EndpointDiscoveryCallback(){
           override fun onEndpointFound(endpointId: String, info: DiscoveredEndpointInfo) {
               connectionsClient.requestConnection(yourCodeName, endpointId, connectionLifecycleCallback)
           }

           override fun onEndpointLost(endpointId: String) {
               Log.d(TAG, "onEndpointLost")
           }

       }
   // Примечание: Обнаружение может не сработать. Мы не рассматриваем случаи сбоев, чтобы не усложнять процесс демонстрации. 
   connectionsClient.startDiscovery(packageName,endpointDiscoveryCallback,
                                    DiscoveryOptions.Builder().setStrategy(Strategy.P2P_STAR).build())
}

Установление связи

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

Фактическое принятие происходит в рамках обратного вызова ConnectionLifecycleCallback. Как показано в предыдущих фрагментах, оповещатель передает этот обратный вызов API через вызов метода startAdvertising(), в то время как обнаружитель передает обратный вызов через вызов метода requestConnection().

Тем не менее запросы на подключение доставляются в ConnectionLifecycleCallback#onConnectionInitiated()

// Обратный вызов для подключения к другим устройствам: как оповещатель, так и обнаружитель должны
// реализовать его.
private val connectionLifecycleCallback = object:ConnectionLifecycleCallback(){
   override fun onConnectionInitiated(endpointId: String, connectionInfo: ConnectionInfo) {
       Log.d(TAG, "onConnectionInitiated: accepting connection")
       connectionsClient.acceptConnection(endpointId,payloadCallback)
       friendCodeName = connectionInfo.endpointName
   }

   override fun onConnectionResult(endpointId: String, result: ConnectionResolution) {
      
      
       // Вы можете использовать функцию if(result.status.isSuccess){}else{} или проверить statusCode с помощью "when"

       when (result.status.statusCode) {
           ConnectionsStatusCodes.STATUS_OK -> {
               // Мы подключились! Теперь можем начать отправлять и получать данные.

               // Как только вы успешно подключитесь к устройствам своих друзей, вы сможете выйти из
               // режима обнаружения, чтобы прекратить обнаружение других устройств
               connectionsClient.stopDiscovery();
               // Вы также можете завершить передачу оповещений
               connectionsClient.stopAdvertising()

               friendEndpointId = endpointId
           }
           ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED -> {
               // Соединение было отклонено одной или обеими сторонами.
           }
           ConnectionsStatusCodes.STATUS_ERROR -> {
               // Соединение прервалось до того, как его удалось принять.
           }
           else -> {
               // Неизвестный код состояния
           }
       }
   }

   override fun onDisconnected(endpointId: String) {
       Log.d(TAG, "onDisconnected: from friend")
       // Выполните необходимую очистку
   }
}

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

API предоставляет уникальный токен для каждого соединения через объект ConnectionInfo, принадлежащий onConnectionInitiated(String endpointId, ConnectionInfo info). Вы можете получить токен из info.getAuthenticationDigits(). .

Рекомендуем показывать токены обоим пользователям, чтобы они могли визуально подтвердить, что они равны. Если визуальное подтверждение невозможно из-за того, что пользователи не могут видеть экраны друг друга, вы можете попросить одно устройство зашифровать необработанный токен, отправить его на другое для расшифровки и сравнения, прежде чем начать делиться конфиденциальным контентом (подробнее о шифровании Android).

Общение

Пока два устройства подключены, они могут отправлять контент друг другу с помощью вызова метода sendPayload() и обратного вызова onPayloadReceived().

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

Каждое устройство зарегистрировало обратный вызов onPayloadReceived() через Nearby.getConnectionsClient(context).acceptConnection(endpointId, payloadCallback). Затем в любое время, когда у пользователей возникает желание поделиться контентом, они просто вызывают:

connectionsClient.sendPayload(
       toEndpointId, Payload.fromBytes(someData.getBytes(UTF_8)))

Резюме

Nearby Connections API идеально подходит для двусторонней связи между пользователями Android, особенно если они хотят обмениваться контентом. При этом нет потребности ни в сотовой системе передачи данных, ни в подключении к интернету.

Для получения более подробной информации о том, как использовать Nearby Connections API, см. документацию по Nearby Connections API.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Isai Damier: Two-way communication without internet: Nearby Connections

Предыдущая статьяПрограмма на Си для проверки числа: положительное или отрицательное
Следующая статьяПогружение в базы данных